push_back против emplace_back

465

Я немного смущен относительно разницы между 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?

  • 11
    Немного хорошего чтения здесь: open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2642.pdf
  • 15
    Обратите внимание, что (как говорит Томас ниже) код, о котором идет речь, взят из эмуляции MSVS C ++ 0x, а не того, чем на самом деле является C ++ 0x.
Показать ещё 5 комментариев
Теги:
c++11
visual-studio-2010
stl
move-semantics

4 ответа

342
Лучший ответ

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

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

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

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

Это полезно, потому что независимо от того, насколько много умений RVO и перемещение семантики приносят в таблицу, все еще есть сложные случаи, когда push_back скорее всего сделает ненужные копии (или переместится). Например, с традиционной insert() функцией std::map вам нужно создать временное, которое затем будет скопировано в 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 С++. Вот ответ от Stephan T Lavavej, официального сопровождающего реализации стандартной библиотеки Visual С++ в Microsoft.

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

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

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

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

  • 81
    Это разъяснение, что это проблема MSVS10, а не проблема C ++, является самой важной частью здесь. Благодарю.
  • 10
    Я считаю, что ваша последняя строка кода 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));
Показать ещё 5 комментариев
148

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
  • 0
    Вам нужно будет использовать vec.emplace_back (std :: move (s)); так как s является lvalue, чтобы получить поведение движения
  • 2
    @David: но тогда вы перемещенная s в области видимости, это не опасно?
Показать ещё 7 комментариев
19

Оптимизация для 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 &&)
  • 6
    +1 для примера кода, который демонстрирует, что на самом деле происходит при вызове emplace_back против push_back .
7

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 эффективно с отображенным типом, который не является ни копируемым, ни подвижным.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню