Menu
CoddyTech

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 destrutor personalizado, um construtor de cópia e um construtor de movimentação. Mas há um princípio orientador que ajuda você a decidir quais funções-membro especiais implementar: a Regra de Três, Cinco e Zero.

A Regra dos Três afirma: se você definir qualquer um destes três, deverá definir os três:

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

A Rule of Five estende isso para o C++ moderno, adicionando operações de movimentação:

  • Destrutor
  • Construtor de cópia
  • Operador de atribuição por cópia
  • Construtor de movimentação
  • Operador de atribuição por movimentação
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 movimentação
    Buffer& operator=(Buffer&& other) noexcept;           // 5. Atribuição por movimentação
};

Ambos os operadores de atribuição seguem um padrão: proteger contra autoatribuição, liberar o recurso antigo, assumir o novo e retornar *this:

Buffer& Buffer::operator=(const Buffer& other) {   // atribuição por cópia
    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 {   // atribuição por movimentação
    if (this != &other) {
        delete[] data;
        data = other.data;
        size = other.size;
        other.data = nullptr;
        other.size = 0;
    }
    return *this;
}

A Regra do Zero é a abordagem mais simples: se sua classe não gerencia recursos diretamente, não defina nenhuma dessas funções. Deixe que o compilador as gere 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 gerencia seus recursos
public:
    Player(std::string n) : name(n) {}
    // Não são necessárias funções de destruição, cópia ou movimentação!
};

Seguir essas regras evita erros como exclusã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 criar uma classe TextBuffer que segue a Regra dos Cinco: implementando as cinco funções-membro especiais para gerenciar corretamente dados de caracteres alocados dinamicamente. Isso demonstrará como as operações de cópia e movimentação trabalham juntas para criar uma classe robusta que gerencia 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 (sem incluir o terminador nulo)
    • Um construtor parametrizado que recebe uma string C (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 movimentação (marcado com noexcept) que transfere a propriedade e imprime "TextBuffer moved". Deixe a origem em um estado vazio válido
    • Um operador de atribuição por movimentação (marcado com 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 a string vazia "" se data for nulo)
    • Um método getLength() que retorna o comprimento
  • main.cpp: Demonstre as cinco funções-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 por meio de um construtor de movimentação a partir de original, usando std::move()
    • Crie target com o texto "Target"
    • Use a atribuição por movimentação: 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 exibir texto vazio com comprimento 0, enquanto os objetos de destino contêm os dados transferidos. Isso demonstra a Regra dos Cinco em ação: 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().

Experimente você mesmo

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

using namespace std;

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

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

    // TODO: Crie 'copied' usando o construtor de cópia a partir de 'original'

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

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

    // TODO: Crie 'moved' usando o construtor de movimentação a partir de 'original' com std::move()

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

    // TODO: Use a atribuição por movimentação: target = std::move(copied)

    // TODO: Imprima "--- Final State ---"

    // TODO: Imprima o estado de cada buffer neste formato:
    // "original: <text> (length: <len>)"
    // Imprima 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

Pratique por conta própria: Compilador de C++ online