Regla de tres: si una clase gestiona un recurso (por ejemplo, memoria), al implementar explícitamente uno de los siguientes métodos, es necesario implementar también los demás:
C++11: Regla de cinco – se añadieron operaciones de movimiento:
La violación de esta regla conduce a errores en la gestión de recursos, como la eliminación doble o fugas de memoria.
class Buffer { char* data; public: Buffer(size_t sz) : data(new char[sz]) {} ~Buffer() { delete[] data; } Buffer(const Buffer& other) : data(new char[strlen(other.data)+1]) { strcpy(data, other.data); } Buffer& operator=(const Buffer& other) { if (&other != this) { delete[] data; data = new char[strlen(other.data)+1]; strcpy(data, other.data); } return *this; } // semántica de movimiento en C++11+: Buffer(Buffer&& other) noexcept : data(other.data) { other.data = nullptr; } Buffer& operator=(Buffer&& other) noexcept { if (&other != this) { delete[] data; data = other.data; other.data = nullptr; } return *this; } };
¿Es suficiente implementar solo el destructor si la clase gestiona un puntero?
No. Sin operaciones de copia y movimiento, al copiar habrá eliminación doble de memoria. Por ejemplo:
Buffer a(10); Buffer b = a; // b y a eliminarán el mismo puntero!
Historia
En la plataforma de agregación de datos para telecomunicaciones, todas las clases solo implementaron el destructor. Después de la refactorización de la estructura, hubo fallos masivos por eliminación doble: la copia de objetos producía comportamientos aleatorios.
Historia
En el proyecto de un juego móvil, olvidaron implementar el constructor de movimiento para la clase contenedora del búfer. Al mover, el objeto se copiaba, lo que causaba copias innecesarias de datos y caídas de rendimiento.
Historia
En la biblioteca de serialización de estructuras de datos, al regresar un objeto de una función, se devolvía un objeto temporal, y el constructor de copia hacía una copia superficial del puntero. Esto provocó numerosas fugas, que solo se manifestaron meses después de su funcionamiento.