Eu pensei bastante sobre essa questão nos últimos quatro anos. Cheguei à conclusão de que a maioria das explicações sobre push_backvs. emplace_backperde a imagem completa.
No ano passado, fiz uma apresentação no C ++ Now sobre dedução de tipo no C ++ 14 . Começo a falar sobre push_backvs. emplace_backàs 13:49, mas há informações úteis que fornecem algumas evidências de suporte antes disso.
A diferença principal real tem a ver com construtores implícitos vs. explícitos. Considere o caso em que temos um único argumento que queremos passar para push_backou emplace_back.
std::vector<T> v;
v.push_back(x);
v.emplace_back(x);
Após o compilador de otimização colocar as mãos nisso, não há diferença entre essas duas instruções em termos de código gerado. A sabedoria tradicional é que push_backconstruirá um objeto temporário, que será movido para a frente, venquanto emplace_backencaminhará o argumento e o construirá diretamente no lugar, sem cópias ou movimentos. Isso pode ser verdade com base no código escrito nas bibliotecas padrão, mas assume-se erroneamente que o trabalho do compilador otimizador é gerar o código que você escreveu. O trabalho do compilador de otimização é realmente gerar o código que você escreveria se fosse um especialista em otimizações específicas da plataforma e não se importasse com a manutenção, apenas com o desempenho.
A diferença real entre essas duas instruções é que quanto mais poderoso emplace_backchamará qualquer tipo de construtor, enquanto o mais cauteloso push_backchamará apenas construtores implícitos. Construtores implícitos devem ser seguros. Se você pode criar implicitamente um Ude a T, você está dizendo que Upode reter todas as informações Tsem perda. É seguro em praticamente qualquer situação passar um Te ninguém se importará se você o fizer U. Um bom exemplo de um construtor implícito é a conversão de std::uint32_tpara std::uint64_t. Um mau exemplo de uma conversão implícita é doublepara std::uint8_t.
Queremos ser cautelosos em nossa programação. Não queremos usar recursos poderosos, porque quanto mais poderoso o recurso, mais fácil é acidentalmente fazer algo incorreto ou inesperado. Se você pretende chamar construtores explícitos, precisará do poder de emplace_back. Se você quiser chamar apenas construtores implícitos, mantenha a segurança de push_back.
Um exemplo
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>tem um construtor explícito de T *. Como emplace_backpode chamar construtores explícitos, passar um ponteiro não proprietário compila perfeitamente. No entanto, quando vfica fora do escopo, o destruidor tentará chamar deleteesse ponteiro, que não foi alocado por newser apenas um objeto de pilha. Isso leva a um comportamento indefinido.
Este não é apenas um código inventado. Este foi um erro de produção real que encontrei. O código era std::vector<T *>, mas possuía o conteúdo. Como parte da migração para C ++ 11, eu corretamente mudou T *para std::unique_ptr<T>indicar que o vector de propriedade sua memória. No entanto, eu estava baseando essas mudanças no meu entendimento em 2012, durante o qual pensei "emplace_back faz tudo que push_back pode fazer e muito mais, então por que eu usaria push_back?", Então mudei também push_backpara emplace_back.
Se eu tivesse deixado o código usando o mais seguro push_back, instantaneamente teria detectado esse bug de longa data e teria sido visto como um sucesso da atualização para o C ++ 11. Em vez disso, mascarei o bug e não o encontrei meses depois.