El contenedor std::vector es uno de los componentes más utilizados de la biblioteca estándar de C++ (STL). Se basa en una estructura de datos de arreglo dinámico que permite el acceso aleatorio y la gestión automática del crecimiento de la memoria.
Arquitectura y Crecimiento
Internamente, un std::vector gestiona un puntero a un bloque de memoria contigua. Utiliza dos variables fundamentales para el control de recursos:
- Size: La cantidad actual de elementos almacenados.
- Capacity: El espacio total reservado en memoria antes de que sea necesaria una nueva asignación.
Cuando se insertan elementos mediante push_back o emplace_back y el size iguala a la capacity, el contenedor realiza una reasignación: reserva un nuevo bloque de memoria más grande, mueve los objetos existentes al nuevo destino y libera la memoria anterior.
Factores de Expansión
El factor por el cual se incrementa la capacidad varía según el compilador:
- MSVC (Microsoft Visual C++): Utiliza típicamente un factor de 1.5.
- GCC (GNU Compiler Collection): Utiliza habitualmente un factor de 2.0.
Optimización del Rendimiento
Para evitar la sobrecarga de múltiples reasignaciones y movimientos de datos innecesarios, se recomienda el uso del método reserve(). Si conocemos de antemano la cantidad aproximada de elementos, podemos pre-asignar la memoria necesaria de la siguiente manera:
#include <vector>
void optimizarVector() {
std::vector<int> datos;
// Reservamos espacio para 500 elementos de una vez
datos.reserve(500);
for(int i = 0; i < 500; ++i) {
datos.push_back(i); // No habrá reasignaciones de memoria aquí
}
}
Diferencias entre push_back y emplace_back
Aunque ambos añaden elementos al final, emplace_back permite la construcción in-place del objeto pasando los argumentos directamente al constructor, evitando en algunos casos la creación de un objeto temporal. A partir de C++17, emplace_back devuelve una referencia al elemento insertado, mientras que push_back tradicionalmente no devuelve nada (void).
Gestión de Eliminación: erase vs std::remove
- erase(): Es un método miembro que elimina físicamente los elementos del contenedor y ajusta su tamaño.
- std::remove(): Es un algoritmo de la cabecera
<algorithm>que desplaza los elementos que no coinciden con el criterio al principio del rango, pero no altera el tamaño del vector; devuelve un iterador al nuevo final lógico.
Invalidez de Iteradores
Los iteradores de un vector pueden quedar invalidados en dos escenarios principales:
- Operaciones de inserción: Si la inserción provoca una reasignación de memoria (crecimiento de capacidad), todos los iteradores, punteros y referencias anteriores quedan invalidados.
- Operaciones de borrado: Al eliminar un elemetno, todos los iteradores que apuntan al elemento borrado y a los elementos posteriores se invalidan debido al desplazamiento de memoria.
Liberación Efectiva de Memoria
Llamar a clear() elimina los elementos pero mantiene la capacidad (la memoria sigue reservada). Para liberar realmente el espacio asignado, existen dos técnicas comunes:
1. Uso de shrink_to_fit
#include <iostream>
#include <vector>
int main() {
std::vector<double> valores(1000, 1.0);
valores.clear(); // Size es 0, Capacity sigue siendo 1000
valores.shrink_to_fit(); // Reduce la capacidad para que coincida con el tamaño (0)
return 0;
}
2. El modismo Swap
#include <vector>
int main() {
std::vector<int> contenedor(500, 10);
// Intercambio con un vector temporal vacío
std::vector<int>().swap(contenedor);
// 'contenedor' ahora tiene size 0 y capacity 0
return 0;
}
La especialización std::vector<bool>
Es importante notar que std::vector<bool> no es un contenedor estándar típico. Está optimizado para ahorrar espacio almacanando cada booleano como un bit individual. Esto implica que no se puede obtener una dirección de memoria (puntero) directa a un elemento individual, ya que C++ no permite direccionar bits de forma nativa. Si se requiere un comportamiento de contenedor estándar o acceso a bits específico, se recomienda utilizar std::deque<bool> o std::bitset.