Эффективность push_back () C ++ 11 с std :: move по сравнению с emplace_back () для уже построенных объектов

86

В C ++ 11, emplace_back()как правило, предпочтительнее (с точки зрения эффективности), push_back()поскольку он допускает создание на месте, но так ли это при использовании push_back(std::move())с уже созданным объектом?

Например, что по- emplace_back()прежнему предпочтительнее в следующих случаях?

std::string mystring("hello world");
std::vector<std::string> myvector;

myvector.emplace_back(mystring);
myvector.push_back(std::move(mystring));
// (of course assuming we don't care about using the value of mystring after)

Кроме того, есть ли какая-то польза в приведенном выше примере вместо того, чтобы делать:

myvector.emplace_back(std::move(mystring));

Или этот шаг здесь полностью избыточен или не имеет никакого эффекта?

Бунт
источник
myvector.emplace_back(mystring);копирует и не перемещается. Два других хода должны быть эквивалентными.
TC
См. Также этот вопрос и результаты: Запрошенный опрос для VC ++ относительно вставки и размещения
ildjarn

Ответы:

116

Давайте посмотрим, что делают разные вызовы, которые вы предоставили:

  1. emplace_back(mystring): Это создание нового элемента на месте с любым аргументом, который вы указали. Поскольку вы указали lvalue, эта конструкция на месте фактически является копией, т.е. это то же самое, что и вызовpush_back(mystring)

  2. push_back(std::move(mystring)): Это вызывает вставку перемещения, которая в случае std :: string является конструкцией перемещения на месте.

  3. emplace_back(std::move(mystring)): Это снова внутренняя конструкция с указанными вами аргументами. Поскольку этот аргумент является rvalue, он вызывает конструктор перемещения std::string, т.е. это конструкция перемещения на месте, как в 2.

Другими словами, если вызывается с одним аргументом типа T, будь то RValue или именующее выражение, emplace_backи push_backэквивалентны.

Однако для любого другого аргумента (ов) emplace_backвыигрывает гонка, например, с a char const*в vector<string>:

  1. emplace_back("foo")требует строительства string::string(char const*)на месте.

  2. push_back("foo")сначала нужно вызвать string::string(char const*)неявное преобразование, необходимое для соответствия сигнатуре функции, а затем вставить перемещение, как в случае 2. выше. Следовательно, это эквивалентноpush_back(string("foo"))

Арне Мертц
источник
1
Конструктор перемещения обычно более эффективен, чем конструкторы копирования, поэтому использование rvalue (случай 2, 3) более эффективно, чем использование lvalue (случай 1), несмотря на ту же семантику.
Райан Ли
как насчет такой ситуации? void foo (строка && s) {vector.emplace (s); // 1 vector.emplace (std :: move (s)); // 2}
VALOD9
@VALOD это снова номера 1 и 3 в моем списке. sможет быть определена как RValue ссылка , что связывается только с rvalues, но внутри foo, sэто именующий.
Arne Mertz
1

Emplace_back получает список ссылок rvalue и пытается построить элемент контейнера прямо на месте. Вы можете вызвать emplace_back со всеми типами, которые поддерживаются конструкторами элементов контейнера. Когда вызывается emplace_back для параметров, которые не являются ссылками на rvalue, он «возвращается» к обычным ссылкам и, по крайней мере, вызывается конструктор копирования, когда параметр и элементы контейнера имеют один и тот же тип. В вашем случае myvector.emplace_back (mystring) должен сделать копию строки, потому что компилятор не мог знать, что параметр myvector подвижен. Итак, вставьте std :: move, что дает желаемое преимущество. Push_back должен работать так же, как emplace_back для уже построенных элементов.

Tunichtgut
источник
2
Это ... интересный способ описания переадресации / универсальных ссылок.
TC