Move assignment operator
Ein Move-Zuweisungsoperator ist eine nicht-templatbasierte nicht-statische Elementfunktion mit dem Namen operator = die mit einem Argument desselben Klassentyps aufgerufen werden kann und den Inhalt des Arguments kopiert, wobei möglicherweise das Argument verändert wird.
Inhaltsverzeichnis |
Syntax
Für die formale Syntax des Verschiebezuweisungsoperators, siehe Funktionsdeklaration . Die folgende Syntaxliste zeigt nur einen Teil aller gültigen Syntaxformen des Verschiebezuweisungsoperators.
Rückgabetyp
operator=(
Parameterliste
);
|
(1) | ||||||||
Rückgabetyp
operator=(
Parameterliste
)
Funktionsrumpf
|
(2) | ||||||||
Rückgabetyp
operator=(
Parameterliste-ohne-Standardwerte
) = default;
|
(3) | ||||||||
Rückgabetyp
operator=(
Parameterliste
) = delete;
|
(4) | ||||||||
Rückgabetyp
Klassenname
::
operator=(
Parameterliste
)
Funktionsrumpf
|
(5) | ||||||||
Rückgabetyp
Klassenname
::
operator=(
Parameterliste-ohne-Standardwerte
) = default;
|
(6) | ||||||||
| class-name | - |
die Klasse, deren Move-Zuweisungsoperator deklariert wird, der Klassentyp wird als
T
in den folgenden Beschreibungen angegeben
|
| parameter-list | - |
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 keinen Standardargument hat
|
| function-body | - | der Funktionsrumpf des Move-Zuweisungsoperators |
| return-type | - |
jeder Typ, aber
T&
wird bevorzugt, um mit skalaren Typen konsistent zu sein
|
Erklärung
struct X { X& operator=(X&& other); // Move-Zuweisungsoperator // X operator=(const X other); // Fehler: Falscher Parametertyp }; union Y { // Move-Zuweisungsoperatoren können andere Syntaxen als die oben aufgeführten haben, // solange sie der allgemeinen Funktionsdeklarationssyntax folgen // und die oben aufgeführten Einschränkungen nicht verletzen auto operator=(Y&& other) -> Y&; // OK: Nachgestellter Rückgabetyp Y& operator=(this Y&& self, Y& other); // OK: Expliziter Objektparameter // Y& operator=(Y&&, int num = 1); // Fehler: Enthält andere Nicht-Objekt-Parameter };
Der Verschiebungszuweisungsoperator wird aufgerufen, sobald 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 implizit konvertierbaren Typs ist.
Move-Zuweisungsoperatoren übertragen typischerweise die vom Argument gehaltenen Ressourcen (z.B. Zeiger auf dynamisch allokierte Objekte, Dateideskriptoren, TCP-Sockets, Thread-Handles etc.), anstatt Kopien davon zu erstellen, und lassen das Argument in einem gültigen, aber ansonsten unbestimmten Zustand zurück. Da Move-Zuweisung die Lebensdauer des Arguments nicht verändert, wird der Destruktor typischerweise zu einem späteren Zeitpunkt für das Argument aufgerufen. Beispielsweise kann Move-Zuweisung von einem std::string oder von einem std::vector dazu führen, dass das Argument leer zurückbleibt. Eine Move-Zuweisung ist weniger, nicht restriktiver definiert als gewöhnliche Zuweisung; während gewöhnliche Zuweisung zwei Kopien der Daten nach Abschluss hinterlassen muss, ist Move-Zuweisung nur verpflichtet, eine Kopie zu hinterlassen.
Implizit deklarierter Move-Zuweisungsoperator
Wenn keine benutzerdefinierten Move-Zuweisungsoperatoren für einen Klassentyp bereitgestellt werden und alle folgenden Bedingungen zutreffen:
- es gibt keine benutzerdeklarierten copy constructors ;
- es gibt keine benutzerdeklarierten move constructors ;
- es gibt keine benutzerdeklarierten copy assignment operators ;
- es gibt keinen benutzerdeklarierten destructor ,
dann deklariert der Compiler einen Move-Zuweisungsoperator als inline public Mitglied 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 benutzerdefinierte Move-Zuweisungsoperatoren vorhanden sind, kann der Benutzer die Generierung des implizit deklarierten Move-Zuweisungsoperators mit dem Schlüsselwort
default
weiterhin erzwingen.
Der implizit deklarierte Move-Zuweisungsoperator hat eine Ausnahmespezifikation, wie beschrieben in dynamischer Ausnahmespezifikation (bis C++17) noexcept-Spezifikation (seit C++17) .
Da für jede Klasse immer ein Zuweisungsoperator (Move oder Copy) deklariert wird, wird der Zuweisungsoperator der Basisklasse stets verborgen. Wenn eine using-Deklaration verwendet wird, um den Zuweisungsoperator aus der Basisklasse einzubringen, und sein Argumenttyp derselbe sein könnte wie der Argumenttyp des impliziten Zuweisungsoperators der abgeleiteten Klasse, wird die using-Deklaration ebenfalls durch die implizite Deklaration verborgen.
Implizit definierter Move-Zuweisungsoperator
Wenn der implizit deklarierte Move-Zuweisungsoperator weder gelöscht noch trivial ist, wird er (d.h. ein Funktionskörper wird generiert und kompiliert) vom Compiler definiert, falls ODR-used oder needed for constant evaluation (seit C++14) .
Für Union-Typen kopiert der implizit definierte Verschiebungszuweisungsoperator die Objektrepräsentation (wie durch std::memmove ).
Für Nicht-Union-Klassentypen führt der Move-Zuweisungsoperator eine vollständige memberweise Move-Zuweisung der direkten Basisklassen und unmittelbaren nicht-statischen Member des Objekts durch, in ihrer Deklarationsreihenfolge, unter Verwendung von built-in Zuweisung für Skalare, memberweiser Move-Zuweisung für Arrays und Move-Zuweisungsoperator für Klassentypen (nicht-virtual aufgerufen).
|
Der implizit definierte Verschiebungszuweisungsoperator für eine Klasse
|
(seit C++14)
(bis C++23) |
|
Der implizit definierte Verschiebungszuweisungsoperator für eine Klasse
|
(seit C++23) |
Wie bei der Zuweisung durch Kopieren ist es nicht spezifiziert, ob virtuelle Basisklassen-Subobjekte, die über mehr als einen Pfad im Vererbungsgitter erreichbar sind, durch den implizit definierten Move-Zuweisungsoperator mehr als einmal zugewiesen werden:
struct V { V& operator=(V&& other) { // dies kann ein- oder zweimal aufgerufen werden // wenn zweimal aufgerufen, ist 'other' das gerade verschobene V-Subobjekt return *this; } }; struct A : virtual V {}; // operator= ruft V::operator= auf struct B : virtual V {}; // operator= ruft V::operator= auf struct C : B, A {}; // operator= ruft B::operator=, dann A::operator= auf // aber sie dürfen V::operator= nur einmal aufrufen int main() { C c1, c2; c2 = std::move(c1); }
Gelöschter Move-Zuweisungsoperator
Der implizit deklarierte oder standardmäßig festgelegte Verschiebungszuweisungsoperator für die Klasse
T
ist als gelöscht definiert, wenn eine der folgenden Bedingungen erfüllt ist:
-
Tbesitzt ein nicht-statisches Datenelement eines const-qualifizierten Nicht-Klassen-Typs (oder möglicherweise mehrdimensionales Array davon). -
Tbesitzt ein nicht-statisches Datenelement eines Referenztyps. -
Tbesitzt ein potenziell konstruiertes Subobjekt vom KlassentypM(oder möglicherweise mehrdimensionales Array davon), sodass die Überladungsauflösung bei der Suche nachM's Move-Zuweisungsoperator
-
- führt nicht zu einem verwendbaren Kandidaten, oder
- im Fall, dass das Unterobjekt ein variant member ist, wählt eine nicht-triviale Funktion aus.
Ein implizit deklarierter gelöschter Move-Zuweisungsoperator wird von der Überladungsauflösung ignoriert.
Trivialer Move-Zuweisungsoperator
Der Verschiebungszuweisungsoperator für die Klasse
T
ist trivial, wenn alle folgenden Bedingungen zutreffen:
- Es ist nicht vom Benutzer bereitgestellt (d.h., es ist implizit-definiert oder defaulted);
-
That keine virtuellen Memberfunktionen; -
That keine virtuellen Basisklassen; -
Der Move-Zuweisungsoperator, der für jede direkte Basis von
Tausgewählt wird, ist trivial; -
Der Move-Zuweisungsoperator, der für jedes nicht-statische Klassentyp-Mitglied (oder Array von Klassentyp) von
Tausgewählt wird, ist trivial.
Ein trivialer Verschiebungszuweisungsoperator führt dieselbe Aktion aus wie der triviale Kopierzuweisungsoperator, das heißt, er erstellt eine Kopie der Objektrepräsentation als ob durch std::memmove . Alle mit der C-Sprache kompatiblen Datentypen sind trivial verschiebbar zuweisbar.
Qualifizierter Move-Zuweisungsoperator
|
Ein Move-Zuweisungsoperator ist geeignet, wenn er nicht gelöscht ist. |
(bis C++20) |
|
Ein Move-Zuweisungsoperator ist geeignet, wenn alle folgenden Bedingungen erfüllt sind:
|
(seit C++20) |
Die Trivialität von geeigneten Move-Zuweisungsoperatoren bestimmt, ob die Klasse ein trivially copyable type ist.
Hinweise
Wenn sowohl Kopier- als auch Move-Zuweisungsoperatoren bereitgestellt werden, wählt die Überladungsauflösung die Move-Zuweisung, wenn das Argument ein rvalue ist (entweder ein prvalue wie ein namenloser Temporärwert oder ein xvalue wie das Ergebnis von std::move ), und wählt die Kopierzuweisung, wenn das Argument ein lvalue ist (benanntes Objekt oder eine Funktion/ein Operator, die/dem eine Lvalue-Referenz zurückgibt). Wenn nur die Kopierzuweisung bereitgestellt wird, wählen alle Argumentkategorien diese (sofern sie ihr Argument als Wert oder als Referenz auf const entgegennimmt, da Rvalues an const-Referenzen binden können), was die Kopierzuweisung zum Fallback für die Move-Zuweisung 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, mehr als einmal durch den implizit definierten Verschiebezuweisungsoperator zugewiesen werden (dasselbe gilt für copy assignment ).
Siehe assignment operator overloading für weitere Details zum erwarteten Verhalten eines benutzerdefinierten move-assignment operators.
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; // impliziter Move-Zuweisungsoperator B& B::operator=(B&&) // ruft A's Move-Zuweisungsoperator auf // ruft s2's Move-Zuweisungsoperator auf // und erstellt eine bitweise Kopie von n }; struct C : B { ~C() {} // Destruktor verhindert implizite Move-Zuweisung }; struct D : B { D() {} ~D() {} // Destruktor würde implizite Move-Zuweisung verhindern D& operator=(D&&) = default; // erzwingt trotzdem eine Move-Zuweisung }; int main() { A a1, a2; std::cout << "Versuche A von Rvalue-Temporary zu move-zuweisen\n"; a1 = f(A()); // Move-Zuweisung von Rvalue-Temporary std::cout << "Versuche A von Xvalue zu move-zuweisen\n"; a2 = std::move(a1); // Move-Zuweisung von Xvalue std::cout << "\nVersuche B zu move-zuweisen\n"; B b1, b2; std::cout << "Vor Move, b1.s = \"" << b1.s << "\"\n"; b2 = std::move(b1); // ruft implizite Move-Zuweisung auf std::cout << "Nach Move, b1.s = \"" << b1.s << "\"\n"; std::cout << "\nVersuche C zu move-zuweisen\n"; C c1, c2; c2 = std::move(c1); // ruft den Kopierzuweisungsoperator auf std::cout << "\nVersuche D zu move-zuweisen\n"; D d1, d2; d2 = std::move(d1); }
Ausgabe:
Versuche A von Rvalue-Temporary zu move-zuweisen move assigned Versuche A von Xvalue zu move-zuweisen move assigned Versuche B zu move-zuweisen Vor Move, b1.s = "test" move assigned Nach Move, b1.s = "" Versuche C zu move-zuweisen copy assigned Versuche D zu move-zuweisen move assigned
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äßige Move-Zuweisungsoperatoren
als gelöscht definiert sind, berücksichtigten keine mehrdimensionalen Array-Typen |
diese Typen berücksichtigen |
| CWG 1402 | C++11 |
ein standardmäßiger Move-Zuweisungsoperator, der
einen nicht-trivialen Kopierzuweisungsoperator aufrufen würde, war gelöscht; ein gelöschter standardmäßiger Move-Zuweisungsoperator nahm weiterhin an der Überladungsauflösung teil |
erlaubt Aufruf eines solchen
Kopierzuweisungsoperators; bei Überladungsauflösung ignoriert |
| CWG 1806 | C++11 |
Spezifikation für einen standardmäßigen Move-Zuweisungsoperator
mit virtueller Basisklasse fehlte |
hinzugefügt |
| CWG 2094 | C++11 |
ein flüchtiges Unterobjekt machte einen standardmäßigen
Move-Zuweisungsoperator nicht-trivial ( CWG issue 496 ) |
Trivialität nicht betroffen |
| CWG 2180 | C++11 |
ein standardmäßiger Move-Zuweisungsoperator für Klasse
T
war nicht als gelöscht definiert, wenn
T
abstrakt ist und
nicht move-zuweisbare direkte virtuelle Basisklassen hat |
der Operator ist in diesem Fall
als gelöscht definiert |
| CWG 2595 | C++20 |
ein Move-Zuweisungsoperator war nicht eligible, wenn es
einen anderen Move-Zuweisungsoperator gibt, der stärker constrained ist, aber seine associated constraints nicht erfüllt |
er kann in diesem Fall eligible sein |
| CWG 2690 | C++11 |
der implizit definierte Move-Zuweisungsoperator für
Union-Typen kopierte die Objektrepräsentation nicht |
sie kopieren die Objekt-
repräsentation |