Есть ли случаи, когда неправильно заменять 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 или ссылка на один:

LIVE


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