user2015453
user2015453

Reputation: 5114

Moving an object into a map

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

Answers (4)

Charles Salvia
Charles Salvia

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

Sean
Sean

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

hmjd
hmjd

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

Nicol Bolas
Nicol Bolas

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

Related Questions