Namensräume
Varianten
Aktionen

Move-Zuweisungsoperator

Von cppreference.com
< cpp‎ | Sprache
 
 
C++ Sprache
Allgemeine Themen
Kontrollfluss
Bedingte Ausführungsaussagen
if
Iterationsanweisungen (Schleifen)
for
Bereichs-for (C++11)
Sprunganweisungen
Funktionen
Funktionsdeklaration
Lambda-Funktionsausdruck
inline-Spezifizierer
Dynamische Ausnahmespezifikationen (bis C++17*)
noexcept-Spezifizierer (C++11)
Ausnahmen
Namensräume
Typen
Spezifizierer
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Speicherdauer-Spezifizierer
Initialisierung
Ausdrücke
Alternative Darstellungen
Literale
Boolesch - Ganzzahl - Gleitkommazahl
Zeichen - String - nullptr (C++11)
Benutzerdefinierte (C++11)
Dienstprogramme
Attribute (C++11)
Typen
typedef-Deklaration
Typalias-Deklaration (C++11)
Umwandlungen
Speicherzuweisung
Klassen
Klassenspezifische Funktionseigenschaften
explicit (C++11)
static

Spezielle Member-Funktionen
Templates
Sonstiges
 
 

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

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

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

  • T ein Literal-Typ ist, und
  • der für jede direkte Basisklasse-Subobjekt ausgewählte Zuweisungsoperator eine constexpr-Funktion ist, und
  • für jedes nicht-statische Datenmitglied von T, das von Klassentyp (oder einem Array davon) ist, der für dieses Mitglied ausgewählte Zuweisungsoperator eine constexpr-Funktion ist.
(seit C++14)
(bis C++23)

Der implizit definierte Move-Zuweisungsoperator für eine Klasse T ist constexpr.

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

  • T hat ein nicht-statisches Datenmitglied eines const-qualifizierten Nicht-Klassentyps (oder möglicherweise ein mehrdimensionales Array davon).
  • T hat ein nicht-statisches Datenmitglied eines Referenztyps.
  • T hat ein potenziell konstruiertes Subobjekt vom Klassentyp M (oder möglicherweise ein mehrdimensionales Array davon), so dass die Überladungsauflösung zur Ermittlung des Move-Zuweisungsoperators von M
  • 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);
  • T keine virtuellen Memberfunktionen hat;
  • T keine virtuellen Basisklassen hat;
  • der für jede direkte Basis von T ausgewählte Move-Zuweisungsoperator ist trivial;
  • der für jedes nicht-statische Klassenmitglied (oder Array eines Klassenmitglieds) von T ausgewä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 T
wurde nicht als gelöscht definiert, wenn T abstrakt war und
nicht-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

[bearbeiten] Siehe auch