Ein Move-Konstruktor der Klasse T
ist ein Nicht-Template-Konstruktor, dessen erster Parameter T&&, const T&&, volatile T&& oder const volatile T&& ist, und entweder gibt es keine anderen Parameter, oder die restlichen Parameter haben alle Standardwerte.
Inhalt
- 1 Syntax
- 2 Erläuterung
- 3 Implizit deklarierter move-Konstruktor
- 4 Gelöschter implizit deklarierter move-Konstruktor
- 5 Trivialer move-Konstruktor
- 6 Berechtigter move-Konstruktor
- 7 Implizit-definierter Move-Konstruktor
- 8 Hinweise
- 9 Beispiel
- 10 Fehlerberichte
- 11 Siehe auch
Syntax
class_name ( class_name && ) |
(1) | (seit C++11) | |||||||
class_name ( class_name && ) = default; |
(2) | (seit C++11) | |||||||
class_name ( class_name && ) = delete; |
(3) | (seit C++11) | |||||||
Wobei class_name die aktuelle Klasse (oder die aktuelle Instanziierung einer Klassenvorlage) benennen muss, oder, wenn sie im Namespace-Bereich oder in einer Friend-Deklaration deklariert wird, ein qualifizierter Klassenname sein muss.
Erläuterung
Der move-Konstruktor wird typischerweise aufgerufen, wenn ein Objekt (durch direkte Initialisierung oder Copy-Initialisierung) von rvalue (xvalue oder prvalue) (bis C++17)xvalue (seit C++17) des gleichen Typs initialisiert wird, einschließlich
- Initialisierung: T a = std::move(b); oder T a(std::move(b));, wobei
b
vom TypT
ist; - Funktionsargumentübergabe: f(std::move(a));, wobei
a
vom TypT
ist undf
void f(T t) ist; - Funktionsrückgabe: return a; innerhalb einer Funktion wie T f(), wobei
a
vom TypT
ist, die einen move-Konstruktor hat.
Wenn der Initialisierer ein prvalue ist, wird der move-Konstruktoraufruf oft optimiert (bis C++17)und nie gemacht (seit C++17), siehe copy elision.
Move-Konstruktoren „stehlen“ typischerweise die Ressourcen, die das Argument hält (z.z. B. Zeiger auf dynamisch zugewiesene Objekte, Dateideskriptoren, TCP-Sockets, E/A-Streams, laufende Threads usw.), anstatt Kopien von ihnen zu erstellen, und lassen das Argument in einem gültigen, aber ansonsten unbestimmten Zustand. Zum Beispiel kann das Verschieben von einem std::string oder einem std::vector dazu führen, dass das Argument leer bleibt. Auf dieses Verhalten sollte man sich jedoch nicht verlassen. Für einige Typen, wie std::unique_ptr, ist der „moved-from“-Zustand vollständig spezifiziert.
Implizit deklarierter move-Konstruktor
Wenn für einen Klassentyp (struct, class oder union) keine benutzerdefinierten move-Konstruktoren vorgesehen sind und alle der folgenden Bedingungen erfüllt sind:
- es gibt keine benutzerdeklarierten Kopierkonstruktoren;
- es gibt keine benutzerdeklarierten Kopierzuweisungsoperatoren;
- es gibt keine benutzerdeklarierten Verschiebezuweisungsoperatoren;
- es gibt keinen benutzerdeklarierten Destruktor.
dann wird der Compiler einen move-Konstruktor als nicht-explizites inline public
Mitglied seiner Klasse mit der Signatur T::T(T&&)
deklarieren.
Eine Klasse kann mehrere Move-Konstruktoren haben, z.B. sowohl T::T(const T&&) als auch T::T(T&&). Wenn einige benutzerdefinierte Move-Konstruktoren vorhanden sind, kann der Benutzer dennoch die Erzeugung des implizit deklarierten Move-Konstruktors mit dem Schlüsselwort default
erzwingen.
Der implizit deklarierte (oder bei seiner ersten Deklaration voreingestellte) move-Konstruktor hat eine Ausnahmespezifikation, wie sie in der dynamischen Ausnahmespezifikation (bis C++17)Ausnahmespezifikation (seit C++17)
Gelöschter implizit deklarierter move-Konstruktor
Der implizit deklarierte oder bei seiner ersten Deklaration voreingestellte move-Konstruktor für die Klasse T
wird als gelöscht definiert, wenn eine der folgenden Bedingungen erfüllt ist:
-
T
hat nichtstatische Datenmitglieder, die nicht verschoben werden können (haben gelöschte, unzugängliche oder mehrdeutige Verschiebekonstruktoren); -
T
hat eine direkte oder virtuelle Basisklasse, die nicht verschoben werden kann (hat gelöschte, unzugängliche oder mehrdeutige Verschiebekonstruktoren); -
T
hat eine direkte oder virtuelle Basisklasse mit einem gelöschten oder unzugänglichen Destruktor; -
T
ist eine unionähnliche Klasse und hat ein variantes Mitglied mit einem nicht-trivialen Move-Konstruktor.
Ein voreingestellter move-Konstruktor, der gelöscht ist, wird von der Überlastauflösung ignoriert (sonst würde er die Kopierinitialisierung von rvalue verhindern).
Trivialer move-Konstruktor
Der move-Konstruktor für die Klasse T
ist trivial, wenn alle der folgenden Punkte zutreffen:
- er ist nicht benutzerdefiniert (d.h., er ist implizit definiert oder voreingestellt);
-
T
hat keine virtuellen Mitgliedsfunktionen; -
T
hat keine virtuellen Basisklassen; - der Konstruktor, der für jede direkte Basis von
T
ausgewählt wird, ist trivial; - der Konstruktor, der für jedes nicht-statische Klassentyp- (oder Array von Klassentyp-) Mitglied von
T
ausgewählt wird, ist trivial.
Ein trivialer move-Konstruktor ist ein Konstruktor, der die gleiche Aktion wie der triviale copy-Konstruktor durchführt, d.h. eine Kopie der Objektrepräsentation wie bei std::memmove erstellt. Alle mit der Sprache C kompatiblen Datentypen (POD-Typen) sind trivial verschiebbar.
Zulässiger Verschiebekonstruktor
Ein Verschiebekonstruktor ist zulässig, wenn er nicht gelöscht wird. |
(bis C++20) |
Ein Move-Konstruktor kommt in Frage, wenn
|
(seit C++20) |
Die Trivialität der in Frage kommenden Move-Konstruktoren bestimmt, ob die Klasse ein Typ mit impliziter Lebensdauer ist, und ob die Klasse ein trivial kopierbarer Typ ist.
Implizit definierter move-Konstruktor
Wenn der implizit deklarierte move-Konstruktor weder gelöscht noch trivial ist, wird er vom Compiler definiert (d.h. ein Funktionskörper wird generiert und kompiliert), wenn er odr-verwendet oder für die Konstantenauswertung benötigt wird. Bei Union-Typen kopiert der implizit definierte move-Konstruktor die Objektrepräsentation (wie bei std::memmove). Für Nicht-Union-Klassentypen (class und struct) führt der move-Konstruktor eine vollständige mitgliederweise Verschiebung der Basen und nicht-statischen Mitglieder des Objekts in ihrer Initialisierungsreihenfolge durch, indem er eine direkte Initialisierung mit einem xvalue-Argument verwendet. Wenn dies die Anforderungen eines constexpr-Konstruktors erfüllt, ist der generierte move-Konstruktor constexpr
.
Hinweise
Um die starke Ausnahmegarantie zu ermöglichen, sollten benutzerdefinierte move-Konstruktoren keine Ausnahmen werfen. Zum Beispiel verlässt sich std::vector auf std::move_if_noexcept, um zwischen move und copy zu wählen, wenn die Elemente verschoben werden müssen.
Wenn sowohl copy- als auch move-Konstruktoren bereitgestellt werden und keine anderen Konstruktoren in Frage kommen, wählt die Überladungsauflösung den move-Konstruktor, wenn das Argument ein rWert desselben Typs ist (ein xWert wie das Ergebnis von std::move oder ein prWert wie ein namenloses temporäres Element (bis C++17)), und wählt den copy-Konstruktor, wenn das Argument ein lWert ist (benanntes Objekt oder eine Funktion/ein Operator, der eine lWert-Referenz zurückgibt). Wenn nur der Kopierkonstruktor zur Verfügung gestellt wird, wählen alle Argumentkategorien ihn aus (solange er eine Referenz auf const annimmt, da rvalues an const-Referenzen binden können), was das Kopieren zum Fallback für das Verschieben macht, wenn Verschieben nicht verfügbar ist.
Ein Konstruktor wird als ‚move constructor‘ bezeichnet, wenn er eine rvalue-Referenz als Parameter annimmt. Er ist nicht verpflichtet, irgendetwas zu verschieben, die Klasse muss keine zu verschiebende Ressource haben, und ein ‚move constructor‘ ist möglicherweise nicht in der Lage, eine Ressource zu verschieben, wie in dem zulässigen (aber vielleicht nicht sinnvollen) Fall, dass der Parameter eine const rvalue-Referenz ist (const T&&).
Beispiel
#include <string>#include <iostream>#include <iomanip>#include <utility> struct A{ std::string s; int k; A() : s("test"), k(-1) { } A(const A& o) : s(o.s), k(o.k) { std::cout << "move failed!\n"; } A(A&& o) noexcept : s(std::move(o.s)), // explicit move of a member of class type k(std::exchange(o.k, 0)) // explicit move of a member of non-class type { }}; A f(A a){ return a;} struct B : A{ std::string s2; int n; // implicit move constructor B::(B&&) // calls A's move constructor // calls s2's move constructor // and makes a bitwise copy of n}; struct C : B{ ~C() { } // destructor prevents implicit move constructor C::(C&&)}; struct D : B{ D() { } ~D() { } // destructor would prevent implicit move constructor D::(D&&) D(D&&) = default; // forces a move constructor anyway}; int main(){ std::cout << "Trying to move A\n"; A a1 = f(A()); // return by value move-constructs the target from the function parameter std::cout << "Before move, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; A a2 = std::move(a1); // move-constructs from xvalue std::cout << "After move, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; std::cout << "Trying to move B\n"; B b1; std::cout << "Before move, b1.s = " << std::quoted(b1.s) << "\n"; B b2 = std::move(b1); // calls implicit move constructor std::cout << "After move, b1.s = " << std::quoted(b1.s) << "\n"; std::cout << "Trying to move C\n"; C c1; C c2 = std::move(c1); // calls copy constructor std::cout << "Trying to move D\n"; D d1; D d2 = std::move(d1);}
Ausgabe:
Trying to move ABefore move, a1.s = "test" a1.k = -1After move, a1.s = "" a1.k = 0Trying to move BBefore move, b1.s = "test"After move, b1.s = ""Trying to move Cmove failed!Trying to move D
Mängelberichte
Die folgenden verhaltensverändernden Mängelberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
DR | Angewandt auf | Verhalten wie veröffentlicht | Korrektes Verhalten |
---|---|---|---|
CWG 1402 | C++11 | Ein voreingestellter Move-Konstruktor, der einen nicht-trivialen Copy-Konstruktor aufrufen würde , wurde gelöscht; ein standardmäßiger Move-Konstruktor, der gelöscht wurde, nahm noch an der Überlastauflösung teil |
erlaubt den Aufruf eines solchen Kopierkonstruktors; in der Überladungsauflösung ignoriert |
CWG 2094 | C++11 | ein flüchtiges Unterobjekt aus einem defaulted move-Konstruktor nichttrivial (CWG496) |
Trivialität nicht betroffen |
Siehe auch
- Konstruktor umwandeln
- Zuweisung kopieren
- Konstruktor kopieren
- Kopierelision
- Standardkonstruktor
- Destruktor
- explizite
- Initialisierung
- Aggregatinitialisierung
- Konstanteninitialisierung
- Kopierinitialisierung
- Standardinitialisierung
- Direkte Initialisierung
- Listeninitialisierung
- Listeninitialisierung
- Referenzinitialisierung
- Wertinitialisierung
- Nullinitialisierung
- Zuweisung verschieben
- neu