Вставьте элементы в std :: map без дополнительного копирования

Рассмотрим эту программу:

#include <map>
#include <string>
#define log magic_log_function // Please don't mind this.

//
// ADVENTURES OF PROGO THE C++ PROGRAM
//

class element;
typedef std::map<int, element> map_t;

class element {
public:
element(const std::string&);
element(const element&);
~element();
std::string name;
};
element::element(const std::string& arg)
: name(arg)
{
log("element ", arg, " constucted, ", this);
}
element::element(const element& other)
: name(other.name)
{
name += "-copy";
log("element ", name, " copied, ", this);
}
element::~element()
{
log("element ", name, " destructed, ", this);
}
int main(int argc, char **argv)
{
map_t map1; element b1("b1");
log(" > Done construction.");
log(" > Making map 1.");
map1.insert(std::pair<int, element>(1, b1));
log(" > Done making map 1.");
log(" > Before returning from main()");
}

Он создает несколько объектов в стеке и insertс ними в std::map контейнер, создание два дополнительные временные копии в процессе:

element b1 constucted, 0x7fff228c6c60
> Done construction.
> Making map 1.
element b1-copy copied, 0x7fff228c6ca8
element b1-copy-copy copied, 0x7fff228c6c98
element b1-copy-copy-copy copied, 0x232d0c8
element b1-copy-copy destructed, 0x7fff228c6c98
element b1-copy destructed, 0x7fff228c6ca8
> Done making map 1.
> Before returning from main()
element b1 destructed, 0x7fff228c6c60
element b1-copy-copy-copy destructed, 0x232d0c8

Мы можем избавиться от одного дополнительного вызова конструктора копирования, изменив std::pair подпись std::pair<int, element&>Однако второй временный объект все еще создается и сразу уничтожается:

element b1 constucted, 0x7fff0fe75390
> Done construction.
> Making map 1.
element b1-copy copied, 0x7fff0fe753c8
element b1-copy-copy copied, 0x1bc4098
element b1-copy destructed, 0x7fff0fe753c8
> Done making map 1.
> Before returning from main()
element b1 destructed, 0x7fff0fe75390
element b1-copy-copy destructed, 0x1bc4098

Есть ли способ сделать std::map просто взять объект в стеке по ссылке и сделать его единственную внутреннюю копию?

6

Решение

Стандартная практика (с более старыми версиями C ++), где я был, состоит в использовании карты общих указателей.

Все еще создает копию общих указателей, но это обычно гораздо менее обременительно, чем копирование больших объектов.

4

Другие решения

Это один из многих вариантов использования, которые мотивировали C++11«s move функциональность, поддерживаемая множеством новых функций, в частности, ссылки на значения, а также множество новых стандартных интерфейсов библиотеки, в том числе std::map::emplace, std::vector::emplace_back, так далее.

Если по какой-либо причине вы еще не можете использовать C++11вы можете, по крайней мере, утешить себя мыслью, что проблема была распознана, что решение стандартизировано и реализовано, и что, кроме того, многие из нас используют его, некоторые из нас [1] в рабочем коде. Итак, как гласит старая шутка, решение существует и это ваш звонок относительно того, когда вы его поднимаете.

Обратите внимание, что вам не нужно использовать emplace функция-член, если ваши объекты реализуют конструкторы перемещения, что они могут даже сделать по умолчанию. Этого не произойдет, если у вас есть явные конструкторы копирования, поэтому ваш приведенный выше тест может привести к эффектам наблюдателя (и действительно, он может также подавить оптимизацию компилятора в случае POD, поэтому даже с C ++ 03 у вас может не быть проблемы, которую вы думаю, что вы делаете).

Есть множество доступных хаков, которые вроде бы избегают копий только с «незначительными» изменениями исходного кода, но ИМХО лучший подход — начать двигаться к C ++ 11. Что бы вы ни делали, попробуйте сделать это так, чтобы неизбежная миграция стала менее болезненной.


[Примечание 1]: Отказ от ответственности: я больше не пишу производственный код, так как более или менее удален, поэтому я не являюсь частью «некоторых из нас» в этом предложении.

8

Ты можешь использовать устанавливать () :

Элемент создается на месте, то есть операции копирования или перемещения не выполняются. Конструктор типа элемента (value_type, то есть std :: pair) вызывается с точно такими же аргументами, что и для функции

3

Ну, если у вас нет emplaceВы можете создать элемент в куче и передать указатели на карту:

typedef std::map<int, element*> map_t;
...
printf(" > Making pair 1.\n");
std::pair<int, element*> pair(1, new element ("b1")) ;
printf(" > Making map 1.\n");
map1.insert(pair);

но тогда у вас могут возникнуть утечки памяти, если вы не позаботитесь, когда ваша карта выйдет из области видимости …

-1