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.
Desafio
FácilVamos 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 classeTextBufferque armazena texto em um array de caracteres alocado dinamicamente. Sua classe precisa de:- Membros privados: um ponteiro
char*chamadodatapara o conteúdo do texto, e umsize_t lengthpara 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
- Membros privados: um ponteiro
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
TextBufferchamadooriginalcom o texto de entrada - Crie
copiedusando o construtor de cópia a partir deoriginal - Crie
anothercom o texto"Temporary" - Use a atribuição por cópia:
another = original - Crie
movedatravés da construção por movimento a partir deoriginalusandostd::move() - Crie
targetcom 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
- Crie um
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;
}
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
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico