Menu
Coddy logo textTech

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 gestiona recursos como memoria dinámica, has aprendido que necesitas un destructor, un constructor de copia y un constructor de movimiento personalizados. Pero existe un principio rector que te ayuda a decidir qué funciones miembro especiales implementar: la Regla del Tres, el Cinco y el Cero.

La Regla de los Tres establece: si defines cualquiera de estos tres, deberías definir los tres:

  • Destructor
  • Constructor de copia
  • Operador de asignación por copia

La Regla de los Cinco extiende esto para el C++ moderno, añadiendo operaciones de movimiento:

  • Destructor
  • Constructor de copia
  • Operador de asignación por copia
  • Constructor de movimiento
  • Operador de asignación por 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
};

La Regla del Cero es el enfoque más sencillo: si tu clase no gestiona recursos directamente, no definas ninguna de estas funciones. Deja que el compilador las genere, o utiliza punteros inteligentes y contenedores estándar que gestionen los recursos por ti.

class Player {
    std::string name;              // std::string gestiona su propia memoria
    std::vector<int> scores;       // std::vector maneja sus recursos
public:
    Player(std::string n) : name(n) {}
    // ¡No se necesitan funciones de destructor, copia o movimiento!
};

Seguir estas reglas evita errores como la doble eliminación, fugas de memoria y punteros colgantes que ocurren cuando se definen algunas operaciones pero faltan otras.

challenge icon

Desafío

Fácil

Vamos a construir una clase TextBuffer que siga la Regla de los Cinco — implementando las cinco funciones miembro especiales para gestionar adecuadamente 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 gestiona recursos.

Crearás dos archivos para organizar tu código:

  • TextBuffer.h: Define una clase TextBuffer que almacena texto en un arreglo de caracteres asignado dinámicamente. Tu clase necesita:
    • Miembros privados: un puntero char* llamado data para el contenido del texto, y un size_t length para la longitud de la cadena (sin incluir el terminador nulo)
    • Un constructor parametrizado que toma una cadena de estilo C (const char*), asigna memoria, copia el contenido e imprime "TextBuffer created: <text>"
    • Un destructor que libera la memoria (si no es nula) e imprime "TextBuffer destroyed"
    • Un constructor de copia que realiza una copia profunda e imprime "TextBuffer copied"
    • Un operador de asignación por copia que maneja la auto-asignación, limpia los datos existentes, realiza una copia profunda e imprime "TextBuffer copy-assigned". Devuelve *this
    • Un constructor de movimiento (marcado como noexcept) que transfiere la propiedad e imprime "TextBuffer moved". Deja el origen en un estado vacío válido
    • Un operador de asignación por movimiento (marcado como noexcept) que maneja la auto-asignación, limpia los datos existentes, transfiere la propiedad e imprime "TextBuffer move-assigned". Devuelve *this
    • Un método getText() que devuelve el texto almacenado (devuelve una cadena vacía "" si data es nulo)
    • Un método getLength() que devuelve la longitud
  • main.cpp: Demuestra las cinco funciones miembro especiales en acción. Lee una cadena de texto de la entrada, luego:
    • Crea un TextBuffer llamado original con el texto de entrada
    • Crea copied usando el constructor de copia de original
    • Crea another con el texto "Temporary"
    • Usa la asignación por copia: another = original
    • Crea moved mediante la construcción por movimiento desde original usando std::move()
    • Crea target con el texto "Target"
    • Usa la asignación por movimiento: target = std::move(copied)
    • Imprime "--- Final State ---"
    • Imprime "original: <text> (length: <len>)" para cada buffer: original, copied, moved, another, target

Después de los movimientos, los objetos de origen (original y copied) deben mostrar un texto vacío con longitud 0, mientras que los objetos de destino contienen los datos transferidos. Esto demuestra la Regla de los Cinco en acción — las cinco funciones trabajando juntas para asegurar una gestión de recursos segura.

Incluye <cstring> para funciones de cadena como strlen y strcpy, y <utility> para std::move().

Hoja de referencia

La Regla de los Tres establece que si defines cualquiera de estas tres funciones miembro especiales, deberías definir las tres:

  • Destructor
  • Constructor de copia
  • Operador de asignación por copia

La Regla de los Cinco extiende esto para el C++ moderno añadiendo operaciones de movimiento:

  • Destructor
  • Constructor de copia
  • Operador de asignación por copia
  • Constructor de movimiento
  • Operador de asignación por 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
};

La Regla del Cero establece que si tu clase no gestiona recursos directamente, no definas ninguna función miembro especial. Utiliza punteros inteligentes y contenedores estándar que gestionen los recursos automáticamente:

class Player {
    std::string name;              // std::string gestiona su propia memoria
    std::vector<int> scores;       // std::vector maneja sus recursos
public:
    Player(std::string n) : name(n) {}
    // ¡No se necesitan funciones de destructor, copia o movimiento!
};

Seguir estas reglas evita errores como la doble liberación, fugas de memoria y punteros colgantes.

Pruébalo tú mismo

#include <iostream>
#include <string>
#include <utility>
#include "TextBuffer.h"

using namespace std;

int main() {
    string input;
    getline(cin, input);

    // TODO: Crear un TextBuffer llamado 'original' con el texto de entrada

    // TODO: Crear 'copied' usando el constructor de copia de 'original'

    // TODO: Crear 'another' con el texto "Temporary"

    // TODO: Usar la asignación por copia: another = original

    // TODO: Crear 'moved' mediante la construcción por movimiento desde 'original' usando std::move()

    // TODO: Crear 'target' con el texto "Target"

    // TODO: Usar la asignación por movimiento: target = std::move(copied)

    // TODO: Imprimir "--- Final State ---"

    // TODO: Imprimir el estado de cada búfer en este formato:
    // "original: <text> (length: <len>)"
    // Imprimir para: original, copied, moved, another, target

    return 0;
}
quiz iconPonte a prueba

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