El constructor de copia y el operador de asignación por copia son necesarios para trabajar con objetos que poseen recursos (por ejemplo, memoria dinámica). Por defecto, el compilador generará una copia superficial de los campos, lo cual es inseguro para un puntero crudo:
class Buffer { public: Buffer(size_t size) { data = new int[size]; this->size = size; } ~Buffer() { delete[] data; } // Implementación correcta del constructor de copia Buffer(const Buffer& other) : size(other.size) { data = new int[size]; std::copy(other.data, other.data + size, data); } // Operador de asignación correcto Buffer& operator=(const Buffer& other) { if (this != &other) { delete[] data; size = other.size; data = new int[size]; std::copy(other.data, other.data + size, data); } return *this; } private: int* data; size_t size; };
De lo contrario, al copiar dos objetos, uno libera la memoria, mientras que el otro queda con un puntero colgado (double free o uso después de liberar).
¿Qué sucederá si solo se declara el constructor de copia explícitamente, pero no se declara el operador de asignación? ¿Cuándo es necesario y cuándo no?
Respuesta: Si solo se declara el constructor de copia, pero no se declara el operador de asignación, el compilador generará un operador de asignación por defecto (copia bit a bit), lo cual es peligroso si el objeto contiene recursos dinámicos, es decir, destruye el mismo puntero dos veces.
En el caso de la gestión de memoria, es necesario implementar ambos: tanto el constructor de copia como el operador de asignación, para evitar errores de copia/fugas de memoria.
Historia
En un servidor multimedia, al copiar un objeto de buffer se utilizó el constructor de copia, pero se olvidaron del operador de asignación. Cuando un buffer se asignaba a otro, se producía doble liberación de memoria al destruir los objetos. El error se manifestó en pruebas de estrés.
Historia
En un proyecto de logística de transporte, el operador de asignación por defecto copiaba una estructura con un puntero a un arreglo de coordenadas. Después de eliminar un objeto, el segundo accedía a la memoria liberada, causando errores de segmentación.
Historia
En uno de los proyectos con recursos gráficos, se olvidaron de implementar el constructor de copia al pasar objetos entre hilos. La transmisión por valor llevó a una copia superficial, y después de modificar el objeto en otro hilo, se produjeron daños en los datos y la caída del programa.