За последние четыре года я много думал об этом. Я пришел к выводу, что большинство объяснений по отношению к 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
вызовут только неявные конструкторы. Неявные конструкторы должны быть безопасными. Если вы можете неявно построить a U
из a 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. Вместо этого я замаскировал ошибку и не нашел ее несколько месяцев спустя.
std::unique_ptr<T>
имеет явный конструктор изT *
. Посколькуemplace_back
могут вызывать явные конструкторы, передача не принадлежащего указателю компилируется просто отлично. Однако, когдаv
выходит из области видимости, деструктор пытается вызватьdelete
этот указатель, который не был выделен,new
потому что это просто объект стека. Это приводит к неопределенному поведению.explicit
конструктор - это конструктор, к которомуexplicit
применено ключевое слово . «Неявный» конструктор - это любой конструктор, у которого нет этого ключевого слова. В случаеstd::unique_ptr
с конструктором изT *
, разработчикstd::unique_ptr
написал этот конструктор, но проблема здесь в томemplace_back
, что вызвал пользователь этого типа , который вызвал этот явный конструктор. Если бы это былоpush_back
вместо вызова этого конструктора, он бы полагался на неявное преобразование, которое может вызывать только неявные конструкторы.push_back
всегда позволяет использовать унифицированную инициализацию, что мне очень нравится. Например:С другой стороны,
v.emplace_back({ 42, 121 });
работать не будет.источник
{}
синтаксис для вызова фактического конструктора, вы можете просто удалить{}
и использоватьemplace_back
.{}
синтаксис для вызова реальных конструкторов. Вы могли бы датьaggregate
конструктор, который принимает 2 целых числа, и этот конструктор будет вызываться при использовании{}
синтаксиса. Дело в том, что если вы пытаетесь вызвать конструктор,emplace_back
было бы предпочтительнее, так как он вызывает конструктор на месте. И поэтому не требует, чтобы тип был копируемым.emplace
агрегировать без явной записи конструктора шаблонов. , На этом этапе также неясно, будет ли это рассматриваться как дефект и, следовательно, будет ли он пригоден для бэкпорта, или пользователи C ++ <20 останутся SoL.Обратная совместимость с компиляторами до C ++ 11.
источник
push_back
предпочтительнее.emplace_back
это не «отличная» версияpush_back
. Это потенциально опасная версия. Прочитайте другие ответы.Некоторые реализации библиотеки emplace_back не ведут себя так, как указано в стандарте C ++, включая версию, поставляемую с Visual Studio 2012, 2013 и 2015.
Чтобы учесть известные ошибки компилятора, предпочтите использовать,
std::vector::push_back()
если параметры ссылаются на итераторы или другие объекты, которые будут недействительными после вызова.В одном компиляторе v содержит значения 123 и 21 вместо ожидаемых 123 и 123. Это связано с тем, что второй вызов
emplace_back
приводит к изменению размера, при котором точкаv[0]
становится недействительной.Работающая реализация приведенного выше кода будет использовать
push_back()
вместоemplace_back()
следующего:Примечание: использование вектора целых для демонстрационных целей. Я обнаружил эту проблему с гораздо более сложным классом, который включал динамически размещенные переменные-члены, и вызов
emplace_back()
привел к серьезному падению.источник
push_back
быть иначе в этом случае?