Drei-/Fünf-/Null-Regel
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 25 von 104.
Wenn deine Klasse Ressourcen wie dynamischen Speicher verwaltet, hast du gelernt, dass du einen benutzerdefinierten Destruktor, Kopierkonstruktor und Verschiebekonstruktor benötigst. Es gibt jedoch ein Leitprinzip, das dir bei der Entscheidung hilft, welche speziellen Memberfunktionen du implementieren solltest: die Rule of Three, Five, and Zero.
Die Rule of Three besagt: Wenn du eines dieser drei Elemente definierst, solltest du alle drei definieren:
- Destruktor
- Kopierkonstruktor
- Kopierzuweisungsoperator
Die Rule of Five erweitert dies für modernes C++ um Verschiebeoperationen:
- 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. Move-Konstruktor
Buffer& operator=(Buffer&& other) noexcept; // 5. Move-Zuweisung
};Die Rule of Zero ist der einfachste Ansatz: Wenn deine Klasse Ressourcen nicht direkt verwaltet, definiere keine dieser Funktionen. Lass den Compiler sie generieren oder verwende Smart Pointer und Standardcontainer, die Ressourcen für dich 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, keine Copy- oder Move-Funktionen nötig!
};Das Befolgen dieser Regeln verhindert Fehler wie doppeltes Löschen, Speicherlecks und hängende Zeiger, die auftreten, wenn einige Operationen definiert sind, andere jedoch fehlen.
Aufgabe
EinfachLass uns eine TextBuffer-Klasse erstellen, die der „Rule of Five“ folgt: Sie implementiert alle fünf speziellen Memberfunktionen, um dynamisch zugewiesene Zeichendaten ordnungsgemäß zu verwalten. Dies zeigt, wie Kopier- und Verschiebeoperationen zusammenwirken, um eine robuste, Ressourcen verwaltende Klasse zu erstellen.
Du erstellst zwei Dateien, um deinen Code zu organisieren:
TextBuffer.h: Definiere eineTextBuffer-Klasse, die Text in einem dynamisch zugewiesenen Zeichenarray speichert. Deine Klasse benötigt:- Private Member: einen
char*-Zeiger namensdatafür den Textinhalt und einesize_t lengthfür die Stringlänge (ohne den Nullterminator) - Einen parametrisierten Konstruktor, der einen C-String (
const char*) entgegennimmt, Speicher reserviert, den Inhalt kopiert und"TextBuffer created: <text>"ausgibt - Einen Destruktor, der den Speicher freigibt (falls er nicht null ist) und
"TextBuffer destroyed"ausgibt - Einen Kopierkonstruktor, der eine tiefe Kopie durchführt und
"TextBuffer copied"ausgibt - Einen Kopierzuweisungsoperator, der Selbstzuweisung behandelt, vorhandene Daten bereinigt, eine tiefe Kopie durchführt und
"TextBuffer copy-assigned"ausgibt. Gib*thiszurück - Einen Verschiebekonstruktor (mit
noexceptgekennzeichnet), der den Besitz überträgt und"TextBuffer moved"ausgibt. Hinterlasse die Quelle in einem gültigen leeren Zustand - Einen Verschiebezuweisungsoperator (mit
noexceptgekennzeichnet), der Selbstzuweisung behandelt, vorhandene Daten bereinigt, den Besitz überträgt und"TextBuffer move-assigned"ausgibt. Gib*thiszurück - Eine
getText()-Methode, die den gespeicherten Text zurückgibt (gib eine leere Zeichenkette""zurück, wenn data null ist) - Eine
getLength()-Methode, die die Länge zurückgibt
- Private Member: einen
main.cpp: Zeige alle fünf speziellen Memberfunktionen in Aktion. Lies eine Textzeichenkette von der Eingabe ein und:- Erstelle einen
TextBuffernamensoriginalmit dem eingegebenen Text - Erstelle
copiedmithilfe des Kopierkonstruktors ausoriginal - Erstelle
anothermit dem Text"Temporary" - Verwende die Kopierzuweisung:
another = original - Erstelle
moved, indem du ausoriginalmithilfe vonstd::move()einen Verschiebekonstruktor verwendest - Erstelle
targetmit dem Text"Target" - Verwende die Verschiebezuweisung:
target = std::move(copied) - Gib
"--- Final State ---"aus - Gib für jeden Puffer — original, copied, moved, another, target —
"original: <text> (length: <len>)"aus
- Erstelle einen
Nach den Verschiebeoperationen sollten die Quellobjekte (original und copied) einen leeren Text mit der Länge 0 anzeigen, während die Zielobjekte die übertragenen Daten enthalten. Dies demonstriert die „Rule of Five“ in Aktion: Alle fünf Funktionen arbeiten zusammen, um eine sichere Ressourcenverwaltung zu gewährleisten.
Binde <cstring> für Stringfunktionen wie strlen und strcpy sowie <utility> für std::move() ein.
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 Eingabetext
// TODO: Erstelle 'copied' mithilfe des Copy-Konstruktors von 'original'
// TODO: Erstelle 'another' mit dem Text "Temporary"
// TODO: Verwende Copy-Zuweisung: another = original
// TODO: Erstelle 'moved' durch Move-Konstruktion von 'original' mit std::move()
// TODO: Erstelle 'target' mit dem Text "Target"
// TODO: Verwende 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>)"
// Gib aus 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 & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler