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 map
s 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.
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;
. :)
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>
andpair<key_type, mapped_type>
. The conversion between these can be effected safely using a technique similar to that used bystd::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 thatpair<const key_type, mapped_type>
has a different layout thanpair<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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With