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, de um construtor de cópia e de 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 amplia 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 de cópia
Buffer(Buffer&& other) noexcept; // 4. Construtor de movimentação
Buffer& operator=(Buffer&& other) noexcept; // 5. Atribuição de movimentação
};A Rule of Zero é a abordagem mais simples: se 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 gerenciem 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 move necessários!
};Seguir essas regras evita bugs 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.
Desafio
FácilVamos criar uma classe TextBuffer que siga a Regra dos Cinco: implementando todas 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 classeTextBufferque armazene 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 tamanho da string (sem incluir o terminador nulo) - Um construtor parametrizado que receba uma string C (
const char*), aloque memória, copie o conteúdo e imprima"TextBuffer created: <text>" - Um destrutor que libere a memória (se não for nula) e imprima
"TextBuffer destroyed" - Um construtor de cópia que faça uma cópia profunda e imprima
"TextBuffer copied" - Um operador de atribuição por cópia que trate a autoatribuição, libere os dados existentes, faça uma cópia profunda e imprima
"TextBuffer copy-assigned". Retorne*this - Um construtor de movimentação (marcado como
noexcept) que transfira a propriedade e imprima"TextBuffer moved". Deixe a origem em um estado vazio válido - Um operador de atribuição por movimentação (marcado como
noexcept) que trate a autoatribuição, libere os dados existentes, transfira a propriedade e imprima"TextBuffer move-assigned". Retorne*this - Um método
getText()que retorne o texto armazenado (retorne uma string vazia""se os dados forem nulos) - Um método
getLength()que retorne o tamanho
- Membros privados: um ponteiro
main.cpp: Demonstre todas as cinco funções-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
movedconstruindo-o por movimentação a partir deoriginalusandostd::move() - Crie
targetcom 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
- Crie um
Após as movimentações, os objetos de origem (original e copied) devem mostrar texto vazio com tamanho 0, enquanto os objetos de destino devem conter os dados transferidos. Isso demonstra a Regra dos Cinco em ação: todas as cinco funções trabalhando juntas para garantir um 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' por construção de movimentação a partir de 'original' usando 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;
}
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 POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner GenéricoPratique por conta própria: Compilador de C++ online