На ревью утверждают, что замена push_back на emplace_back всегда устраняет копирование элемента. Верно ли это?
Нет. emplace_back конструирует новый элемент непосредственно в памяти контейнера, но не устраняет копирование или перемещение во всех случаях. Если передать уже созданный объект, потребуется его копирование или перемещение, а при перераспределении памяти std::vector могут перемещаться или копироваться и ранее добавленные элементы.
emplace_back появился вместе с механизмами перемещения и perfect forwarding в C++11. Его задача — дать контейнеру возможность вызвать конструктор элемента прямо в выделенной памяти, не создавая отдельный временный объект для последующего добавления.
Ранее типичный вызов push_back принимал готовый объект. Для добавления элемента, создаваемого из нескольких аргументов, это могло приводить к созданию временного объекта и его последующему перемещению или копированию.
Название emplace_back часто ошибочно трактуют как гарантию отсутствия копирований. Это приводит к неверным ожиданиям по производительности и к механической замене всех вызовов push_back, даже когда она не дает преимуществ.
Нужно различать два случая: создание элемента из аргументов конструктора и добавление уже существующего объекта. Кроме того, стоимость операции определяется не только способом добавления, но и текущей емкостью контейнера, конструкторами типа и их свойствами исключений.
При вызове emplace_back аргументы передаются конструктору элемента, который создается сразу в конце контейнера. При вызове push_back обычно передается уже готовый объект, поэтому контейнер помещает в себя его копию или перемещенную версию.
В первом случае emplace_back действительно позволяет избежать отдельного временного объекта. Во втором контейнер получает существующий объект item, поэтому должен скопировать или переместить его независимо от названия метода.
При расширении std::vector может потребоваться выделить новый буфер и перенести туда все элементы. Если перемещающий конструктор элемента не является noexcept или недоступен, реализация может предпочесть копирование ради сохранения гарантий исключений. Поэтому emplace_back не гарантирует отсутствие операций над уже существующими элементами.
push_back часто предпочтительнее, когда объект уже существует: намерение добавить готовый объект выражено яснее. emplace_back полезен, когда элемент нужно создать из его конструктора и подходящий набор аргументов не создает неоднозначностей или нежелательных преобразований.
В сервисе элементы дорогостоящего типа добавлялись в вектор. Разработчик заменил все вызовы push_back на emplace_back и ожидал заметного ускорения. Измерения показали, что большая часть времени уходила на расширение вектора, а не на создание временных объектов.
Рассматривались два варианта. Механическая замена методов почти не меняла число перемещений при перераспределении и ухудшала читаемость кода для уже созданных объектов. Предварительный вызов reserve уменьшал число перераспределений, но требовал реалистичной оценки количества элементов и увеличивал заранее выделенный объем памяти.
Выбрали emplace_back для построения элементов из аргументов, push_back для уже существующих объектов и предварительный reserve там, где размер входных данных был известен. Это устранило лишние перераспределения, сохранило ясность кода и не создало ложной гарантии отсутствия копирований.
Ответ: Будет выбран конструктор копирования или перемещения, принимающий этот объект. Непосредственная конструкция нового элемента из аргументов не означает, что существующий объект можно поместить в контейнер без операции над ним.
Ответ: При нехватке емкости вектор выделяет новый буфер и переносит туда уже существующие элементы. Новые элементы могут конструироваться на месте, но это не отменяет перемещения или копирования старых элементов. Число таких операций можно уменьшить с помощью reserve, если требуемый размер известен заранее.
Ответ: push_back явно показывает добавление готового объекта и может быть понятнее при работе с уже созданным значением. Кроме того, чрезмерное использование emplace_back иногда приводит к неожиданному выбору конструктора или неявному преобразованию аргументов. Выбор должен определяться смыслом операции, а не предположением, что emplace_back всегда быстрее.