Rule of Three / Five / Zero
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 25 von 104.
Wenn Ihre Klasse Ressourcen wie dynamischen Speicher verwaltet, haben Sie gelernt, dass Sie einen benutzerdefinierten Destruktor, Kopierkonstruktor und Verschiebekonstruktor benötigen. Aber es gibt ein Leitprinzip, das Ihnen bei der Entscheidung hilft, welche speziellen Elementfunktionen implementiert werden sollen: die Regel der Drei, Fünf und Null.
Die Dreierregel (Rule of Three) besagt: Wenn Sie eine dieser drei definieren, sollten Sie alle drei definieren:
- Destruktor
- Kopierkonstruktor
- Kopierzuweisungsoperator
Die Rule of Five erweitert dies für modernes C++ und fügt Verschiebeoperationen hinzu:
- Destruktor
- Kopierkonstruktor
- Kopierzuweisungsoperator
- Verschiebekonstruktor
- Verschiebezuweisungsoperator
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
~Buffer() { delete[] data; } // 1. Destruktor
Buffer(const Buffer& other); // 2. Kopierkonstruktor
Buffer& operator=(const Buffer& other); // 3. Kopierzuweisung
Buffer(Buffer&& other) noexcept; // 4. Verschiebekonstruktor
Buffer& operator=(Buffer&& other) noexcept; // 5. Verschiebezuweisung
};Die Rule of Zero ist der einfachste Ansatz: Wenn Ihre Klasse Ressourcen nicht direkt verwaltet, definieren Sie keine dieser Funktionen. Lassen Sie den Compiler diese generieren oder verwenden Sie Smart Pointer und Standard-Container, die Ressourcen für Sie verwalten.
class Player {
std::string name; // std::string verwaltet seinen eigenen Speicher
std::vector<int> scores; // std::vector verwaltet seine Ressourcen
public:
Player(std::string n) : name(n) {}
// Kein Destruktor, Kopier- oder Verschiebe-Funktionen benötigt!
};Das Befolgen dieser Regeln verhindert Fehler wie Double-Deletion, Speicherlecks und Dangling Pointers, die auftreten, wenn einige Operationen definiert sind, andere jedoch fehlen.
Aufgabe
EinfachLassen Sie uns eine TextBuffer-Klasse erstellen, die der Rule of Five folgt — indem wir alle fünf speziellen Elementfunktionen implementieren, um dynamisch zugewiesene Zeichendaten ordnungsgemäß zu verwalten. Dies wird zeigen, wie Kopier- und Verschiebeoperationen zusammenarbeiten, um eine robuste, ressourcenverwaltende Klasse zu erstellen.
Sie werden zwei Dateien erstellen, um Ihren Code zu organisieren:
TextBuffer.h: Definieren Sie eineTextBuffer-Klasse, die Text in einem dynamisch zugewiesenen Zeichen-Array speichert. Ihre Klasse benötigt:- Private Member: einen
char*-Pointer namensdatafür den Textinhalt und einesize_t lengthfür die Stringlänge (ohne Null-Terminator) - Einen parametrisierten Konstruktor, der einen C-String (
const char*) entgegennimmt, Speicher zuweist, den Inhalt kopiert und"TextBuffer created: <text>"ausgibt - Einen Destruktor, der den Speicher freigibt (falls nicht null) und
"TextBuffer destroyed"ausgibt - Einen Kopierkonstruktor, der eine tiefe Kopie (deep copy) durchführt und
"TextBuffer copied"ausgibt - Einen Kopierzuweisungsoperator, der Selbstzuweisung behandelt, vorhandene Daten bereinigt, eine tiefe Kopie durchführt und
"TextBuffer copy-assigned"ausgibt. Geben Sie*thiszurück - Einen Verschiebekonstruktor (als
noexceptmarkiert), der den Besitz überträgt und"TextBuffer moved"ausgibt. Hinterlassen Sie die Quelle in einem gültigen leeren Zustand - Einen Verschiebezuweisungsoperator (als
noexceptmarkiert), der Selbstzuweisung behandelt, vorhandene Daten bereinigt, den Besitz überträgt und"TextBuffer move-assigned"ausgibt. Geben Sie*thiszurück - Eine
getText()-Methode, die den gespeicherten Text zurückgibt (geben Sie einen leeren String""zurück, falls data null ist) - Eine
getLength()-Methode, die die Länge zurückgibt
- Private Member: einen
main.cpp: Demonstrieren Sie alle fünf speziellen Elementfunktionen in Aktion. Lesen Sie einen Text-String von der Eingabe ein, dann:- Erstellen Sie einen
TextBuffernamensoriginalmit dem eingegebenen Text - Erstellen Sie
copiedunter Verwendung des Kopierkonstruktors vonoriginal - Erstellen Sie
anothermit dem Text"Temporary" - Verwenden Sie die Kopierzuweisung:
another = original - Erstellen Sie
moveddurch Verschiebekonstruktion vonoriginalunter Verwendung vonstd::move() - Erstellen Sie
targetmit dem Text"Target" - Verwenden Sie die Verschiebezuweisung:
target = std::move(copied) - Geben Sie
"--- Final State ---"aus - Geben Sie
"original: <text> (length: <len>)"für jeden Buffer aus: original, copied, moved, another, target
- Erstellen Sie einen
Nach den Verschiebungen sollten die Quellobjekte (original und copied) leeren Text mit der Länge 0 anzeigen, während die Zielobjekte die übertragenen Daten halten. Dies demonstriert die Rule of Five in Aktion — alle fünf Funktionen arbeiten zusammen, um eine sichere Ressourcenverwaltung zu gewährleisten.
Binden Sie <cstring> für String-Funktionen wie strlen und strcpy ein, sowie <utility> für std::move().
Spickzettel
Die Rule of Three (Dreierregel) besagt, dass wenn Sie eine dieser drei speziellen Elementfunktionen definieren, Sie alle drei definieren sollten:
- Destruktor
- Kopierkonstruktor
- Kopierzuweisungsoperator
Die Rule of Five (Fünferregel) erweitert dies für modernes C++, indem sie Verschiebeoperationen hinzufügt:
- Destruktor
- Kopierkonstruktor
- Kopierzuweisungsoperator
- Verschiebekonstruktor
- Verschiebezuweisungsoperator
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
~Buffer() { delete[] data; } // 1. Destruktor
Buffer(const Buffer& other); // 2. Kopierkonstruktor
Buffer& operator=(const Buffer& other); // 3. Kopierzuweisung
Buffer(Buffer&& other) noexcept; // 4. Verschiebekonstruktor
Buffer& operator=(Buffer&& other) noexcept; // 5. Verschiebezuweisung
};Die Rule of Zero (Nullerregel) besagt, dass wenn Ihre Klasse Ressourcen nicht direkt verwaltet, Sie keine speziellen Elementfunktionen definieren sollten. Verwenden Sie Smart Pointer und Standardcontainer, die Ressourcen automatisch verwalten:
class Player {
std::string name; // std::string verwaltet seinen eigenen Speicher
std::vector<int> scores; // std::vector verwaltet seine Ressourcen
public:
Player(std::string n) : name(n) {}
// Keine Destruktor-, Kopier- oder Verschiebe-Funktionen erforderlich!
};Das Befolgen dieser Regeln verhindert Fehler wie doppeltes Freigeben (double-deletion), Speicherlecks und hängende Zeiger (dangling pointers).
Probier es selbst
#include <iostream>
#include <string>
#include <utility>
#include "TextBuffer.h"
using namespace std;
int main() {
string input;
getline(cin, input);
// TODO: Erstelle einen TextBuffer namens 'original' mit dem eingegebenen Text
// TODO: Erstelle 'copied' unter Verwendung des Kopierkonstruktors von 'original'
// TODO: Erstelle 'another' mit dem Text "Temporary"
// TODO: Verwende die Kopierzuweisung: another = original
// TODO: Erstelle 'moved' durch Move-Konstruktion von 'original' unter Verwendung von std::move()
// TODO: Erstelle 'target' mit dem Text "Target"
// TODO: Verwende die Move-Zuweisung: target = std::move(copied)
// TODO: Gib "--- Final State ---" aus
// TODO: Gib den Zustand jedes Buffers in diesem Format aus:
// "original: <text> (length: <len>)"
// Ausgabe für: original, copied, moved, another, target
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamensräume & GültigkeitsbereicheEinführung in OOP in C++Klassen vs. ObjekteDer 'this'-PointerMethoden (Elementfunktionen)Attribute (Datenelemente)Grundlagen zu Ctors & DtorsRückblick - Einfacher Taschenrechner4Klasseneigenschaften
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen & KlassenZusammenfassung - Bankkonto-Manager7Vererbung
Grundlagen der VererbungZugriffsstufen bei VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick - Mitarbeiter-Hierarchie2Speicherverwaltung
Stack vs. Heap SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Smart Pointers in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsspezifizierer in C++Zugriffsspezifizierer im DetailInformation HidingStruct vs. ClassVerschachtelte & innere KlassenRückblick - Studentenverwaltungssystem8Polymorphie
Kompilierzeit- vs. Laufzeit-PolymorphieFunktionsüberladungVirtuelle Funktionen – WiederholungRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIZusammenfassung – Formen-Rechner3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorVerschiebekonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktoren im DetailRule of Three / Five / ZeroZusammenfassung - String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladen der [] und () OperatorenTypumwandlungsoperatorenRückblick - Matrix-Klasse9Templates
FunktionstemplatesKlassentemplatesTemplate-SpezialisierungVariadische TemplatesSFINAE & Type Traits GrundlagenRückblick - Generische Container