push_back vs emplace_back: ¿cuál es la diferencia y qué elegir para un principiante?

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: ¿por qué es importante?

\n\n

¿Alguna vez te has preguntado por qué en C++ existen dos formas de agregar un elemento al final de un vector? Parecería, ¿para qué reinventar la rueda si existe el probado push_back? La respuesta está en el rendimiento y la flexibilidad del C++ moderno.

\n\n

En Stack Overflow, esta pregunta ha acumulado miles de visitas y cientos de comentarios. Y no es casualidad: entender la diferencia entre push_back y emplace_back no es solo un conocimiento académico, sino una habilidad práctica que puede acelerar tu código de 2 a 3 veces. Analicémoslo de una vez por todas.

\n\n

Diferencia básica: ¿qué dicen las documentaciones?

\n\n

Empecemos con lo simple. Ambos métodos agregan un elemento al final del contenedor (vector, lista, cola). Pero lo hacen de manera diferente:

\n\n
    \n
  • push_back — recibe un objeto ya creado (por valor, referencia o referencia rvalue) y lo copia/mueve al contenedor.
  • \n
  • emplace_back — recibe los argumentos para el constructor del objeto y lo crea directamente dentro del contenedor (construcción in-place).
  • \n
\n\n

¿Suena abstracto? Veamos el código.

\n\n

Ejemplo práctico: creando una clase

\n\n

Imaginemos que tenemos una clase simple Person:

\n\n
#include <iostream>\n#include <vector>\n#include <string>\n\nclass Person {\npublic:\n    std::string name;\n    int age;\n\n    // Constructor\n    Person(const std::string& n, int a) \n        : name(n), age(a) {\n        std::cout << "Constructor llamado para " << name << std::endl;\n    }\n\n    // Constructor de copia\n    Person(const Person& other) \n        : name(other.name), age(other.age) {\n        std::cout << "Constructor de copia para " << name << std::endl;\n    }\n\n    // Constructor de movimiento\n    Person(Person&& other) noexcept\n        : name(std::move(other.name)), age(other.age) {\n        std::cout << "Constructor de movimiento para " << name << std::endl;\n    }\n};
\n\n

Ahora veamos qué sucede al usar diferentes métodos.

\n\n

Opción 1: push_back con objeto temporal

\n\n
int main() {\n    std::vector<Person> people;\n    people.push_back(Person("Alice", 25));\n    return 0;\n}
\n\n

Salida:

\n
Constructor llamado para Alice\nConstructor de movimiento para Alice
\n\n

¿Qué sucedió? Primero se crea un objeto temporal Person (primera llamada al constructor). Luego, este objeto se mueve al vector (constructor de movimiento). Dos llamadas: trabajo duplicado.

\n\n

Opción 2: push_back con objeto ya creado

\n\n
int main() {\n    std::vector<Person> people;\n    Person alice("Alice", 25);\n    people.push_back(alice);  // alice — lvalue\n    return 0;\n}
\n\n

Salida:

\n
Constructor llamado para Alice\nConstructor de copia para Alice
\n\n

Aquí vemos una copia, no un movimiento. Si el objeto es grande (contiene, por ejemplo, arreglos dinámicos), la copia puede ser costosa.

\n\n

Opción 3: emplace_back

\n\n
int main() {\n    std::vector<Person> people;\n    people.emplace_back("Bob", 30);  // Pasamos argumentos para el constructor\n    return 0;\n}
\n\n

Salida:

\n
Constructor llamado para Bob
\n\n

¡Solo una llamada al constructor! El objeto Person se crea directamente dentro de la memoria del vector, sin copias o movimientos intermedios.

\n\n

Rendimiento: ¿cuándo es crítica la diferencia?

\n\n

Para tipos simples (int, double, punteros) la diferencia es imperceptible: el compilador lo optimiza todo. Pero para objetos complejos, la diferencia puede ser significativa:

\n\n
    \n
  • Objetos con constructor de copia pesado (por ejemplo, std::string, contenedo

Blogs

Book Recommendations