c++ move - push_back vs emplace_back




operator constructor (6)

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

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

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

Вместо того, чтобы value_type значение 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 C ++ . Вот ответ от Stephan T Lavavej, официального сопровождающего реализации стандартной библиотеки Visual C ++ в Microsoft.

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

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

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

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

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


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


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


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

// создает элементы на месте.
emplace_back ( "элемент");

// Создает новый объект, а затем копирует (или перемещает) его значение аргументов. push_back (explicitDataType { "элемент"});


Я уставился на этот вопрос раньше, прочитав ссылку Говарда Хиннанта, не мог полностью понять его после часа размышлений. Теперь я смотрел и получил ответ через пять минут. (Edit: получил ответ слишком щедрым, поскольку ссылка Хиннанта имела ответ. Я имел в виду, что я понял и смог объяснить это более простым способом, который, надеюсь, кто-то найдет полезным).

В принципе, это позволяет вам быть общим в определенных ситуациях, в зависимости от введенного кода. Рассмотрим этот код:

#include <utility>
#include <vector>
#include <iostream>
using namespace std;

class GoodBye
{
  double b;
 public:
  GoodBye( double&& a):b(std::move(a)){ std::cerr << "move"; }
  GoodBye( const double& a):b(a){ std::cerr << "copy"; }
};

struct Hello {
  double m_x;

  double & get()  { return m_x; }
};

int main()
{
  Hello h;
  GoodBye a(std::forward<double>(std::move(h).get()));
  return 0;
}

Этот код печатает «move». Интересно, что если я удалю std::forward , он печатает копию. Это, для меня, трудно обойти вокруг меня, но давайте принять его и двигаться дальше. (Edit: Я предполагаю, что это происходит потому, что get вернет ссылку lvalue на rvalue. Такая сущность распадается на lvalue, но std :: forward будет использовать ее в rvalue, как и при обычном использовании переадресации. хоть).

Теперь давайте представим себе другой класс:

struct Hello2 {
  double m_x;

  double & get() & { return m_x; }
  double && get() && { return std::move(m_x); }
};

Предположим, что в main коде h был экземпляр Hello2. Теперь нам больше не нужен std :: forward, потому что вызов std::move(h).get() возвращает rvalue. Однако предположим, что код является общим:

template <class T>
void func(T && h) {
  GoodBye a(std::forward<double>(std::forward<T>(h).get()));
}

Теперь, когда мы вызываем func , мы хотели бы, чтобы он работал правильно с Hello и Hello2 , то есть мы хотели бы вызвать переход. Это происходит только для rvalue Hello если мы включаем внешний std::forward , поэтому нам это нужно. Но ... Мы добрались до пергамента. Когда мы передаем rvalue Hello2 этой функции, перегрузка rvalue get () уже вернет значение rvalue double, поэтому std::forward фактически принимает значение rvalue. Поэтому, если это не так, вы не сможете писать полностью общий код, как указано выше.

Черт.





c++ visual-studio-2010 stl c++11 move-semantics