Reputation: 5114
The problem with this is that the huge objects will be copied into the maps
Huge huge1(some,args);
Huge huge2(some,args);
std::map<int,Huge> map1;
std::map<Huge,int> map2;
map1.insert({0,huge1});
map2.insert({huge2,0});
how can I guarantee a move? Will this work or is there more to it?
map1.insert({0,std::move(huge1)});
map2.insert({std::move(huge2),0});
Upvotes: 37
Views: 27044
Reputation: 53339
std::map::insert
has an overload for R-values:
std::pair<iterator,bool> insert(value_type&&);
Any expression which binds to this overload will invoke R-value constructors. Since std::map<K,V>::value_type
is std::pair<const key_type, mapped_type>
, and std::pair
has a constructor that takes R-values:
template<class U1, class U2>
pair(U1&& x, U2&& y);
then you are guaranteed that R-value constructors for key_type
and mapped_type
will be invoked, both in the creation of the pair
object, and in the map insertion, as long as you insert the pair using an expression that creates R-values, such as:
map1.insert(std::make_pair(0, Huge());
OR
map1.insert(std::make_pair(0, std::move(huge1));
Of course, all of this is dependent on Huge
having a proper R-value constructor:
Huge(Huge&& h)
{
...
}
Finally, you can also use std::map::emplace
if you simply want to construct a new Huge
object as an element in the map.
Upvotes: 57
Reputation: 10256
Along with the above, you can also rely on std::unique_ptr<>
's lack of a copy constructor, though this changes the interface slightly.
#include <iostream>
#include <map>
#include <memory>
class Huge {
public:
Huge(int i) : x{i} {}
int x;
};
using HugePtrT = std::unique_ptr<Huge>;
using MyMapT = std::map<int, HugePtrT>;
int
main() {
MyMapT myMap;
myMap[42].reset(new Huge{1});
std::cout << myMap[42]->x << std::endl;
myMap[43] = std::move(myMap[42]);
if (myMap[42])
std::cout << "42: " << myMap[42]->x << std::endl;
if (myMap[43])
std::cout << "43: " << myMap[43]->x << std::endl;
}
which produces the expected output:
1
43: 1
If you omit the std::move()
call, the program will fail to compile. Similarly, you could use .reset()
to assign the pointer.
This has the advantage that it will work on classes that don't have an R-value constructor, is very light weight, memory ownership is clearly defined, and gives you a boost::optional<>
-like semantics. An argument could be made that std::unique_ptr
is lighter weight than an object that has been moved via an R-value constructor because an R-Value moved object requires an allocation (though to be fair, all of the C++11 compilers that I'm aware of support Return Value Optimizations or copy elision), even though the guts of the object are moved.
The reason std::unique_ptr<>
works like this is because std::unique_ptr<>
does not have a copy constructor, it only has a move constructor.
Upvotes: 2
Reputation: 122011
An alternative that avoids both copying and moving would be to use std::map::emplace()
. From the linked reference page:
Inserts a new element to the container. The element is constructed in-place, i.e. no copy or move operations are performed. The constructor of the element type (value_type, that is, std::pair) is called with exactly the same arguments as supplied to the function, forwarded with std::forward(args)....
Upvotes: 6
Reputation: 474436
You could do that (the {0,std::move(huge1)}
part). But you could also skip the middleman (assuming you're constructing the objects within the function) like this:
map1.emplace(std::piecewise_construct, 0, std::forward_as_tuple(some, args));
map2.emplace(std::piecewise_construct, std::forward_as_tuple(some, args), 0);
Or, if your function is given the objects, you can still use emplace
:
map1.emplace(0, std::move(huge1));
map2.emplace(std::move(huge1), 0);
Upvotes: 16