Regola del Tre / Cinque / Zero
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 25 di 104.
Quando la tua classe gestisce risorse come la memoria dinamica, hai imparato che ti servono un distruttore personalizzato, un costruttore di copia e un costruttore di spostamento. Ma c'è un principio guida che ti aiuta a decidere quali funzioni membro speciali implementare: la Regola del tre, del cinque e dello zero.
La Regola del Tre afferma: se definisci uno qualsiasi di questi tre elementi, dovresti definirli tutti e tre:
- Distruttore
- Costruttore di copia
- Operatore di assegnazione per copia
La Regola del cinque estende questo principio per il C++ moderno, aggiungendo le operazioni di spostamento:
- Distruttore
- Costruttore di copia
- Operatore di assegnazione per copia
- Costruttore di spostamento
- Operatore di assegnazione per spostamento
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
~Buffer() { delete[] data; } // 1. Distruttore
Buffer(const Buffer& other); // 2. Costruttore di copia
Buffer& operator=(const Buffer& other); // 3. Assegnazione per copia
Buffer(Buffer&& other) noexcept; // 4. Costruttore di spostamento
Buffer& operator=(Buffer&& other) noexcept; // 5. Assegnazione per spostamento
};La Regola dello Zero è l'approccio più semplice: se la tua classe non gestisce direttamente risorse, non definire nessuna di queste funzioni. Lascia che sia il compilatore a generarle, oppure usa puntatori intelligenti e contenitori standard che gestiscono le risorse al posto tuo.
class Player {
std::string name; // std::string gestisce autonomamente la propria memoria
std::vector<int> scores; // std::vector gestisce le proprie risorse
public:
Player(std::string n) : name(n) {}
// Non servono funzioni di distruzione, copia o spostamento!
};Seguire queste regole previene bug come la doppia eliminazione, le perdite di memoria e i puntatori pendenti che si verificano quando alcune operazioni sono definite ma ne mancano altre.
Sfida
FacileCreiamo una classe TextBuffer che segue la Rule of Five: implementa tutte e cinque le funzioni membro speciali per gestire correttamente i dati dei caratteri allocati dinamicamente. Questo mostrerà come le operazioni di copia e spostamento collaborano per creare una classe robusta che gestisce le risorse.
Creerai due file per organizzare il tuo codice:
TextBuffer.h: definisci una classeTextBufferche memorizza il testo in un array di caratteri allocato dinamicamente. La classe deve avere:- Membri privati: un puntatore
char*chiamatodataper il contenuto testuale e unsize_t lengthper la lunghezza della stringa (senza includere il terminatore null) - Un costruttore con parametri che accetta una stringa C (
const char*), alloca memoria, copia il contenuto e stampa"TextBuffer created: <text>" - Un distruttore che libera la memoria (se non è null) e stampa
"TextBuffer destroyed" - Un costruttore di copia che esegue una copia profonda e stampa
"TextBuffer copied" - Un operatore di assegnazione per copia che gestisce l'autoassegnazione, libera i dati esistenti, esegue una copia profonda e stampa
"TextBuffer copy-assigned". Restituisci*this - Un costruttore di spostamento (contrassegnato
noexcept) che trasferisce la proprietà e stampa"TextBuffer moved". Lascia la sorgente in uno stato valido e vuoto - Un operatore di assegnazione per spostamento (contrassegnato
noexcept) che gestisce l'autoassegnazione, libera i dati esistenti, trasferisce la proprietà e stampa"TextBuffer move-assigned". Restituisci*this - Un metodo
getText()che restituisce il testo memorizzato (restituisci una stringa vuota""se data è null) - Un metodo
getLength()che restituisce la lunghezza
- Membri privati: un puntatore
main.cpp: mostra in azione tutte e cinque le funzioni membro speciali. Leggi una stringa di testo dall'input, poi:- Crea un
TextBufferchiamatooriginalcon il testo inserito - Crea
copiedusando il costruttore di copia dioriginal - Crea
anothercon il testo"Temporary" - Usa l'assegnazione per copia:
another = original - Crea
movedcostruendolo per spostamento daoriginalusandostd::move() - Crea
targetcon il testo"Target" - Usa l'assegnazione per spostamento:
target = std::move(copied) - Stampa
"--- Final State ---" - Stampa
"original: <text> (length: <len>)"per ogni buffer: original, copied, moved, another, target
- Crea un
Dopo gli spostamenti, gli oggetti sorgente (original e copied) dovrebbero mostrare un testo vuoto con lunghezza 0, mentre gli oggetti di destinazione contengono i dati trasferiti. Questo mostra in azione la Rule of Five: tutte e cinque le funzioni lavorano insieme per garantire una gestione sicura delle risorse.
Includi <cstring> per funzioni per le stringhe come strlen e strcpy, e <utility> per std::move().
Provalo tu
#include <iostream>
#include <string>
#include <utility>
#include "TextBuffer.h"
using namespace std;
int main() {
string input;
getline(cin, input);
// TODO: Crea un TextBuffer chiamato 'original' con il testo di input
// TODO: Crea 'copied' usando il costruttore di copia di 'original'
// TODO: Crea 'another' con il testo "Temporary"
// TODO: Usa l'assegnazione per copia: another = original
// TODO: Crea 'moved' usando il costruttore di spostamento su 'original' con std::move()
// TODO: Crea 'target' con il testo "Target"
// TODO: Usa l'assegnazione per spostamento: target = std::move(copied)
// TODO: Stampa "--- Final State ---"
// TODO: Stampa lo stato di ogni buffer in questo formato:
// "original: <text> (length: <len>)"
// Stampa per: original, copied, moved, another, target
return 0;
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online