Regla de tres / cinco / cero
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 25 de 104.
Cuando tu clase administra recursos como memoria dinámica, has aprendido que necesitas un destructor personalizado, un constructor de copia y un constructor de movimiento. Pero hay un principio rector que te ayuda a decidir qué funciones miembro especiales implementar: la Regla de Tres, Cinco y Cero.
La Regla de Tres establece: si defines cualquiera de estos tres elementos, deberías definir los tres:
- Destructor
- Constructor de copia
- Operador de asignación de copia
La Regla de cinco amplía esto para C++ moderno, añadiendo operaciones de movimiento:
- Destructor
- Constructor de copia
- Operador de asignación de copia
- Constructor de movimiento
- Operador de asignación de movimiento
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
~Buffer() { delete[] data; } // 1. Destructor
Buffer(const Buffer& other); // 2. Constructor de copia
Buffer& operator=(const Buffer& other); // 3. Asignación por copia
Buffer(Buffer&& other) noexcept; // 4. Constructor de movimiento
Buffer& operator=(Buffer&& other) noexcept; // 5. Asignación por movimiento
};Ambos operadores de asignación siguen un patrón: protegerse contra la autoasignación, liberar el recurso antiguo, tomar el nuevo y devolver *this:
Buffer& Buffer::operator=(const Buffer& other) { // asignación por copia
if (this != &other) {
delete[] data;
size = other.size;
data = new int[size];
std::copy(other.data, other.data + size, data);
}
return *this;
}
Buffer& Buffer::operator=(Buffer&& other) noexcept { // asignación por movimiento
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}La Regla del Cero es el enfoque más sencillo: si tu clase no administra recursos directamente, no definas ninguna de estas funciones. Deja que el compilador las genere o usa punteros inteligentes y contenedores estándar que se encarguen de los recursos por ti.
class Player {
std::string name; // std::string gestiona su propia memoria
std::vector<int> scores; // std::vector gestiona sus recursos
public:
Player(std::string n) : name(n) {}
// ¡No se necesitan funciones de destrucción, copia ni movimiento!
};Seguir estas reglas evita errores como la doble eliminación, las fugas de memoria y los punteros colgantes que ocurren cuando algunas operaciones están definidas, pero faltan otras.
Desafío
FácilVamos a crear una clase TextBuffer que siga la Regla de Cinco: implementaremos las cinco funciones miembro especiales para gestionar correctamente los datos de caracteres asignados dinámicamente. Esto demostrará cómo las operaciones de copia y movimiento trabajan juntas para crear una clase robusta que gestione recursos.
Crearás dos archivos para organizar tu código:
TextBuffer.h: Define una claseTextBufferque almacene texto en una matriz de caracteres asignada dinámicamente. Tu clase necesita:- Miembros privados: un puntero
char*llamadodatapara el contenido de texto y unsize_t lengthpara la longitud de la cadena (sin incluir el terminador nulo) - Un constructor parametrizado que acepte una cadena C (
const char*), asigne memoria, copie el contenido e imprima"TextBuffer created: <text>" - Un destructor que libere la memoria (si no es nula) e imprima
"TextBuffer destroyed" - Un constructor de copia que realice una copia profunda e imprima
"TextBuffer copied" - Un operador de asignación por copia que gestione la autoasignación, libere los datos existentes, realice una copia profunda e imprima
"TextBuffer copy-assigned". Devuelve*this - Un constructor de movimiento (marcado como
noexcept) que transfiera la propiedad e imprima"TextBuffer moved". Deja el origen en un estado vacío válido - Un operador de asignación por movimiento (marcado como
noexcept) que gestione la autoasignación, libere los datos existentes, transfiera la propiedad e imprima"TextBuffer move-assigned". Devuelve*this - Un método
getText()que devuelva el texto almacenado (devuelve la cadena vacía""si data es nulo) - Un método
getLength()que devuelva la longitud
- Miembros privados: un puntero
main.cpp: Demuestra las cinco funciones miembro especiales en acción. Lee una cadena de texto de la entrada y luego:- Crea un
TextBufferllamadooriginalcon el texto de entrada - Crea
copiedusando el constructor de copia a partir deoriginal - Crea
anothercon el texto"Temporary" - Usa la asignación por copia:
another = original - Crea
movedmediante la construcción por movimiento desdeoriginal, usandostd::move() - Crea
targetcon el texto"Target" - Usa la asignación por movimiento:
target = std::move(copied) - Imprime
"--- Final State ---" - Imprime
"original: <text> (length: <len>)"para cada búfer: original, copied, moved, another, target
- Crea un
Después de los movimientos, los objetos de origen (original y copied) deberían mostrar texto vacío con longitud 0, mientras que los objetos de destino contienen los datos transferidos. Esto demuestra la Regla de Cinco en acción: las cinco funciones trabajan juntas para garantizar una gestión segura de los recursos.
Incluye <cstring> para funciones de cadenas como strlen y strcpy, y <utility> para std::move().
Pruébalo tú mismo
#include <iostream>
#include <string>
#include <utility>
#include "TextBuffer.h"
using namespace std;
int main() {
string input;
getline(cin, input);
// TODO: Crea un TextBuffer llamado 'original' con el texto de entrada
// TODO: Crea 'copied' usando el constructor de copia a partir de 'original'
// TODO: Crea 'another' con el texto "Temporary"
// TODO: Usa la asignación por copia: another = original
// TODO: Crea 'moved' mediante el constructor de movimiento a partir de 'original' usando std::move()
// TODO: Crea 'target' con el texto "Target"
// TODO: Usa la asignación por movimiento: target = std::move(copied)
// TODO: Imprime "--- Final State ---"
// TODO: Imprime el estado de cada búfer con este formato:
// "original: <text> (length: <len>)"
// Imprime el estado de: original, copied, moved, another, target
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genéricoPractica por tu cuenta: Compilador de C++ online