push_back против emplace_back

763

Я немного сбит с толку относительно разницы между push_backи emplace_back.

void emplace_back(Type&& _Val);
void push_back(const Type& _Val);
void push_back(Type&& _Val);

Поскольку существует push_backперегрузка, принимающая ссылку на rvalue, я не совсем понимаю, для чего это emplace_backделается?

ronag
источник
11
Немного хорошего чтения здесь: open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2642.pdf
Йохан Котлински
16
Обратите внимание, что (как говорит Томас ниже) код в вопросе взят из эмуляции MSVS для C ++ 0x, а не для того, чем на самом деле является C ++ 0x.
me22
5
Лучшая статья для чтения: open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2345.pdf . N2642 в основном формулировка для стандарта; N2345 - это статья, которая объясняет и мотивирует идею.
Алан
Обратите внимание, что даже в MSVC10 есть template <class _Valty> void emplace_back(_Valty&& _Val)версия, которая использует универсальную ссылку, которая обеспечивает идеальную пересылку конструкторам с explicitодним аргументом.
Joki
Связанный: есть ли случай, когда push_backпредпочтительнее emplace_back? Единственный случай, о котором я могу подумать, это если бы класс был каким-то образом копируемым ( T&operator=(constT&)), но не конструируемым ( T(constT&)), но я не могу понять, почему кто-то когда-либо этого захотел.
Бен

Ответы:

570

В дополнение к тому, что посетитель сказал:

Функция, void emplace_back(Type&& _Val)предоставляемая MSCV10, не соответствует и избыточна, потому что, как вы заметили, она строго эквивалентна push_back(Type&& _Val).

Но настоящая форма C ++ 0x emplace_backдействительно полезна void emplace_back(Args&&...):;

Вместо того, чтобы брать a, value_typeон принимает список аргументов с переменным числом аргументов, что означает, что теперь вы можете безошибочно пересылать аргументы и непосредственно создавать объект в контейнере без какого-либо временного объекта.

Это полезно, потому что независимо от того, насколько умные RVO и семантика перемещения приносят в таблицу, все еще существуют сложные случаи, когда push_back может создавать ненужные копии (или перемещаться). Например, с помощью традиционной insert()функции a std::mapвы должны создать временный объект, который затем будет скопирован в a std::pair<Key, Value>, который затем будет скопирован на карту:

std::map<int, Complicated> m;
int anInt = 4;
double aDouble = 5.0;
std::string aString = "C++";

// cross your finger so that the optimizer is really good
m.insert(std::make_pair(4, Complicated(anInt, aDouble, aString))); 

// should be easier for the optimizer
m.emplace(4, anInt, aDouble, aString);

Так почему же они не реализовали правильную версию emplace_back в MSVC? На самом деле, это слишком долго меня беспокоило, поэтому я задал тот же вопрос в блоге по Visual C ++ . Вот ответ Стефана Т Лававея, официального сопровождающего реализации стандартной библиотеки Visual C ++ в Microsoft.

Вопрос: Являются ли бета 2 emplace функции просто своего рода заполнителем прямо сейчас?

A: Как вы, возможно, знаете, шаблоны Variad не реализованы в VC10. Мы моделируем их с помощью препроцессорного оборудования для таких вещей, как make_shared<T>()кортеж и новые вещи <functional>. Это препроцессорное оборудование относительно сложно в использовании и обслуживании. Кроме того, это существенно влияет на скорость компиляции, так как нам приходится многократно включать подзаголовки. Из-за сочетания наших временных ограничений и проблем со скоростью компиляции мы не смоделировали шаблоны переменных в наших функциях emplace.

Когда в компиляторе реализованы различные шаблоны, можно ожидать, что мы воспользуемся ими в библиотеках, в том числе в наших функциях emplace. Мы очень серьезно относимся к соответствию, но, к сожалению, мы не можем сделать все сразу.

Это понятное решение. Каждый, кто хотя бы один раз пытался эмулировать шаблон вариации с ужасными приемами препроцессора, знает, как отвратительно получается это.

Томас Пети
источник
101
Это разъяснение, что это проблема MSVS10, а не проблема C ++, является наиболее важной частью здесь. Спасибо.
me22
11
Я считаю, что ваша последняя строка кода C ++ не будет работать. pair<const int,Complicated>не имеет конструктора, который принимает int, другой int, double и в качестве 4-го параметра строку. Тем не менее, вы можете напрямую создать эту пару объектов, используя его кусочно-конструктор. Синтаксис будет разным, конечно:m.emplace(std::piecewise,std::forward_as_tuple(4),std::forward_as_tuple(anInt,aDouble,aString));
Sellibitze
3
К счастью, вариационные шаблоны будут в VS2013, теперь в предварительном просмотре.
Даниэль Уорвикер
11
следует ли обновить этот ответ, чтобы отразить новые события в 2013 году?
becko
6
Если вы используете Visual Studio 2013 или более позднюю версию , у вас должна быть поддержка «реальной», emplace_backесли она была реализована в Visual C ++ при добавлении шаблонов с переменными параметрами
kayleeFrye_onDeck
200

emplace_backне должен принимать аргумент типа vector::value_type, а вместо этого использовать переменные аргументы, которые передаются в конструктор добавляемого элемента.

template <class... Args> void emplace_back(Args&&... args); 

Можно передать a, value_typeкоторый будет перенаправлен в конструктор копирования.

