Перемещение объекта в карту
Проблема заключается в том, что огромные объекты будут скопированы в карты
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});
как я могу гарантировать ход? Будет ли это работать или есть еще больше?
map1.insert({0,std::move(huge1)});
map2.insert({std::move(huge2),0});
Ответы
Ответ 1
std::map::insert
имеет перегрузку для значений R:
std::pair<iterator,bool> insert(value_type&&);
Любое выражение, которое связывается с этой перегрузкой, вызывается конструкторами R-значения. Поскольку std::map<K,V>::value_type
есть std::pair<const key_type, mapped_type>
, а std::pair
имеет конструктор, который принимает R-значения:
template<class U1, class U2>
pair(U1&& x, U2&& y);
тогда вам гарантируется, что конструкторы R-value для key_type
и mapped_type
будут вызваны как при создании объекта pair
, так и при вставке карты, если вы вставляете пару, используя выражение, которое создает значения R, такие как:
map1.insert(std::make_pair(0, Huge());
ИЛИ
map1.insert(std::make_pair(0, std::move(huge1));
Конечно, все это зависит от Huge
наличия правильного конструктора R-значения:
Huge(Huge&& h)
{
...
}
Наконец, вы также можете использовать std::map::emplace
, если хотите просто создать новый объект Huge
как элемент на карте.
Ответ 2
Вы можете сделать это (часть {0,std::move(huge1)}
). Но вы также можете пропустить посредника (предполагая, что вы строите объекты внутри функции) следующим образом:
map1.emplace(std::piecewise_construct, 0, std::forward_as_tuple(some, args));
map2.emplace(std::piecewise_construct, std::forward_as_tuple(some, args), 0);
Или, если вашей функции заданы объекты, вы все равно можете использовать emplace
:
map1.emplace(0, std::move(huge1));
map2.emplace(std::move(huge1), 0);
Ответ 3
Альтернативой, которая позволяет избежать как копирования, так и перемещения, будет использование std::map::emplace()
. На ссылочной странице:
Вставляет новый элемент в контейнер. Элемент построен на месте, т.е. операции копирования и перемещения не выполняются. Конструктор типа элемента (value_type, т.е. std:: pair) вызывается с точно такими же аргументами, как указано к функции, перенаправленной с помощью std:: forward (args)....
Ответ 4
Наряду с вышесказанным вы также можете положиться на std::unique_ptr<>
отсутствие конструктора копирования, хотя это немного меняет интерфейс.
#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;
}
который выдает ожидаемый результат:
1
43: 1
Если вы опустите вызов std::move()
, программа не сможет скомпилировать. Аналогично, вы можете использовать .reset()
для назначения указателя.
Это имеет то преимущество, что он будет работать на классах, у которых нет конструктора R-значения, очень легкий, владение памятью четко определено и дает вам boost::optional<>
-подобную семантику. Можно было бы привести аргумент, что std::unique_ptr
является более легким весом, чем объект, который был перемещен с помощью конструктора R-значения, потому что объект, перемещенный по R-значению, требует распределения (хотя, быть честным, все компиляторы С++ 11, которые Я знаю поддержку Оптимизация возвращаемого значения или копирование elision), хотя перемещаются кишки объекта.
Причина std::unique_ptr<>
работает так, потому что std::unique_ptr<>
не имеет конструктора копирования, он имеет только конструктор перемещения.