Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stealing resources from std::map's keys allowed?

In C++, is it OK to steal resources from a map that I do not need afterwards anymore? More precisely, assume I have a std::map with std::string keys and I want to construct a vector out of it by stealing the resources of the maps keys using std::move. Note that such a write access to the keys corrupts the internal datastructure (ordering of keys) of the map but I won't use it afterwards.

Question: Can I do this without any problems or will this lead to unexpected bugs for example in the destructor of the map because I accessed it in a way that std::map was not intended for?

Here is an example program:

#include<map>
#include<string>
#include<vector>
#include<iostream>
using namespace std;
int main(int argc, char *argv[])
{
    std::vector<std::pair<std::string,double>> v;
    { // new scope to make clear that m is not needed 
      // after the resources were stolen
        std::map<std::string,double> m;
        m["aLongString"]=1.0;
        m["anotherLongString"]=2.0;
        //
        // now steal resources
        for (auto &p : m) {
            // according to my IDE, p has type 
            // std::pair<const class std::__cxx11::basic_string<char>, double>&
            cout<<"key before stealing: "<<p.first<<endl;
            v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));
            cout<<"key after stealing: "<<p.first<<endl;
        }
    }
    // now use v
    return 0;
}

It produces the output:

key before stealing: aLongString
key after stealing: 
key before stealing: anotherLongString
key after stealing: 

EDIT: I would like to do this for the entire content of a large map and save dynamic allocations by this resource stealing.

like image 652
phinz Avatar asked Feb 22 '20 07:02

phinz


2 Answers

You're doing undefined behavior, using const_cast to modify a const variable. Don't do that. The reason it's const is because maps are sorted by their keys. So modifying a key in-place is breaking the underlying assumption the map is built on.

You should never use const_cast to remove const from a variable and modify that variable.

That being said, C++17 has the solution for your problem: std::map's extract function:

#include <map>
#include <string>
#include <vector>
#include <utility>

int main() {
  std::vector<std::pair<std::string, double>> v;
  std::map<std::string, double> m{{"aLongString", 1.0},
                                  {"anotherLongString", 2.0}};

  auto extracted_value = m.extract("aLongString");
  v.emplace_back(std::make_pair(std::move(extracted_value.key()),
                                std::move(extracted_value.mapped())));

  extracted_value = m.extract("anotherLongString");
  v.emplace_back(std::make_pair(std::move(extracted_value.key()),
                                std::move(extracted_value.mapped())));
}

And don't using namespace std;. :)

like image 107
druckermanly Avatar answered Sep 24 '22 00:09

druckermanly


Your code attempts to modify const objects, so it has undefined behavior, as druckermanly's answer correctly points out.

Some other answers (phinz's and Deuchie's) argue that the key must not be stored as a const object because the node handle resulted from extracting nodes out of the map allow non-const access to the key. This inference may seem plausible at first, but P0083R3, the paper that introduced the extract functionalities), has a dedicated section on this topic that invalidates this argument:

Concerns

Several concerns have been raised about this design. We will address them here.

Undefined behavior

The most difficult part of this proposal from a theoretical perspective is the fact that the extracted element retains its const key type. This prevents moving out of it or changing it. To solve this, we have provided the key accessor function, which provides non-const access to the key in the element held by the node handle. This function requires implementation "magic" to ensure that it works correctly in the presence of compiler optimizations. One way to do this is with a union of pair<const key_type, mapped_type> and pair<key_type, mapped_type>. The conversion between these can be effected safely using a technique similar to that used by std::launder on extraction and reinsertion.

We do not feel that this poses any technical or philosophical problem. One of the reasons the Standard Library exists is to write non-portable and magical code that the client can’t write in portable C++ (e.g. <atomic>, <typeinfo>, <type_traits>, etc.). This is just another such example. All that is required of compiler vendors to implement this magic is that they not exploit undefined behavior in unions for optimization purposes—and currently compilers already promise this (to the extent that it is being taken advantage of here).

This does impose a restriction on the client that, if these functions are used, std::pair cannot be specialized such that pair<const key_type, mapped_type> has a different layout than pair<key_type, mapped_type>. We feel the likelihood of anyone actually wanting to do this is effectively zero, and in the formal wording we restrict any specialization of these pairs.

Note that the key member function is the only place where such tricks are necessary, and that no changes to the containers or pair are required.

(emphasis mine)

like image 23
L. F. Avatar answered Sep 22 '22 00:09

L. F.