Поскольку он передает аргументы, это означает, что если у вас нет значения rvalue, это все равно означает, что контейнер будет хранить «скопированную» копию, а не перемещенную копию.

 std::vector<std::string> vec;
 vec.emplace_back(std::string("Hello")); // moves
 std::string s;
 vec.emplace_back(s); //copies

Но вышеупомянутое должно быть идентично тому, что push_backделает. Скорее всего, он предназначен для случаев использования, таких как:

 std::vector<std::pair<std::string, std::string> > vec;
 vec.emplace_back(std::string("Hello"), std::string("world")); 
 // should end up invoking this constructor:
 //template<class U, class V> pair(U&& x, V&& y);
 //without making any copies of the strings
посетитель
источник
2
@ Дэвид: но тогда у вас есть перемещение sв области, разве это не опасно?
Матье М.
2
Это не опасно, если вы не планируете больше использовать s для его значения. Перемещение не делает s недействительным, перемещение только украдет внутреннее выделение памяти, уже выполненное в s, и оставит его в состоянии по умолчанию (без выделенного sting), что при уничтожении будет хорошо, как если бы вы только что набрали std :: string str;
Дэвид
4
@ Дэвид: я не уверен, что перемещенный объект должен быть действительным для любого использования, кроме последующего уничтожения.
Бен Фойгт
46
vec.emplace_back("Hello")будет работать, так как const char*аргумент будет перенаправлен в stringконструктор. В этом весь смысл emplace_back.
Александр С.
8
@BenVoigt: перемещенный объект должен находиться в допустимом (но не указанном) состоянии. Однако это не обязательно означает, что вы можете выполнить на нем какую-либо операцию. Посмотрим std::vector. Пустое std::vectorявляется допустимым состоянием, но вы не можете вызвать front()его. Это означает, что любая функция, которая не имеет предварительных условий, все еще может быть вызвана (и деструкторы никогда не могут иметь предварительных условий).
Дэвид Стоун
97

Оптимизация для emplace_backможет быть продемонстрирована в следующем примере.

Ибо emplace_backконструктор A (int x_arg)будет называться. И для push_back A (int x_arg)называется первым, а move A (A &&rhs)потом называется.

Конечно, конструктор должен быть помечен как explicit, но для текущего примера хорошо бы удалить явность.

#include <iostream>
#include <vector>
class A
{
public:
  A (int x_arg) : x (x_arg) { std::cout << "A (x_arg)\n"; }
  A () { x = 0; std::cout << "A ()\n"; }
  A (const A &rhs) noexcept { x = rhs.x; std::cout << "A (A &)\n"; }
  A (A &&rhs) noexcept { x = rhs.x; std::cout << "A (A &&)\n"; }

private:
  int x;
};

int main ()
{
  {
    std::vector<A> a;
    std::cout << "call emplace_back:\n";
    a.emplace_back (0);
  }
  {
    std::vector<A> a;
    std::cout << "call push_back:\n";
    a.push_back (1);
  }
  return 0;
}

вывод:

call emplace_back:
A (x_arg)

call push_back:
A (x_arg)
A (A &&)
vadikrobot
источник
21
+1 за пример кода , который демонстрирует , что на самом деле происходит при вызове emplace_backпротив push_back.
Шон
Я пришел сюда после того, как заметил, что у меня есть код, который вызывал, v.emplace_back(x);где x явно сконструирован с возможностью перемещения, но только с явной копией. Тот факт, что emplace_back«неявно» явный, заставляет меня думать, что моя функция перехода для добавления, вероятно, должна быть push_back. Мысли?
Бен
Если вы позвоните во a.emplace_backвторой раз, будет вызван конструктор перемещения!
X Æ A-12
8

emplace_backсоответствующая реализация передаст аргументы vector<Object>::value_typeконструктору при добавлении в вектор. Напомню, что Visual Studio не поддерживала шаблоны с переменными значениями, но с помощью шаблонов с различными параметрами будет поддерживаться в Visual Studio 2013 RC, поэтому я предполагаю, что будет добавлена ​​соответствующая подпись.

С emplace_back, если вы перенаправляете аргументы непосредственно в vector<Object>::value_typeконструктор, вам не нужно, чтобы тип emplace_back, строго говоря, был подвижным или копируемым для функции. В данном vector<NonCopyableNonMovableObject>случае это бесполезно, поскольку vector<Object>::value_type для роста требуется копируемый или подвижный тип.

Но обратите внимание, что это может быть полезно std::map<Key, NonCopyableNonMovableObject>, поскольку после выделения записи на карте ее больше не нужно перемещать или копировать, в отличие от vector, что означает, что вы можете std::mapэффективно использовать сопоставленный тип, который нельзя ни копировать, ни копировать. подвижна.

Герман Диаго
источник
8

Еще один в случае списков:

// constructs the elements in place.                                                
emplace_back("element");


//It will create new object and then copy(or move) its value of arguments.
push_back(explicitDataType{"element"});
HackSlash
источник
1

Конкретный вариант использования для emplace_back: Если вам нужно создать временный объект, который затем будет помещен в контейнер, используйте emplace_backвместо push_back. Это создаст объект на месте внутри контейнера.

Ноты:

  1. push_backв приведенном выше случае создадим временный объект и переместим его в контейнер. Однако используемая для на месте конструкция emplace_backбудет более производительной, чем создание и перемещение объекта (что обычно требует некоторого копирования).
  2. В общем, вы можете использовать emplace_backвместо push_backвсех случаев без особых проблем. (См. Исключения )
Вайбхав Кумар
источник