Почему я бы использовал push_back вместо emplace_back?

С ++11 векторов имеют новую функцию emplace_back, В отличие от push_back, который опирается на оптимизацию компилятора, чтобы избежать копий, emplace_back использует совершенную пересылку для отправки аргументов непосредственно в конструктор для создания объекта на месте. Мне кажется, что emplace_back делает все push_back можно сделать, но иногда это будет лучше (но никогда не хуже).

По какой причине я должен использовать push_back?

6 ответов

Решение

За последние четыре года я много думал об этом. Я пришел к выводу, что большинство объяснений о push_back против emplace_back пропустить полную картину.

В прошлом году я выступил с докладом на C++. Теперь о типе дедукции в C++ 14. Я начинаю говорить о push_back против emplace_back в 13:49, но есть полезная информация, которая предоставляет некоторые подтверждающие доказательства до этого.

Реальное первичное различие связано с неявными и явными конструкторами. Рассмотрим случай, когда у нас есть один аргумент, который мы хотим передать push_back или же emplace_back,

std::vector<T> v;
v.push_back(x);
v.emplace_back(x);

После того, как ваш оптимизирующий компилятор справится с этим, между этими двумя утверждениями не будет разницы в терминах сгенерированного кода. Традиционная мудрость заключается в том, что push_back построит временный объект, который затем будет перемещен в v в то время как emplace_back передаст аргумент и создаст его непосредственно на месте без копий или перемещений. Это может быть правдой, основываясь на коде, написанном в стандартных библиотеках, но он ошибочно полагает, что работа оптимизирующего компилятора заключается в генерации написанного вами кода. Работа оптимизирующего компилятора на самом деле состоит в том, чтобы генерировать код, который вы написали бы, если бы вы были экспертом по оптимизации для конкретной платформы и не заботились о удобстве сопровождения, а просто о производительности.

Фактическая разница между этими двумя утверждениями заключается в том, что emplace_back будет вызывать любой тип конструктора, тогда как более осторожный push_back будет вызывать только неявные конструкторы. Неявные конструкторы должны быть безопасными. Если вы можете неявно построить U из TВы говорите, что U может хранить всю информацию в T без потерь. В любой ситуации безопасно пройти T и никто не будет возражать, если вы сделаете это U вместо. Хорошим примером неявного конструктора является преобразование из std::uint32_t в std::uint64_t, Плохой пример неявного преобразования double в std::uint8_t,

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

Пример

std::vector<std::unique_ptr<T>> v;
T a;
v.emplace_back(std::addressof(a)); // compiles
v.push_back(std::addressof(a)); // fails to compile

std::unique_ptr<T> имеет явный конструктор из T *, Так как emplace_back может вызывать явные конструкторы, передавая не владеющий указателем компиляцию просто отлично. Однако когда v выходит из области видимости, деструктор попытается вызвать delete на том указателе, который не был выделен new потому что это просто объект стека. Это приводит к неопределенному поведению.

Это не просто придуманный код. Это была настоящая производственная ошибка, с которой я столкнулся. Код был std::vector<T *>, но он владел содержимым. В рамках перехода на C++11 я правильно изменил T * в std::unique_ptr<T> чтобы указать, что вектор владел своей памятью. Тем не менее, я основывал эти изменения на своем понимании в 2012 году, когда я подумал: "emplace_back делает все, что может сделать push_back и даже больше, так зачем мне когда-либо использовать push_back?", Поэтому я также изменил push_back в emplace_back,

Если бы я вместо этого оставил код, как использование более безопасного push_backЯ бы сразу поймал эту давнюю ошибку, и это было бы расценено как успешное обновление до C++11. Вместо этого я замаскировал ошибку и не нашел ее до нескольких месяцев спустя.

push_back всегда позволяет использовать унифицированную инициализацию, что мне очень нравится. Например:

struct aggregate {
    int foo;
    int bar;
};

std::vector<aggregate> v;
v.push_back({ 42, 121 });

С другой стороны, v.emplace_back({ 42, 121 }); не будет работать.

Обратная совместимость с компиляторами до C++11.

Некоторые реализации библиотеки emplace_back не ведут себя так, как указано в стандарте C++, включая версию, поставляемую с Visual Studio 2012, 2013 и 2015.

Чтобы учесть известные ошибки компилятора, предпочитайте использоватьstd::vector::push_back() если параметры ссылаются на итераторы или другие объекты, которые будут недействительными после вызова.

std::vector<int> v;
v.emplace_back(123);
v.emplace_back(v[0]); // Produces incorrect results in some compilers

В одном компиляторе v содержит значения 123 и 21 вместо ожидаемых 123 и 123. Это связано с тем, что второй вызов emplace_back приводит к изменению размера, в какой момент v[0] становится недействительным.

Рабочая реализация приведенного выше кода будет использовать push_back() вместо emplace_back() следующее:

std::vector<int> v;
v.emplace_back(123);
v.push_back(v[0]);

Примечание: использование вектора целых для демонстрационных целей. Я обнаружил эту проблему с гораздо более сложным классом, который включал динамически распределяемые переменные-члены и вызов emplace_back() привело к тяжелой аварии.

Использоватьpush_backтолько для примитивных/встроенных типов или необработанных указателей. В противном случае используйтеemplace_back.

Рассмотрим, что происходит в Visual Studio 2019 с компилятором C++-17. У нас есть emplace_back в функции с правильно настроенными аргументами. Затем кто-то изменяет параметры конструктора, вызываемого emplace_back. В VS нет никаких предупреждений, код также компилируется нормально, а затем вылетает во время выполнения. После этого я удалил все emplace_back из кодовой базы.

Другие вопросы по тегам