Introducción: de lo que callan las documentaciones
Imagina: estás escribiendo un servidor de alta carga o un algoritmo de procesamiento de datos. Tienes dos std::vector<int> y necesitas combinarlos en uno. Parece sencillo, ¿verdad? Lo tomas y lo agregas. Pero C++ no perdona un enfoque superficial. Una forma incorrecta y obtienes copias innecesarias, caída del rendimiento o incluso comportamiento indefinido.
La pregunta «Appending a vector to a vector» en Stack Overflow ha acumulado miles de visitas no por casualidad. Incluso desarrolladores experimentados a veces confunden push_back con insert u olvidan la semántica de movimiento. En este artículo, nos adentraremos bajo el capó del compilador y descubriremos cómo funciona realmente agregar un vector a otro. Aprenderás por qué std::copy puede ser más rápido que insert y cuándo la semántica de movimiento salvará tu proyecto.
La forma más obvia (y peligrosa): push_back en un bucle
Los principiantes suelen escribir así:
std::vector<int> dest = {1, 2, 3};std::vector<int> src = {4, 5, 6};
for (size_t i = 0; i < src.size(); ++i) { dest.push_back(src[i]);}¿Qué sucede aquí? En cada iteración, push_back verifica si hay suficiente memoria asignada. Si no, ocurre una reasignación: el array antiguo se copia a una nueva área de memoria y el antiguo se elimina. Con tres elementos, puede haber 2–3 reasignaciones. Para vectores pequeños es imperceptible, pero con miles de elementos es una catástrofe.
Problema: O(n) verificaciones y copias potenciales de todo el array. En C++17 y versiones posteriores, el compilador puede optimizar, pero confiar en ello es un riesgo.
¿Qué sucede bajo el capó de push_back?
- Verificación de
size() < capacity(). - Si no, llamada a
reallocate(new_capacity). - Copia (o movimiento) de los elementos existentes.
- Eliminación del búfer antiguo.
- Copia del nuevo elemento.
Conclusión: nunca uses un bucle para agregar un vector completo, a menos que lo hagas de forma consciente para una pequeña cantidad de elementos.
El camino correcto: std::vector::insert
La biblioteca estándar proporciona una solución elegante:
std::vector<int> dest = {1, 2, 3};std::vector<int> src = {4, 5, 6};
dest.insert(dest.end(), src.begin(), src.end());¡En una sola línea! Pero ¿qué se esconde detrás de esta llamada? insert hace lo siguiente:
- Calcula la cantidad de memoria necesaria:
dest.size() + src.size(). - Si la capacidad actual es insuficiente, asigna exactamente lo necesario (o con margen, según la implementación).
- Desplaza los elementos existentes (si no se inserta al final).
- Copia (o mueve) los nuevos elementos desde
src.
Importante: insert realiza una reasignación (o ninguna, si hay suficiente espacio). Esto ya es mejor que el bucle, pero aún implica copiar elementos desde src.
Rendimiento de insert
Si src ya no es necesario después de la inserción, puedes mover sus elementos usando std::make_move_iterator:
dest.insert(dest.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()));Esto convierte la copia en movimiento. Para std::vector<int> no hay diferencia (int es un tipo trivial), pero para std::vector<std::string> u objetos complejos, mover es mucho más barato que copiar.
Alternativa: std::copy con back_inserter
Algunos desarrolladores prefieren los algoritmos de STL:
Blogs
Book Recommendations
Leer también
Monorepositorio con Kotlin: organización del código en la práctica
Error de Gradle: Unable to delete file – solución para Kotlin
Patrones de diseño en JavaScript con ejemplos de código
Expresiones regulares en C++ con ejemplos para principiantes
Desarrollo multiplataforma en PHP: escribiendo código que funciona en todas partes