Menu
Coddy logo textTech

Regra dos Três / Cinco / Zero

Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 25 de 104.

Quando sua classe gerencia recursos como memória dinâmica, você aprendeu que precisa de um destruidor, construtor de cópia e construtor de movimento personalizados. Mas existe um princípio orientador que ajuda você a decidir quais funções de membro especiais implementar: a Regra dos Três, Cinco e Zero.

A Regra dos Três estabelece: se você definir qualquer um destes três, você deve definir todos os três:

  • Destrutor
  • Construtor de cópia
  • Operador de atribuição por cópia

A Regra dos Cinco estende isso para o C++ moderno, adicionando operações de movimento:

  • Destrutor
  • Construtor de cópia
  • Operador de atribuição por cópia
  • Construtor de movimento
  • Operador de atribuição por movimento
class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    
    ~Buffer() { delete[] data; }                          // 1. Destrutor
    Buffer(const Buffer& other);                          // 2. Construtor de cópia
    Buffer& operator=(const Buffer& other);               // 3. Atribuição por cópia
    Buffer(Buffer&& other) noexcept;                      // 4. Construtor de movimento
    Buffer& operator=(Buffer&& other) noexcept;           // 5. Atribuição por movimento
};

A Regra do Zero é a abordagem mais simples: se a sua classe não gerencia recursos diretamente, não defina nenhuma dessas funções. Deixe o compilador gerá-las ou use ponteiros inteligentes e contêineres padrão que gerenciam os recursos para você.

class Player {
    std::string name;              // std::string gerencia sua própria memória
    std::vector<int> scores;       // std::vector lida com seus recursos
public:
    Player(std::string n) : name(n) {}
    // Nenhum destrutor, funções de cópia ou de movimento são necessários!
};

Seguir estas regras evita bugs como deleção dupla, vazamentos de memória e ponteiros pendentes que ocorrem quando algumas operações são definidas, mas outras estão ausentes.

challenge icon

Desafio

Fácil

Vamos construir uma classe TextBuffer que segue a Regra dos Cinco — implementando todas as cinco funções de membro especiais para gerenciar adequadamente dados de caracteres alocados dinamicamente. Isso demonstrará como as operações de cópia e movimentação trabalham juntas para criar uma classe robusta de gerenciamento de recursos.

Você criará dois arquivos para organizar seu código:

  • TextBuffer.h: Defina uma classe TextBuffer que armazena texto em um array de caracteres alocado dinamicamente. Sua classe precisa de:
    • Membros privados: um ponteiro char* chamado data para o conteúdo do texto, e um size_t length para o comprimento da string (não incluindo o terminador nulo)
    • Um construtor parametrizado que recebe uma C-string (const char*), aloca memória, copia o conteúdo e imprime "TextBuffer created: <text>"
    • Um destrutor que libera a memória (se não for nula) e imprime "TextBuffer destroyed"
    • Um construtor de cópia que realiza uma cópia profunda e imprime "TextBuffer copied"
    • Um operador de atribuição por cópia que lida com a autoatribuição, limpa os dados existentes, realiza uma cópia profunda e imprime "TextBuffer copy-assigned". Retorne *this
    • Um construtor de movimento (marcado como noexcept) que transfere a propriedade e imprime "TextBuffer moved". Deixe a origem em um estado vazio válido
    • Um operador de atribuição por movimento (marcado como noexcept) que lida com a autoatribuição, limpa os dados existentes, transfere a propriedade e imprime "TextBuffer move-assigned". Retorne *this
    • Um método getText() que retorna o texto armazenado (retorne uma string vazia "" se os dados forem nulos)
    • Um método getLength() que retorna o comprimento
  • main.cpp: Demonstre todas as cinco funções de membro especiais em ação. Leia uma string de texto da entrada e, em seguida:
    • Crie um TextBuffer chamado original com o texto de entrada
    • Crie copied usando o construtor de cópia a partir de original
    • Crie another com o texto "Temporary"
    • Use a atribuição por cópia: another = original
    • Crie moved através da construção por movimento a partir de original usando std::move()
    • Crie target com o texto "Target"
    • Use a atribuição por movimento: target = std::move(copied)
    • Imprima "--- Final State ---"
    • Imprima "original: <text> (length: <len>)" para cada buffer: original, copied, moved, another, target

Após as movimentações, os objetos de origem (original e copied) devem mostrar texto vazio com comprimento 0, enquanto os objetos de destino mantêm os dados transferidos. Isso demonstra a Regra dos Cinco em ação — todas as cinco funções trabalhando juntas para garantir o gerenciamento seguro de recursos.

Inclua <cstring> para funções de string como strlen e strcpy, e <utility> para std::move().

Folha de consulta

A Regra dos Três estabelece que, se você definir qualquer uma destas três funções-membro especiais, você deve definir todas as três:

  • Destrutor
  • Construtor de cópia
  • Operador de atribuição por cópia

A Regra dos Cinco estende isso para o C++ moderno, adicionando operações de movimento:

  • Destrutor
  • Construtor de cópia
  • Operador de atribuição por cópia
  • Construtor de movimento
  • Operador de atribuição por movimento
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. Copy constructor
    Buffer& operator=(const Buffer& other);               // 3. Copy assignment
    Buffer(Buffer&& other) noexcept;                      // 4. Move constructor
    Buffer& operator=(Buffer&& other) noexcept;           // 5. Move assignment
};

A Regra do Zero estabelece que, se sua classe não gerencia recursos diretamente, não defina nenhuma função-membro especial. Use smart pointers e containers padrão que gerenciam recursos automaticamente:

class Player {
    std::string name;              // std::string manages its own memory
    std::vector<int> scores;       // std::vector handles its resources
public:
    Player(std::string n) : name(n) {}
    // No destructor, copy, or move functions needed!
};

Seguir estas regras evita bugs como deleção dupla, vazamentos de memória e ponteiros pendentes.

Experimente você mesmo

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

using namespace std;

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

    // TODO: Criar um TextBuffer chamado 'original' com o texto de entrada

    // TODO: Criar 'copied' usando o construtor de cópia de 'original'

    // TODO: Criar 'another' com o texto "Temporary"

    // TODO: Usar atribuição por cópia: another = original

    // TODO: Criar 'moved' através da construção por movimento de 'original' usando std::move()

    // TODO: Criar 'target' com o texto "Target"

    // TODO: Usar atribuição por movimento: target = std::move(copied)

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

    // TODO: Imprimir o estado de cada buffer neste formato:
    // "original: <text> (length: <len>)"
    // Imprimir para: original, copied, moved, another, target

    return 0;
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos