He pensado bastante en esta pregunta en los últimos cuatro años. He llegado a la conclusión de que la mayoría de las explicaciones sobre push_backvs. se emplace_backpierden la imagen completa.
El año pasado, hice una presentación en C ++ Now sobre Type Deduction en C ++ 14 . Empiezo a hablar sobre push_backvs. emplace_backa las 13:49, pero hay información útil que proporciona alguna evidencia de apoyo antes de eso.
La diferencia principal real tiene que ver con constructores implícitos versus explícitos. Considere el caso en el que tenemos un único argumento al que queremos pasar push_backo emplace_back.
std::vector<T> v;
v.push_back(x);
v.emplace_back(x);
Después de que su compilador de optimización se haga cargo de esto, no hay diferencia entre estas dos declaraciones en términos de código generado. La sabiduría tradicional es que push_backconstruirá un objeto temporal, que luego se moverá hacia adentro, vmientras emplace_backque reenviará el argumento y lo construirá directamente en su lugar sin copias ni movimientos. Esto puede ser cierto en función del código tal como está escrito en las bibliotecas estándar, pero supone erróneamente que el trabajo del compilador de optimización es generar el código que escribió. El trabajo del compilador optimizador es en realidad generar el código que habría escrito si fuera un experto en optimizaciones específicas de la plataforma y no le importara la mantenibilidad, solo el rendimiento.
La diferencia real entre estas dos afirmaciones es que el más poderoso emplace_backllamará a cualquier tipo de constructor, mientras que el más cauteloso push_backllamará solo a los constructores que están implícitos. Se supone que los constructores implícitos son seguros. Si puede construir implícitamente un a Upartir de a T, está diciendo que Upuede contener toda la información Tsin pérdida. Es seguro en casi cualquier situación pasar un Ty a nadie le importará si lo haces en su Ulugar. Un buen ejemplo de un constructor implícito es la conversión de std::uint32_ta std::uint64_t. Un mal ejemplo de una conversión implícita es doublea std::uint8_t.
Queremos ser cautelosos en nuestra programación. No queremos utilizar funciones potentes porque cuanto más potente es la función, más fácil es hacer accidentalmente algo incorrecto o inesperado. Si tiene la intención de llamar a constructores explícitos, entonces necesita el poder de emplace_back. Si solo desea llamar a constructores implícitos, cumpla con la seguridad de push_back.
Un ejemplo
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>tiene un constructor explícito de T *. Debido a que emplace_backpuede llamar a constructores explícitos, pasar un puntero no propietario compila perfectamente. Sin embargo, cuando está vfuera de alcance, el destructor intentará llamar deletea ese puntero, que no fue asignado newporque es solo un objeto de pila. Esto lleva a un comportamiento indefinido.
Este no es solo un código inventado. Este fue un error de producción real que encontré. El código era std::vector<T *>, pero poseía los contenidos. Como parte de la migración a C ++ 11, I correctamente cambiado T *a std::unique_ptr<T>indicar que el vector poseía su memoria. Sin embargo, basé estos cambios en mi comprensión en 2012, durante el cual pensé "emplace_back hace todo lo que push_back puede hacer y más, así que ¿por qué usaría push_back?", Así que también cambié push_backa emplace_back.
Si hubiera dejado el código como el más seguro push_back, habría detectado instantáneamente este error de larga data y habría sido visto como un éxito al actualizar a C ++ 11. En cambio, enmascaré el error y no lo encontré hasta meses después.