push_back vs emplace_back


761

Я трохи розгублений щодо різниці між push_backі emplace_back.

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

Оскільки є push_backперевантаження з урахуванням рецензії, я не зовсім розумію, якою emplace_backстає мета ?



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&)), але я не можу придумати, чому б хтось цього хотів.
Бен

Відповіді:


568

Окрім того, що сказав відвідувач:

Функція, яку void emplace_back(Type&& _Val)надає MSCV10, є невідповідною та зайвою, оскільки, як ви зазначили, вона суворо еквівалентна push_back(Type&& _Val).

Але справжня C ++ 0x форма emplace_backдійсно корисна void emplace_back(Args&&...):;

Замість того, щоб приймати це, 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 зараз є якимось заповнювачем?

Відповідь: Як ви знаєте, варіативні шаблони не реалізовані у VC10. Ми імітуємо їх за допомогою препроцесорної техніки для речей, таких як make_shared<T>(), кортеж та нові речі <functional>. Це препроцесорне обладнання порівняно важко у використанні та обслуговуванні. Також це суттєво впливає на швидкість компіляції, оскільки нам доводиться неодноразово включати підзаголовки. Через поєднання наших обмежень у часі та швидкості компіляції ми не моделювали різні шаблони в наших функціях.

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

Це зрозуміле рішення. Кожен, хто лише раз намагався наслідувати різноманітний шаблон за допомогою жахливих хитрощів препроцесора, знає, наскільки огидними є цей матеріал.


101
Це уточнення, що це питання MSVS10, а не проблема C ++, є найважливішою частиною тут. Дякую.
me22

11
Я вважаю, що ваш останній рядок коду C ++ не буде працювати. pair<const int,Complicated>не має конструктора, який приймає int, інший int, подвійний і як четвертий параметр рядок. Однак ви можете безпосередньо сконструювати цей парний об'єкт, використовуючи його кусково-конструктор. Синтаксис, звичайно, буде різним:m.emplace(std::piecewise,std::forward_as_tuple(4),std::forward_as_tuple(anInt,aDouble,aString));
sellibitze

3
На щастя варіативні шаблони будуть у VS2013, зараз у попередньому перегляді.
Daniel Earwicker

11
чи слід оновлювати цю відповідь, щоб відображати нові події в порівнянні з 2013 роком?
бекко

6
Якщо ви використовуєте Visual Studio 2013 або на пізнішій версії в даний час , ви повинні мати підтримку «реальний» emplace_backтак довго , як це було реалізовано в коли були додані VARIADIC шаблони Visual C ++: msdn.microsoft.com/en-us/library/hh567368. aspx
kayleeFrye_onDeck

200

emplace_backне повинен приймати аргумент типу vector::value_type, а натомість варіативні аргументи, які передаються конструктору доданого елемента.

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

Можна передати, 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
@David: але тоді ви перенесли sсферу застосування, хіба це не небезпечно?
Матьє М.

2
Це не небезпечно, якщо ви більше не плануєте використовувати s для його значення. Переміщення не робить s недійсним, переміщення лише вкраде внутрішній розподіл пам’яті, що вже було зроблено в s, і залишить його у дефолтному стані (не виділяється жала), що при знищенні буде добре, як якщо б ви щойно набрали std :: string str;
Девід

4
@David: Я не впевнений, що переміщений з об'єкта необхідний для будь-якого використання, крім подальшого знищення.
Бен Войгт

46
vec.emplace_back("Hello")буде працювати, так як const char*аргумент буде направлений в stringконструктор. У цьому вся суть emplace_back.
Олександр К.

8
@BenVoigt: об'єкт, що перемістився, повинен бути у дійсному (але не визначеному) стані. Однак це не означає, що ви можете виконувати будь-яку операцію на ньому. Розглянемо std::vector. Порожній std::vector- дійсний стан, але ви не можете його зателефонувати front(). Це означає, що будь-яку функцію, яка не має передумов, все ще можна назвати (а деструктори ніколи не можуть мати передумов).
Девід Стоун

96

Оптимізацію для 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 &&)

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"});

1

Конкретний випадок для використання emplace_back: Якщо вам потрібно створити тимчасовий об'єкт, який потім буде натиснений у контейнер, використовуйте emplace_backзамість push_back. Це створить об'єкт на місці в контейнері.

Примітки:

  1. push_backу наведеному вище випадку створить тимчасовий об’єкт і перемістить його в контейнер. Однак побудована на місці конструкція emplace_backбула б більш ефективною, ніж побудова та переміщення об'єкта (що зазвичай передбачає певне копіювання).
  2. Взагалі, ви можете використовувати emplace_backзамість них push_backу всіх випадках без особливих проблем. (Див. Винятки )
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.