Move-Zuweisungsoperator
Ein Move-Zuweisungsoperator ist eine nicht-template nicht-statische Memberfunktion mit dem Namen operator=, die mit einem Argument vom Typ der gleichen Klasse aufgerufen werden kann und den Inhalt des Arguments kopiert, wobei das Argument möglicherweise mutiert wird.
Inhalt |
[bearbeiten] Syntax
Für die formale Syntax des Move-Zuweisungsoperators siehe Funktionsdeklaration. Die folgende Syntaxliste zeigt nur eine Teilmenge aller gültigen Syntaxen für Move-Zuweisungsoperatoren.
return-type operator=(parameter-list ); |
(1) | ||||||||
return-type operator=(parameter-list ) function-body |
(2) | ||||||||
return-type operator=(parameter-list-no-default ) = default; |
(3) | ||||||||
return-type operator=(parameter-list ) = delete; |
(4) | ||||||||
return-type class-name ::operator=(parameter-list ) function-body |
(5) | ||||||||
return-type class-name ::operator=(parameter-list-no-default ) = default; |
(6) | ||||||||
| Klassenname | - | die Klasse, deren Move-Zuweisungsoperator deklariert wird, der Klassentyp wird in den folgenden Beschreibungen als T angegeben |
| parameter-liste | - | eine Parameterliste mit nur einem Parameter, der vom Typ T&&, const T&&, volatile T&& oder const volatile T&& ist |
| parameter-list-no-default | - | eine Parameterliste mit nur einem Parameter, der vom Typ T&&, const T&&, volatile T&& oder const volatile T&& ist und kein Standardargument hat |
| function-body | - | der Funktionsrumpf des Move-Zuweisungsoperators |
| return-type | - | jeder Typ, aber T& wird bevorzugt, um konsistent mit Skalartypen zu sein |
[bearbeiten] Erklärung
struct X { X& operator=(X&& other); // move assignment operator // X operator=(const X other); // Error: incorrect parameter type }; union Y { // move assignment operators can have syntaxes not listed above, // as long as they follow the general function declaration syntax // and do not viloate the restrictions listed above auto operator=(Y&& other) -> Y&; // OK: trailing return type Y& operator=(this Y&& self, Y& other); // OK: explicit object parameter // Y& operator=(Y&&, int num = 1); // Error: has other non-object parameters };
Der Move-Zuweisungsoperator wird aufgerufen, wenn er durch Überladungsauflösung ausgewählt wird, z. B. wenn ein Objekt auf der linken Seite eines Zuweisungsausdrucks erscheint, wobei die rechte Seite ein R-Wert desselben oder eines implizit konvertierbaren Typs ist.
Move-Zuweisungsoperatoren übertragen typischerweise die vom Argument gehaltenen Ressourcen (z. B. Zeiger auf dynamisch zugewiesene Objekte, Dateideskriptoren, TCP-Sockets, Thread-Handles usw.) anstatt sie zu kopieren, und hinterlassen das Argument in einem gültigen, aber sonst unbestimmten Zustand. Da die Move-Zuweisung die Lebensdauer des Arguments nicht ändert, wird der Destruktor typischerweise später für das Argument aufgerufen. Zum Beispiel kann die Move-Zuweisung von einem std::string oder einem std::vector dazu führen, dass das Argument leer hinterlassen wird. Eine Move-Zuweisung ist weniger, nicht mehr restriktiv definiert als eine normale Zuweisung; wo eine normale Zuweisung bei Abschluss zwei Datenkopien hinterlassen muss, muss eine Move-Zuweisung nur eine hinterlassen.
[bearbeiten] Implizit deklarierter Move-Zuweisungsoperator
Wenn für einen Klassentyp keine benutzerdefinierten Move-Zuweisungsoperatoren bereitgestellt werden und alle folgenden Bedingungen erfüllt sind:
- keine benutzerdeklarierten Kopierkonstruktoren vorhanden sind;
- keine benutzerdeklarierten Move-Konstruktoren vorhanden sind;
- keine benutzerdeklarierten Kopier-Zuweisungsoperatoren vorhanden sind;
- kein benutzerdeklarisierter Destruktor vorhanden ist,
dann deklariert der Compiler einen Move-Zuweisungsoperator als inline public Member seiner Klasse mit der Signatur T& T::operator=(T&&).
Eine Klasse kann mehrere Move-Zuweisungsoperatoren haben, z. B. sowohl T& T::operator=(const T&&) als auch T& T::operator=(T&&). Wenn einige benutzerdefinierte Move-Zuweisungsoperatoren vorhanden sind, kann der Benutzer immer noch die Generierung des implizit deklarierten Move-Zuweisungsoperators mit dem Schlüsselwort default erzwingen.
Der implizit deklarierte Move-Zuweisungsoperator hat eine Ausnahmespezifikation, wie in dynamische Ausnahmespezifikation(bis C++17)noexcept-Spezifikation(seit C++17) beschrieben.
Da für jede Klasse immer ein Zuweisungsoperator (Move oder Kopie) deklariert wird, ist der Zuweisungsoperator der Basisklasse immer versteckt. Wenn eine using-Deklaration verwendet wird, um den Zuweisungsoperator aus der Basisklasse zu übernehmen, und dessen Argumenttyp derselbe sein könnte wie der Argumenttyp des impliziten Zuweisungsoperators der abgeleiteten Klasse, wird die using-Deklaration durch die implizite Deklaration ebenfalls versteckt.
[bearbeiten] Implizit definierter Move-Zuweisungsoperator
Wenn der implizit deklarierte Move-Zuweisungsoperator weder gelöscht noch trivial ist, wird er (d. h. ein Funktionsrumpf wird generiert und kompiliert) vom Compiler definiert, wenn er ODR-verwendet wirdoder für die konstante Auswertung benötigt wird(seit C++14).
Für Union-Typen kopiert der implizit definierte Move-Zuweisungsoperator die Objekt-Repräsentation (wie durch std::memmove).
Für Nicht-Union-Klassentypen führt der Move-Zuweisungsoperator eine vollständige Member-weise Move-Zuweisung der direkten Basisklassen und unmittelbaren nicht-statischen Member des Objekts in ihrer Deklarationsreihenfolge durch, wobei für Skalare die eingebaute Zuweisung, für Arrays die Member-weise Move-Zuweisung und für Klassentypen der Move-Zuweisungsoperator (nicht-virtuell aufgerufen) verwendet wird.
|
Der implizit definierte Move-Zuweisungsoperator für eine Klasse
|
(seit C++14) (bis C++23) |
|
Der implizit definierte Move-Zuweisungsoperator für eine Klasse |
(seit C++23) |
Wie bei der Kopierzuweisung ist es nicht spezifiziert, ob virtuelle Basisklassen-Subobjekte, die über mehr als einen Pfad im Vererbungsgitter erreichbar sind, vom implizit definierten Move-Zuweisungsoperator mehr als einmal zugewiesen werden.
struct V { V& operator=(V&& other) { // this may be called once or twice // if called twice, 'other' is the just-moved-from V subobject return *this; } }; struct A : virtual V {}; // operator= calls V::operator= struct B : virtual V {}; // operator= calls V::operator= struct C : B, A {}; // operator= calls B::operator=, then A::operator= // but they may only call V::operator= once int main() { C c1, c2; c2 = std::move(c1); }
[bearbeiten] Gelöschter Move-Zuweisungsoperator
Der implizit deklarierte oder standardmäßig generierte Move-Zuweisungsoperator für die Klasse T wird als gelöscht definiert, wenn eine der folgenden Bedingungen erfüllt ist:
-
That ein nicht-statisches Datenmitglied eines const-qualifizierten Nicht-Klassentyps (oder möglicherweise ein mehrdimensionales Array davon). -
That ein nicht-statisches Datenmitglied eines Referenztyps. -
That ein potenziell konstruiertes Subobjekt vom KlassentypM(oder möglicherweise ein mehrdimensionales Array davon), so dass die Überladungsauflösung zur Ermittlung des Move-Zuweisungsoperators vonM
- nicht zu einem benutzbaren Kandidaten führt, oder
- im Falle des Subobjekts als Varianten-Mitglied eine nicht-triviale Funktion auswählt.
Ein gelöschter implizit deklarierter Move-Zuweisungsoperator wird von der Überladungsauflösung ignoriert.
[bearbeiten] Trivialer Move-Zuweisungsoperator
Der Move-Zuweisungsoperator für die Klasse T ist trivial, wenn alle folgenden Bedingungen erfüllt sind:
- Er ist nicht benutzerdefiniert (d. h. er ist implizit definiert oder standardmäßig generiert);
-
Tkeine virtuellen Memberfunktionen hat; -
Tkeine virtuellen Basisklassen hat; - der für jede direkte Basis von
Tausgewählte Move-Zuweisungsoperator ist trivial; - der für jedes nicht-statische Klassenmitglied (oder Array eines Klassenmitglieds) von
Tausgewählte Move-Zuweisungsoperator ist trivial.
Ein trivialer Move-Zuweisungsoperator führt die gleiche Aktion wie der triviale Kopier-Zuweisungsoperator aus, d. h. er kopiert die Objekt-Repräsentation, als ob mit std::memmove. Alle Datentypen, die mit der C-Sprache kompatibel sind, sind trivial verschiebbar.
[bearbeiten] Berechtigter Move-Zuweisungsoperator
|
Ein Move-Zuweisungsoperator ist berechtigt, wenn er nicht gelöscht ist. |
(bis C++20) |
|
Ein Move-Zuweisungsoperator ist berechtigt, wenn alle folgenden Bedingungen erfüllt sind:
|
(seit C++20) |
Die Trivialität von berechtigten Move-Zuweisungsoperatoren bestimmt, ob die Klasse ein trivially copyable type ist.
[bearbeiten] Hinweise
Wenn sowohl Kopier- als auch Move-Zuweisungsoperatoren bereitgestellt werden, wählt die Überladungsauflösung den Move-Zuweisungsoperator, wenn das Argument ein R-Wert ist (entweder ein prvalue wie ein namenloses temporäres Objekt oder ein xvalue wie das Ergebnis von std::move), und wählt den Kopier-Zuweisungsoperator, wenn das Argument ein L-Wert ist (benanntes Objekt oder eine Funktion/Operator, die eine L-Wert-Referenz zurückgibt). Wenn nur der Kopier-Zuweisungsoperator bereitgestellt wird, wählen alle Argumentkategorien ihn aus (solange er sein Argument per Wert oder als Referenz auf const annimmt, da R-Werte an const-Referenzen gebunden werden können), was den Kopier-Zuweisungsoperator zum Fallback für den Move-Zuweisungsoperator macht, wenn Move nicht verfügbar ist.
Es ist nicht spezifiziert, ob virtuelle Basisklassen-Subobjekte, die über mehr als einen Pfad im Vererbungsgitter erreichbar sind, vom implizit definierten Move-Zuweisungsoperator mehr als einmal zugewiesen werden (gleiches gilt für den Kopier-Zuweisungsoperator).
Siehe Zuweisungsoperator-Überladung für zusätzliche Details zum erwarteten Verhalten eines benutzerdefinierten Move-Zuweisungsoperators.
[bearbeiten] Beispiel
#include <iostream> #include <string> #include <utility> struct A { std::string s; A() : s("test") {} A(const A& o) : s(o.s) { std::cout << "move failed!\n"; } A(A&& o) : s(std::move(o.s)) {} A& operator=(const A& other) { s = other.s; std::cout << "copy assigned\n"; return *this; } A& operator=(A&& other) { s = std::move(other.s); std::cout << "move assigned\n"; return *this; } }; A f(A a) { return a; } struct B : A { std::string s2; int n; // implicit move assignment operator B& B::operator=(B&&) // calls A's move assignment operator // calls s2's move assignment operator // and makes a bitwise copy of n }; struct C : B { ~C() {} // destructor prevents implicit move assignment }; struct D : B { D() {} ~D() {} // destructor would prevent implicit move assignment D& operator=(D&&) = default; // force a move assignment anyway }; int main() { A a1, a2; std::cout << "Trying to move-assign A from rvalue temporary\n"; a1 = f(A()); // move-assignment from rvalue temporary std::cout << "Trying to move-assign A from xvalue\n"; a2 = std::move(a1); // move-assignment from xvalue std::cout << "\nTrying to move-assign B\n"; B b1, b2; std::cout << "Before move, b1.s = \"" << b1.s << "\"\n"; b2 = std::move(b1); // calls implicit move assignment std::cout << "After move, b1.s = \"" << b1.s << "\"\n"; std::cout << "\nTrying to move-assign C\n"; C c1, c2; c2 = std::move(c1); // calls the copy assignment operator std::cout << "\nTrying to move-assign D\n"; D d1, d2; d2 = std::move(d1); }
Ausgabe
Trying to move-assign A from rvalue temporary move assigned Trying to move-assign A from xvalue move assigned Trying to move-assign B Before move, b1.s = "test" move assigned After move, b1.s = "" Trying to move-assign C copy assigned Trying to move-assign D move assigned
[bearbeiten] Fehlerberichte
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 1353 | C++11 | die Bedingungen, unter denen standardmäßig generierte Move-Zuweisungsoperatoren als gelöscht definiert werden, berücksichtigten keine mehrdimensionalen Array-Typen |
diese Typen berücksichtigen |
| CWG 1402 | C++11 | ein standardmäßig generierter Move-Zuweisungsoperator, der einen nicht-trivialen Kopier-Zuweisungsoperator aufrufen würde, wurde gelöscht; ein standardmäßig generierter Move-Zuweisungsoperator, der gelöscht ist, nahm immer noch an der Überladungsauflösung teil |
erlaubt den Aufruf solcher Kopierzuweisung Operatoren; wurde ignoriert in der Überladungsauflösung |
| CWG 1806 | C++11 | Die Spezifikation für einen standardmäßig generierten Move-Zuweisungsoperator mit einer virtuellen Basisklasse war unvollständig |
hinzugefügt |
| CWG 2094 | C++11 | ein volatiles Subobjekt machte einen standardmäßig generierten Move-Zuweisungsoperator nicht-trivial (CWG Issue 496) |
Trivialität nicht betroffen |
| CWG 2180 | C++11 | Ein standardmäßig generierter Move-Zuweisungsoperator für die Klasse Twurde nicht als gelöscht definiert, wenn T abstrakt war undnicht-verschiebbar direkte virtuelle Basisklassen hatte |
der Operator wird in diesem Fall als gelöscht definiert |
| CWG 2595 | C++20 | Ein Move-Zuweisungsoperator war nicht berechtigt, wenn es einen anderen Move-Zuweisungsoperator gab, der stärker eingeschränkt war, aber seine zugehörigen Constraints nicht erfüllte |
er kann in diesem Fall berechtigt sein |
| CWG 2690 | C++11 | Der implizit definierte Move-Zuweisungsoperator für Union-Typen kopierte die Objekt-Repräsentation nicht |
sie kopieren die Objekt- Repräsentation |