The rule of three/five/zero
Inhaltsverzeichnis |
Regel von drei
Wenn eine Klasse einen benutzerdefinierten Destruktor , einen benutzerdefinierten Copy-Konstruktor , oder einen benutzerdefinierten Copy-Zuweisungsoperator benötigt, dann benötigt sie mit großer Wahrscheinlichkeit alle drei.
Da C++ Objekte benutzerdefinierter Typen in verschiedenen Situationen kopiert und kopierzuweist (Übergabe/Rückgabe als Wert, Manipulation eines Containers, etc.), werden diese speziellen Memberfunktionen aufgerufen, falls zugänglich, und falls sie nicht benutzerdefiniert sind, werden sie implizit vom Compiler definiert.
Die implizit definierten speziellen Elementfunktionen sollten nicht verwendet werden, wenn die Klasse eine Ressource verwaltet , deren Handle ein Objekt eines Nicht-Klassen-Typs ist (Raw-Pointer, POSIX-Dateideskriptor, etc), deren Destruktor nichts tut und der Kopierkonstruktor/Zuweisungsoperator ein "shallow copy" durchführt (kopiert den Wert des Handles, ohne die zugrundeliegende Ressource zu duplizieren).
#include <cstddef> #include <cstring> #include <iostream> #include <utility> class rule_of_three { char* cstring; // roher Zeiger, verwendet als Handle für einen // dynamisch allokierten Speicherblock public: explicit rule_of_three(const char* s = "") : cstring(nullptr) { if (s) { cstring = new char[std::strlen(s) + 1]; // allokieren std::strcpy(cstring, s); // befüllen } } ~rule_of_three() // I. Destruktor { delete[] cstring; // freigeben } rule_of_three(const rule_of_three& other) // II. Kopierkonstruktor : rule_of_three(other.cstring) {} rule_of_three& operator=(const rule_of_three& other) // III. Kopierzuweisung { // implementiert durch Kopieren-und-Tauschen der Kürze halber // beachte, dass dies potenzielle Wiederverwendung von Speicher verhindert rule_of_three temp(other); std::swap(cstring, temp.cstring); return *this; } const char* c_str() const // Zugriffsmethode { return cstring; } }; int main() { rule_of_three o1{"abc"}; std::cout << o1.c_str() << ' '; auto o2{o1}; // II. verwendet Kopierkonstruktor std::cout << o2.c_str() << ' '; rule_of_three o3("def"); std::cout << o3.c_str() << ' '; o3 = o2; // III. verwendet Kopierzuweisung std::cout << o3.c_str() << '\n'; } // I. alle Destruktoren werden hier aufgerufen
Ausgabe:
abc abc def abc
Klassen, die nicht-kopierbare Ressourcen durch kopierbare Handles verwalten, müssen möglicherweise Kopierzuweisung und Kopierkonstruktor private deklarieren und ihre Definitionen nicht bereitstellen (bis C++11) Kopierzuweisung und Kopierkonstruktor als = delete definieren (seit C++11) . Dies ist eine weitere Anwendung der Rule of Three: Das Löschen eines und das implizite Definieren des anderen ist typischerweise inkorrekt.
Regel der Fünf
Da das Vorhandensein eines benutzerdefinierten (einschließlich = default oder = delete deklarierten) Destruktors, Kopierkonstruktors oder Kopierzuweisungsoperators die implizite Definition des move constructor und des move assignment operator verhindert, muss jede Klasse, für die Move-Semantik erwünscht ist, alle fünf speziellen Member-Funktionen deklarieren:
class rule_of_five { char* cstring; // Rohzeiger, verwendet als Handle für einen // dynamisch allokierten Speicherblock public: explicit rule_of_five(const char* s = "") : cstring(nullptr) { if (s) { cstring = new char[std::strlen(s) + 1]; // allokieren std::strcpy(cstring, s); // befüllen { } ~rule_of_five() { delete[] cstring; // deallokieren } rule_of_five(const rule_of_five& other) // Kopierkonstruktor : rule_of_five(other.cstring) {} rule_of_five(rule_of_five&& other) noexcept // Move-Konstruktor : cstring(std::exchange(other.cstring, nullptr)) {} rule_of_five& operator=(const rule_of_five& other) // Kopierzuweisung { // implementiert als Move-Zuweisung von einer temporären Kopie für Kürze // beachten Sie, dass dies potenzielle Wiederverwendung von Speicher verhindert return *this = rule_of_five(other); } rule_of_five& operator=(rule_of_five&& other) noexcept // Move-Zuweisung { std::swap(cstring, other.cstring); return *this; } // alternativ können beide Zuweisungsoperatoren durch eine Copy-and-Swap- // Implementierung ersetzt werden, die ebenfalls keine Wiederverwendung // von Speicher in der Kopierzuweisung ermöglicht. // rule_of_five& operator=(rule_of_five other) noexcept // { // std::swap(cstring, other.cstring); // return *this; // } };
Im Gegensatz zur Rule of Three ist das Unterlassen der Bereitstellung eines Move-Konstruktors und einer Move-Zuweisung in der Regel kein Fehler, sondern eine verpasste Optimierungsmöglichkeit.
Regel von Null
Klassen, die benutzerdefinierte Destruktoren, Kopier-/Move-Konstruktoren oder Kopier-/Move-Zuweisungsoperatoren besitzen, sollten sich ausschließlich mit Ownership befassen (was aus dem Single Responsibility Principle folgt). Andere Klassen sollten keine benutzerdefinierten Destruktoren, Kopier-/Move-Konstruktoren oder Kopier-/Move-Zuweisungsoperatoren besitzen [1] .
Diese Regel erscheint auch in den C++ Core Guidelines als C.20: If you can avoid defining default operations, do .
class rule_of_zero { std::string cppstring; public: rule_of_zero(const std::string& arg) : cppstring(arg) {} };
` Tags wurde gemäß den Anweisungen nicht übersetzt, da es sich um C++-Code handelt. Die HTML-Struktur und Attribute wurden ebenfalls unverändert beibehalten.
Wenn eine Basisklasse für polymorphe Verwendung vorgesehen ist, muss ihr Destruktor möglicherweise als public und virtual deklariert werden. Dies unterbindet implizite Moves (und verwirft implizite Kopien), daher müssen die speziellen Elementfunktionen als = default [2] definiert werden.
class base_of_five_defaults { public: base_of_five_defaults(const base_of_five_defaults&) = default; base_of_five_defaults(base_of_five_defaults&&) = default; base_of_five_defaults& operator=(const base_of_five_defaults&) = default; base_of_five_defaults& operator=(base_of_five_defaults&&) = default; virtual ~base_of_five_defaults() = default; };
Allerdings macht dies die Klasse anfällig für Slicing, weshalb polymorphe Klassen das Kopieren oft als = delete definieren (siehe C.67: A polymorphic class should suppress public copy/move in den C++ Core Guidelines), was zur folgenden generischen Formulierung für die Rule of Five führt: