push_back vs emplace_back: в чем разница и что выбрать?
Основное отличие: где создается объект
Главное различие между push_back и emplace_back кроется в месте создания объекта.
- push_back — принимает уже готовый объект (по значению, ссылке или rvalue-ссылке) и копирует/перемещает его в контейнер.
- emplace_back — принимает аргументы для конструктора и создает объект непосредственно в памяти вектора, избегая временных объектов.
Детальный разбор производительности
Рассмотрим ваш пример с классом MyClass:
// Вариант с push_back (создание временного объекта)
vec.push_back(MyClass(10, 20));
// 1. Создается временный MyClass(10,20) в стеке
// 2. Вызывается конструктор копирования или перемещения для копирования в вектор
// 3. Временный объект уничтожается
// Вариант с emplace_back (конструирование на месте)
vec.emplace_back(10, 20);
// 1. Вектор выделяет память
// 2. Прямо в этой памяти вызывается конструктор MyClass(10,20)
// 3. Никаких временных объектов
emplace_back выигрывает, когда:
- Объект имеет тяжелый конструктор копирования/перемещения (например, содержит большие массивы или строки).
- Вы передаете несколько аргументов для конструктора.
- Работаете с некопируемыми типами (например,
std::unique_ptr).
Когда push_back все еще хорош
push_back не устарел и в некоторых случаях даже удобнее:
- Если у вас уже есть объект (например, переменная), который нужно добавить:
MyClass obj(10, 20);
vec.push_back(obj); // логично: берем существующий объект
vec.emplace_back(obj); // работает, но сбивает с толку
- При работе с простыми типами (int, double) разница в производительности нулевая.
- Для кода с move-семантикой:
vec.push_back(std::move(obj)); // явно показываем намерение переместить
vec.emplace_back(std::move(obj)); // менее очевидно
Move-семантика и emplace_back
Важное уточнение: emplace_back не использует move-семантику для переданных аргументов. Он передает их в конструктор как есть (по значению или по ссылке). Если вы хотите переместить объект, используйте push_back с std::move.
Практический совет
Общее правило:
- Используйте
emplace_back, когда создаете объект прямо в вызове с новыми аргументами. - Используйте
push_back, когда у вас уже есть объект (переменная) или когда хотите явно показать перемещение.
В вашем примере vec.emplace_back(10, 20) — идеальный случай для emplace_back. Но не зацикливайтесь на микрооптимизациях: разница заметна только на тысячах объектов или тяжелых конструкторах.
Дополнение: безопасность исключений и читаемость
Есть еще один аспект, который часто упускают — безопасность исключений. emplace_back может быть менее безопасным в некоторых сценариях:
std::vector> vec;
vec.emplace_back(new int(42)); // Потенциальная утечка памяти при исключении
Здесь лучше использовать push_back с std::make_unique:
vec.push_back(std::make_unique(42)); // Безопасно и читаемо
Также помните о читаемости кода. В команде разработчиков push_back часто понятнее новичкам. Не жертвуйте ясностью кода ради незначительного прироста производительности. Для подавляющего большинства случаев разница между push_back и emplace_back не критична — выбирайте то, что делает код понятнее.