Namespaces
Variants

Move assignment operator

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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

1) Deklaration eines Move-Zuweisungsoperators innerhalb der Klassendefinition.
2-4) Definition eines Move-Zuweisungsoperators innerhalb der Klassendefinition.
3) Der Move-Zuweisungsoperator ist explizit als Standard definiert.
4) Der Move-Zuweisungsoperator ist gelöscht.
5,6) Definition eines Move-Zuweisungsoperators außerhalb der Klassendefinition (die Klasse muss eine Deklaration enthalten (1) ).
6) Der Move-Zuweisungsoperator ist explizit als Standard deklariert.
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:

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 T ist constexpr , falls

  • T ein literal type ist, und
  • der für die Verschiebung jedes direkten Basisklassen-Subobjekts ausgewählte Zuweisungsoperator eine constexpr-Funktion ist, und
  • für jedes nicht-statische Datenelement von T , das von Klassentyp ist (oder eines Arrays davon), der für die Verschiebung dieses Elements ausgewählte Zuweisungsoperator eine constexpr-Funktion ist.
(seit C++14)
(bis C++23)

Der implizit definierte Verschiebungszuweisungsoperator für eine Klasse T ist constexpr .

(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:

  • T besitzt ein nicht-statisches Datenelement eines const-qualifizierten Nicht-Klassen-Typs (oder möglicherweise mehrdimensionales Array davon).
  • T besitzt ein nicht-statisches Datenelement eines Referenztyps.
  • T besitzt ein potenziell konstruiertes Subobjekt vom Klassentyp M (oder möglicherweise mehrdimensionales Array davon), sodass die Überladungsauflösung bei der Suche nach M '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);
  • T hat keine virtuellen Memberfunktionen;
  • T hat keine virtuellen Basisklassen;
  • Der Move-Zuweisungsoperator, der für jede direkte Basis von T ausgewählt wird, ist trivial;
  • Der Move-Zuweisungsoperator, der für jedes nicht-statische Klassentyp-Mitglied (oder Array von Klassentyp) von T ausgewä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

Siehe auch