С++ 11 emplace_back и синтаксис push_back со структурой

Я использую MSVC, Visual Studio 2013.

Предположим, что у меня есть структура:

struct my_pair {
    int foo, bar;
};

И я хочу добавить кучу из них эффективно, не создавая временного, а затем отбрасывая его:

vector<my_pair> v;
v.push_back(41, 42); // does not work              [a]
v.push_back({41,42}); // works                     [b]
v.emplace_back(41,42); // does not work            [c]
v.emplace_back({41,42}); // does not work          [d]
v.emplace_back(my_pair{41,42}); //works            [e]

Теперь, если я добавлю конструктор и скопирую конструктор в свой код:

my_pair(int foo_, int bar_) : foo(foo_), bar(bar_) 
{
    cout << "in cstor" << endl;
}
my_pair(const my_pair& copy) : foo(copy.foo), bar(copy.bar)
{
    cout << "in copy cstor" << endl;
}

Затем изменяется поведение:

v.push_back(41, 42); // does not work                              [f]
v.push_back({41,42}); // displays "in cstor" and "in copy cstor"   [g]
v.emplace_back(41,42); // displays "in cstor"                      [h]
v.emplace_back({41,42}); // does not work                          [i]
v.emplace_back(my_pair{41,42}); // "in cstor" and "in copy cstor"  [j]

Если добавить конструктор перемещения:

my_pair(my_pair&& move_) : foo(move_.foo), bar(move_.bar)
{
    cout << "in move cstor" << endl;
}

Тогда:

v.emplace_back(my_pair{41,42}); //displays "in cstor", "in move cstor"   [k]
v.emplace_back({41,42}); // still does not work                          [l]
v.push_back({41,42}); // displays "in cstor", "in move cstor"            [m]

Вопросы:
для [a, b] Я понимаю причину работы и не работает.
для [c] он не работает, потому что нет конструктора для пересылки аргументов.
для [d], почему это не работает в случае нажатия?
для [e], почему он работает при добавлении имени класса?
для [h], кажется, что это самый эффективный код, если есть конструктор, который отображает аргументы членам для [j], похоже, что это так плохо, как push_back, и с дополнительным набором текста я не уверен, почему кто-то должен это делать через push_back
для [k, m], с добавлением конструктора перемещения кажется, что push_back(T&&) вызывается, что приводит к той же производительности, что и emplace. Но опять же, с дополнительной типизацией, я не уверен, почему кто-то это сделает.

Я прочитал, что MSVC не добавляет для вас конструктор перемещения: Почему конструктор копирования вызван в вызов std::vector:: emplace_back()?

В чем разница между [d, e] и почему это связано с этим. И почему push_back(T&&) работает без добавления имени структуры?

Я могу получить только полные преимущества emplace, если я знаю, что есть конструктор, который принимает каждый член в качестве аргумента?

Должен ли я просто придерживаться push_back? Есть ли причина использовать emplace_back(structname{1,2,3}) вместо push_back({1,2,3}), потому что в итоге все равно вызовет push_back(T&&), и его легче набрать?

В-третьих, как emplace_back(arg1,arg2,etc), сделать свою магию, чтобы полностью избежать копирования или перемещения конструктора?

Ответы

Ответ 1

Для v.emplace_back({41,42}); см. как использовать std::vector:: emplace_back для vector < vector <int → ?


v.emplace_back(41,42); не работает из-за некоторых правил в стандарте (некоторые мои замечания):

Таблица 101 - Дополнительные операции контейнера последовательности

Выражение: a.emplace_back(args)

Тип возврата: void

Операционная семантика:
Добавляет объект типа T, построенный с помощью std::forward<Args>(args)....

Требуется: T EmplaceConstructible в X от args. Для vector T также должен быть MoveInsertable в X.

Для типа EmplaceConstructible,

§ 23.2.1.13

- T EmplaceConstructible в X из args, для ноль или более аргументов args означает, что следующее выражение хорошо сформировано:

allocator_traits<A>::construct(m, p, args);

std::allocator_traits::construct() в свою очередь делает (если возможно) a.construct(p, std::forward<Args>(args)...) (где a есть m в выражении EmplaceConstructible).

a.construct() здесь std::allocator::construct(), который вызывает ::new((void *)p) U(std::forward<Args>(args)...). Это то, что вызывает ошибку компиляции.

U(std::forward<Args>(args)...) (обратите внимание на использование прямой инициализации) найдет конструктор U, который примет перенаправленные аргументы. Однако в вашем случае my_pair является агрегированным типом, который может быть инициализирован только с помощью синтаксиса с принудительной инициализацией (агрегатная инициализация).


v.emplace_back(my_pair{41,42}); работает, потому что он вызывает либо неявно сгенерированный конструктор копии по умолчанию, либо перемещает конструктор (обратите внимание, что эти два не всегда могут быть сгенерированы). Сначала создается временная my_pair, которая проходит тот же процесс, что и процесс v.emplace_back(41,42);, только аргумент представляет собой r-значение my_pair.


ДОПОЛНИТЕЛЬНЫЙ 1:

И почему push_back (T & &) работает без добавления имени структуры?

Это из-за push_back подписей. push_back() не выводится, что означает, что при выполнении push_back({1, 2}) сначала создается и инициализируется временный объект с типом типа векторного элемента с помощью {1, 2}. Тогда этот временный объект будет тем, который передается в push_back(T&&).


Должен ли я просто придерживаться push_back? Есть ли какая-нибудь причина использовать emplace_back (structname {1,2,3}) вместо push_back ({1,2,3}), потому что в любом случае вызовет push_back (T & &), и его легче набрать?

В принципе, функции emplace* предназначены для оптимизации и устранения затрат на создание временных рядов и копирования или перемещения объектов построения при их вставке. Однако для случая совокупных типов данных, когда выполнение чего-то типа emplace_back(1, 2, 3) невозможно, и единственный способ, которым вы могли их вставить, - создать временное копирование или перемещение, тогда, во всяком случае, предпочитайте более компактный синтаксис и идите для push_back({1,2,3}), где он будет в основном иметь такую ​​же производительность, как у emplace_back(structname{1,2,3}).