Есть ли случаи, когда неправильно заменять push back на emplace back?
могу ли я сломать действительную программу C++03, заменив std::vector::push_back
с emplace_back
и компиляция его с помощью компилятора C++ 11? От чтения emplace_back
ссылка я понимаю, что этого не должно произойти, но я признаю, что не полностью получаю ссылки rvalue.
6 ответов
Я построил короткий пример, который фактически не компилируется, когда push_back
заменить на emplace_back
:
#include <vector>
struct S {
S(double) {}
private:
explicit S(int) {}
};
int main() {
std::vector<S>().push_back(0); // OK
std::vector<S>().emplace_back(0); // error!
}
вызов push_back
необходимо преобразовать его аргумент 0
тип int
типа S
. Поскольку это неявное преобразование, явный конструктор S::S(int)
не считается, а S::S(double)
называется. С другой стороны,--2--> выполняет прямую инициализацию, поэтому оба S::S(double)
и есть. Последнее лучше подходит, но это private
, поэтому программа плохо сформирована.
Да, вы можете изменить поведение (больше, чем просто избежать вызова конструктора копирования), так как emplace_back
видит только несовершенно переданные аргументы.
#include <iostream>
#include <vector>
using namespace std;
struct Arg { Arg( int ) {} };
struct S
{
S( Arg ) { cout << "S(int)" << endl; }
S( void* ) { cout << "S(void*)" << endl; }
};
auto main()
-> int
{
vector<S>().ADD( 0 );
}
пример построения:
[H:\dev\test11] > g++ foo.cpp -D ADD=emplace_back && a S(int) [H:\dev\test11] > g++ foo.cpp -D ADD=push_back && a S(void*) [H:\dev\test11] > _
дополнительное соглашение: как указал Брайан Би в ответ, еще одно отличие, которое может привести к другому поведению, - это push_back
вызов включает неявное преобразование в T
, который пренебрегает explicit
конструкторы и преобразования операторы, в то время как emplace_back
использует прямую инициализацию, которая также учитывает explicit
конструкторы и операторы преобразования.
на emplace
версии не создают объект нужного типа на всех при исключительных обстоятельствах. Это может привести к ошибке.
рассмотрим следующий пример, в котором используется std::vector
для простоты (предположим uptr
ведет себя как std::unique_ptr
, кроме конструктора не является явным):
std::vector<uptr<T>> vec;
vec.push_back(new T());
это исключение-безопасный. Временное uptr<T>
создается для перехода в push_back
, который перемещается в вектор. При неудачном перераспределении вектора, выделенные T
по-прежнему принадлежит смарт-указателю, который правильно удаляет его.
сравнить с:
std::vector<uptr<T>> vec;
vec.emplace_back(new T());
emplace_back
не разрешается создавать временный объект. The ptr
будет создан один раз, на месте в векторе. Если перераспределение не удается, нет места для создания на месте, и интеллектуальный указатель никогда не будет создан. The T
будет утечка.
конечно, лучшая альтернатива:
std::vector<std::unique_ptr<T>> vec;
vec.push_back(make_unique<T>());
что эквивалентно первому, но делает создание интеллектуального указателя явным.
Если у вас нет сумасшедших побочных эффектов в конструктор копирования объектов, которые вы держите в своем векторе, то нет.
emplace_back
был введен для оптимизации ненужного копирования и перемещения.
предположим, что пользовательский класс может быть инициализирован из braced-initializer. например,
struct S {
int value;
};
затем
std::vector<S> v;
v.push_back({0}); // fine
v.emplace_back({0}); // template type deduction fails
std::vector::emplace_back
является функцией шаблона, но std::vector::push_back
не является функцией шаблона. С помощью braced-инициализатора std::vector::emplace_back
не так вывод аргумента шаблона не удается.
не выводил контекстах
6) параметр P, чей A является braced-init-list, но P не является
std::initializer_list
или ссылка на один:
int main() {
std::vector<S>().push_back(0);
std::vector<S>().emplace_back(0);
}
подарить конструктор struct в emplace_back я.е приведенный выше код будет такой
int main() {
std::vector<S>().push_back(0);
std::vector<S>().emplace_back(S(0));
}