349 Stimmen

Aus der Dreier-Regel wird mit C++11 die Fünfer-Regel?

Also, nachdem ich dieser wunderbare Vortrag auf rvalue-Referenzen, dachte ich, dass jede Klasse von einem solchen "move constructor" profitieren würde, template<class T> MyClass(T&& other) bearbeiten und natürlich ein "move assignment operator", template<class T> MyClass& operator=(T&& other) wie Philipp in seiner Antwort darlegt, wenn es dynamisch zugewiesene Mitglieder hat oder allgemein Zeiger speichert. Genau wie Sie sollte haben einen Kopiersektor, einen Zuweisungsoperator und einen Destruktor, wenn die oben genannten Punkte zutreffen. Was denken Sie?

336voto

Philipp Punkte 45643

Ich würde sagen, aus der Dreier-Regel wird die Dreier-, Vierer- und Fünfer-Regel:

Jede Klasse sollte explizit genau eine des folgenden Satzes spezieller Mitgliedsfunktionen Funktionen definieren:

  • Keine
  • Destruktor, Kopierkonstruktor, Kopierzuweisungsoperator

Darüber hinaus kann jede Klasse, die explizit einen Destruktor definiert, explizit einen Move-Konstruktor und/oder einen Move-Zuweisungsoperator definieren.

In der Regel eine der folgenden Gruppen von Sondermitgliedern Funktionen sinnvoll:

  • Keine (für viele einfache Klassen, bei denen die implizit erzeugten speziellen Mitgliedsfunktionen korrekt und schnell sind)
  • Destruktor, Kopierkonstruktor, Kopierzuweisungsoperator (in diesem Fall der Klasse wird nicht verschiebbar sein)
  • Destruktor, Konstruktor verschieben, Zuweisungsoperator verschieben (in diesem Fall ist die Klasse nicht kopierbar, nützlich für ressourcenverwaltende Klassen, bei denen die zugrunde liegende Ressource nicht kopierbar ist)
  • Destruktor, Kopier-Konstruktor, Kopier-Zuweisungsoperator, Verschiebe-Konstruktor (wegen der Kopier-Elision gibt es keinen Overhead, wenn der Kopier-Zuweisungsoperator sein Argument als Wert nimmt)
  • Destruktor, Kopierkonstruktor, Kopierzuweisungsoperator, Verschiebekonstruktor, Zuweisungsoperator verschieben

Anmerkung:

  • Der move-Konstruktor und der move-Zuweisungsoperator werden nicht für eine Klasse erzeugt, die explizit eine der anderen speziellen Mitgliedsfunktionen (wie destructor oder copy-constructor oder move-assignment operator) deklariert.
  • Der Kopierkonstruktor und der Kopierzuweisungsoperator werden nicht für eine Klasse erzeugt, die explizit einen Verschiebekonstruktor oder einen Verschiebezuweisungsoperator deklariert.
  • Und dass eine Klasse mit einem explizit deklarierten Destruktor und einem implizit definierten Kopierkonstruktor oder einem implizit definierten Kopierzuweisungsoperator als veraltet gilt.

Insbesondere die folgende, vollkommen gültige polymorphe Basisklasse von C++03:

class C {
  virtual ~C() { }   // allow subtype polymorphism
};

Sollte wie folgt umformuliert werden:

class C {
  C(const C&) = default;               // Copy constructor
  C(C&&) = default;                    // Move constructor
  C& operator=(const C&) = default;  // Copy assignment operator
  C& operator=(C&&) = default;       // Move assignment operator
  virtual ~C() { }                     // Destructor
};

