El mecanismo de copia de objetos en C++ se divide en copia superficial (shallow copy) y copia profunda (deep copy). La diferencia es especialmente importante para las clases con memoria asignada dinámicamente.
En C++, muchas estructuras de datos trabajan con memoria dinámica (new/delete). Por defecto, el compilador genera un constructor de copia y un operador de asignación que realizan una copia byte a byte (shallow copy). Esto es rápido, pero peligroso si el objeto gestiona recursos externos.
La shallow copy copia solo las direcciones de los recursos dinámicamente asignados. Al eliminar un objeto, la memoria será liberada, y el otro objeto quedará con un puntero "colgante" (dangling). Como resultado, surgen problemas de doble liberación, fugas de memoria y fallos.
La copia profunda implica la creación explícita de copias de todos los recursos dinámicos. Para ello, en la clase es necesario implementar el constructor de copia y el operador de asignación manualmente, para asegurar la copia de cada elemento.
Ejemplo de código para una clase con un arreglo:
class DynArray { int* data; size_t size; public: DynArray(size_t n) : size(n), data(new int[n]) {} ~DynArray() { delete[] data; } // Constructor de copia profundo DynArray(const DynArray& other) : size(other.size), data(new int[other.size]) { for (size_t i = 0; i < size; ++i) data[i] = other.data[i]; } // Asignación de copia profunda DynArray& operator=(const DynArray& other) { if (this != &other) { delete[] data; size = other.size; data = new int[size]; for (size_t i = 0; i < size; ++i) data[i] = other.data[i]; } return *this; } };
Aspectos clave:
¿El compilador siempre genera correctamente el constructor de copia y el operador de asignación, verdad?
Respuesta:
Incorrecto. Para las clases con recursos dinámicos, la copia por defecto es incorrecta: ambos objetos poseerán el mismo recurso. La deep copy debe implementarse explícitamente cuando se gestionan recursos externos.
¿Es necesario implementar un destructor si se ha escrito solo el constructor de copia/operador de asignación profundo?
Respuesta:
Sí, de lo contrario habrá una fuga de memoria: si se libera memoria en el constructor de copia personalizado, pero no se implementa un destructor, la memoria no será liberada al destruir los objetos.
¿Puede std::vector almacenar punteros y por qué al copiarlo pueden ocurrir fugas?
Respuesta:
Sí, std::vector puede almacenar punteros. Al copiar dicho std::vector, se copian los propios punteros, no los objetos a los que apuntan. Esto es una shallow copy: si se necesita una deep copy de todo el contenido, será necesario copiar manualmente cada objeto y colocarlos en memoria de forma independiente.
Ejemplo:
std::vector<int*> v1; v1.push_back(new int(42)); std::vector<int*> v2 = v1; // Se copian punteros, no *int
Un programador implementa una clase envoltura de un arreglo sin sobrecargar el constructor de copia/operador de asignación. Como resultado, ambos objetos poseen la misma memoria, y destruir uno lleva a un fallo al acceder al segundo.
Ventajas:
Desventajas:
Un desarrollador implementa la copia profunda: el contenido del arreglo es copiado, hay su propio destructor y un operador de asignación con protección contra la auto-asignación.
Ventajas:
Desventajas: