Shallow copy (copia superficial) - copia solo los valores de los campos del objeto, incluidos los punteros, pero no duplica la memoria asignada dinámicamente. Como resultado, ambos objetos apuntan a la misma área de memoria. Esto puede provocar errores al modificar datos o liberar memoria.
Deep copy (copia profunda) - crea una copia completa de todos los recursos a los que el objeto hace referencia, de modo que cada objeto posea su propia copia de los datos.
Cuándo necesitas deep copy:
Ejemplo de implementación de deep copy:
class ArrayHolder { public: ArrayHolder(size_t size) : sz(size), data(new int[size]) {} ~ArrayHolder() { delete[] data; } // Constructor de copia profunda ArrayHolder(const ArrayHolder& other) : sz(other.sz), data(new int[other.sz]) { std::copy(other.data, other.data + other.sz, data); } // Asignación de copia profunda ArrayHolder& operator=(const ArrayHolder& other) { if (this != &other) { delete[] data; sz = other.sz; data = new int[sz]; std::copy(other.data, other.data + sz, data); } return *this; } private: size_t sz; int* data; };
Pregunta: ¿Funcionará correctamente el constructor de copia si simplemente copias el valor del puntero?
Respuesta: No, esto provocará una eliminación doble de la memoria al destruir ambos objetos. Es necesario implementar la copia profunda para gestionar correctamente los recursos.
Historia
En uno de los proyectos, para transferir matrices entre módulos, se utilizaron objetos de una clase donde el constructor de copia predeterminado solo copiaba el puntero (shallow copy). Como resultado, después de que el objeto temporal saliera del alcance, la matriz se destruía, mientras que el objeto principal quedaba con un puntero "suspendido", lo que llevaba a un comportamiento indefinido y al bloqueo de la aplicación.
Historia
En una biblioteca para trabajar con imágenes, se copiaron varias veces objetos que contenían búferes asignados dinámicamente. Debido a la falta de deep copy, después de liberar la memoria en un objeto, otro accedía a la zona liberada. Esto resultó en daños de datos y bloqueos al eliminar el objeto.
Historia
Al integrar código obsoleto con estándares modernos, en las clases antiguas no se implementaron ni el constructor de copia ni el operador de asignación. Usar estas clases con contenedores de la biblioteca estándar provocó fallos inesperados, ya que los contenedores copiaban objetos de manera superficial.