Ein bisschen ärgerlich, aber wahrscheinlich besser als die Alternative (in diesem Fall die automatische Erzeugung spezieller Memberfunktionen zum Kopieren nur (ohne Umzugsmöglichkeit).

Im Gegensatz zur Regel der großen Drei, bei der die Nichteinhaltung der Regel zu ernsthaften Schäden führen kann, ist die Nichtdeklaration des move-Konstruktors und des move-Zuweisungsoperators im Allgemeinen in Ordnung, aber im Hinblick auf die Effizienz oft suboptimal. Wie bereits erwähnt, werden der move-Konstruktor und der move-Zuweisungsoperator nur dann erzeugt, wenn kein explizit deklarierter copy-Konstruktor, copy-Zuweisungsoperator oder destructor vorhanden ist. Dies ist nicht symmetrisch zum traditionellen C++03-Verhalten in Bezug auf die automatische Generierung von Kopierkonstruktoren und Kopierzuweisungsoperatoren, ist aber wesentlich sicherer. Die Möglichkeit, Verschiebekonstruktoren und Verschiebezuweisungsoperatoren zu definieren, ist also sehr nützlich und schafft neue Möglichkeiten (rein bewegliche Klassen), aber Klassen, die sich an die C++03-Regel der großen Drei halten, sind weiterhin in Ordnung.

Bei ressourcenverwaltenden Klassen können Sie den Kopierkonstruktor und den Kopierzuweisungsoperator als gelöscht definieren (was als Definition gilt), wenn die zugrunde liegende Ressource nicht kopiert werden kann. Oftmals möchte man den Konstruktor und den Zuweisungsoperator dennoch verschieben. Kopieren und Verschieben von Zuweisungsoperatoren werden oft implementiert mit swap wie in C++03. Sprechen über swap wenn wir bereits einen move-constructor und einen move-assignment operator haben, Spezialisierung std::swap werden Unwichtiges weil die generische std::swap verwendet den Move-Konstruktor und den Move-Assignment-Operator, falls verfügbar (und das sollte schnell genug sein).

Klassen, die nicht für die Ressourcenverwaltung (d.h. kein nicht leerer Destruktor) oder Subtyp-Polymorphismus (d.h. kein virtueller Destruktor) gedacht sind, sollten keine der fünf speziellen Mitgliedsfunktionen deklarieren; sie werden alle automatisch generiert und verhalten sich korrekt und schnell.

73voto

NoSenseEtAl Punkte 26326

Ich kann nicht glauben, dass niemand einen Link zu diese .

Der Artikel plädiert im Wesentlichen für die "Rule of Zero". Es ist nicht angemessen, den gesamten Artikel zu zitieren, aber ich glaube, dies ist der Hauptpunkt:

Klassen, die benutzerdefinierte Destruktoren, Konstruktoren zum Kopieren/Verschieben oder Zuweisungsoperatoren zum Kopieren/Verschieben haben, sollten sich ausschließlich mit dem Eigentum befassen. Andere Klassen sollten keine benutzerdefinierten Destruktoren, copy/move Konstruktoren oder Kopieren/Verschieben-Zuweisungsoperatoren haben.

Auch dieser Teil ist IMHO wichtig:

Gemeinsame "ownership-in-a-package"-Klassen sind im Standard enthalten Bibliothek enthalten: std::unique_ptr y std::shared_ptr . Durch den Einsatz von benutzerdefinierter Deleter-Objekte sind beide so flexibel, dass sie praktisch jede Art von Ressource zu verwalten.

21voto

Motti Punkte 104854

Das glaube ich nicht, der Dreisatz ist eine Faustregel, die besagt, dass eine Klasse, die einen der folgenden Punkte implementiert, aber nicht alle, wahrscheinlich fehlerhaft ist.

  1. Konstruktor kopieren
  2. Zuweisungsoperator
  3. Zerstörer

Das Weglassen des move-Konstruktors oder des move-Zuweisungsoperators bedeutet jedoch nicht, dass ein Fehler vorliegt. Es Mai eine verpasste Gelegenheit zur Optimierung sein (in den meisten Fällen) oder dass die Bewegungs-Semantik für diese Klasse nicht relevant ist, aber dies ist kein Fehler.

Die Definition eines Move-Konstruktors ist zwar eine bewährte Methode, aber nicht zwingend erforderlich. Es gibt viele Fälle, in denen ein Move-Konstruktor für eine Klasse nicht relevant ist (z. B. std::complex ) und alle Klassen, die sich in C++03 korrekt verhalten, werden sich auch in C++0x korrekt verhalten, selbst wenn sie keinen Move-Konstruktor definieren.

14voto

peoro Punkte 24751

Ja, ich denke, es wäre schön, einen Move-Konstruktor für solche Klassen anzubieten, aber denken Sie daran:

  • Das ist nur eine Optimierung.

    Die Implementierung von nur einem oder zwei Kopierkonstruktoren, einem Zuweisungsoperator oder einem Destruktor wird wahrscheinlich zu Fehlern führen, während das Fehlen eines Verschiebungskonstruktors nur die Leistung beeinträchtigen kann.

  • Move-Konstruktor kann nicht immer ohne Änderungen angewendet werden.

    Einige Klassen haben ihre Zeiger immer zugewiesen, und daher löschen solche Klassen ihre Zeiger immer im Destruktor. In diesen Fällen müssen Sie zusätzliche Prüfungen hinzufügen, um festzustellen, ob die Zeiger zugewiesen sind oder weggeschoben wurden (jetzt null sind).

8voto

Andrey Rekalo Punkte 191

Hier ein kurzes Update zum aktuellen Stand und den damit verbundenen Entwicklungen seit dem 24. Januar '11.

Gemäß dem C++11 Standard (siehe Anhang D's [depr.impldec]):

Die implizite Deklaration eines Kopierkonstruktors ist veraltet, wenn die Klasse einen benutzerdeklarierten Kopierzuweisungsoperator oder einen benutzerdeklarierten Destruktor hat. Die implizite Deklaration eines Kopierzuweisungsoperators ist veraltet, wenn die Klasse über einen vom Benutzer deklarierten Kopierkonstruktor oder einen vom Benutzer deklarierten Destruktor verfügt.

Es war eigentlich vorgeschlagen um das veraltete Verhalten überflüssig zu machen Damit hat C++14 eine echte "Fünfer-Regel" anstelle der traditionellen "Dreier-Regel". Im Jahr 2013 stimmte die EWG gegen diesen Vorschlag, der in C++2014 umgesetzt werden sollte. Die Hauptbegründung für die Entscheidung über den Vorschlag hatte mit allgemeinen Bedenken zu tun, dass bestehender Code gebrochen werden könnte.

In jüngster Zeit hat es sich vorgeschlagen den Wortlaut von C++11 erneut anzupassen, um die informelle Fünfer-Regel zu erreichen, nämlich dass

keine Kopierfunktion, keine Verschiebefunktion und kein Destruktor vom Compiler generiert werden, wenn eine dieser Funktionen vom Benutzer bereitgestellt wird.

Wenn die EWG diese "Regel" annimmt, wird sie wahrscheinlich für C++17 übernommen werden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X