Move constructors
Ein Move-Konstruktor ist ein Konstruktor , der mit einem Argument desselben Klassentyps aufgerufen werden kann und den Inhalt des Arguments kopiert, wobei möglicherweise das Argument verändert wird.
Inhaltsverzeichnis |
Syntax
class-name
(
parameter-list
);
|
(1) | ||||||||
class-name
(
parameter-list
)
function-body
|
(2) | ||||||||
class-name
(
single-parameter-list
) = default;
|
(3) | ||||||||
class-name
(
parameter-list
) = delete;
|
(4) | ||||||||
class-name
::
class-name
(
parameter-list
)
function-body
|
(5) | ||||||||
class-name
::
class-name
(
single-parameter-list
) = default;
|
(6) | ||||||||
`, `
` Tags oder C++-spezifischen Begriffen stehen, wurde gemäß den Anforderungen keine Übersetzung vorgenommen. Die numerischen Kennzeichnungen (1)-(6) wurden ebenfalls beibehalten, da sie Teil der technischen Dokumentationsstruktur sind.
| class-name | - | die Klasse, deren Move-Konstruktor deklariert wird |
| parameter-list | - |
eine nicht-leere
Parameterliste
, die alle folgenden Bedingungen erfüllt:
|
| single-parameter-list | - | eine Parameterliste mit nur einem Parameter, der vom Typ T && , const T && , volatile T && oder const volatile T && ist und kein Standardargument besitzt |
| function-body | - | der Funktionsrumpf des Move-Konstruktors |
Erklärung
struct X { X(X&& other); // Move-Konstruktor // X(X other); // Fehler: Falscher Parametertyp }; union Y { Y(Y&& other, int num = 1); // Move-Konstruktor mit mehreren Parametern // Y(Y&& other, int num); // Fehler: `num` hat kein Standardargument };
Der Move-Konstruktor wird typischerweise aufgerufen, wenn ein Objekt initialisiert wird (durch Direct-Initialization oder Copy-Initialization ) aus einem rvalue (xvalue oder prvalue) (bis C++17) xvalue (seit C++17) desselben Typs, einschließlich
-
Initialisierung:
T a
=
std
::
move
(
b
)
;
oder
T a
(
std
::
move
(
b
)
)
;
, wobei
b
vom Typ
Tist; -
Funktionsargumentübergabe:
f
(
std
::
move
(
a
)
)
;
, wobei
a
vom Typ
Tist und f als void f ( T t ) deklariert ist; -
Funktionsrückgabe:
return
a
;
innerhalb einer Funktion wie
T f
(
)
, wobei
a
vom Typ
Tist und einen Move-Konstruktor besitzt.
Wenn der Initialisierer ein Prvalue ist, wird der Aufruf des Move-Konstruktors oft wegoptimiert (bis C++17) nie durchgeführt (seit C++17) , siehe Copy Elision .
Move-Konstruktoren übertragen typischerweise die Ressourcen, die vom Argument gehalten werden (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-Konstruktoren die Lebensdauer des Arguments nicht ändern, wird der Destruktor typischerweise zu einem späteren Zeitpunkt für das Argument aufgerufen. Zum Beispiel kann das Verschieben von einem std::string oder von einem std::vector dazu führen, dass das Argument leer zurückbleibt. Für einige Typen, wie std::unique_ptr , ist der Zustand nach dem Verschieben vollständig spezifiziert.
Implizit deklarierter Move-Konstruktor
Wenn keine benutzerdefinierten Move-Konstruktoren für einen Klassentyp bereitgestellt werden und alle folgenden Bedingungen zutreffen:
- es gibt keine benutzerdeklarierten copy constructors ;
- es gibt keine benutzerdeklarierten copy assignment operators ;
- es gibt keine benutzerdeklarierten move assignment operators ;
- es gibt keinen benutzerdeklarierten destructor .
Dann wird der Compiler einen Move-Konstruktor als nicht- explicit inline public Member 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 benutzerdefinierte Move-Konstruktoren vorhanden sind, kann der Benutzer die Generierung des implizit deklarierten Move-Konstruktors mit dem Schlüsselwort default erzwingen.
Der implizit deklarierte (oder bei seiner ersten Deklaration standardmäßig definierte) Move-Konstruktor hat eine Ausnahmespezifikation, wie beschrieben in dynamischer Ausnahmespezifikation (bis C++17) noexcept-Spezifikation (seit C++17) .
Implizit definierter Move-Konstruktor
Wenn der implizit deklarierte Move-Konstruktor weder gelöscht noch trivial ist, wird er (d.h. ein Funktionskörper wird generiert und kompiliert) vom Compiler definiert, falls ODR-use oder needed for constant evaluation vorliegt. Für Union-Typen kopiert der implizit definierte Move-Konstruktor die Objektrepräsentation (wie durch std::memmove ). Für Nicht-Union-Klassentypen führt der Move-Konstruktor eine vollständige memberweise Verschiebung der direkten Basis-Subobjekte und Member-Subobjekte des Objekts in ihrer Initialisierungsreihenfolge durch, unter Verwendung von Direktinitialisierung mit einem xvalue -Argument. Für jeden nicht-statischen Datenelement vom Referenztyp bindet der Move-Konstruktor die Referenz an dasselbe Objekt oder dieselbe Funktion, an die die Quellreferenz gebunden ist.
Wenn dies die Anforderungen eines
constexpr
Konstruktors
(bis C++23)
constexpr
Funktion
(seit C++23)
erfüllt, ist der generierte Move-Konstruktor
constexpr
.
Gelöschter Move-Konstruktor
Der implizit deklarierte oder explizit als Standard definierte Move-Konstruktor für Klasse
T
wird als gelöscht definiert, falls
T
ein
potenziell konstruiertes Subobjekt
vom Klassentyp
M
(oder möglicherweise mehrdimensionales Array davon) besitzt, sodass
-
Mverfügt über einen Destruktor, der gelöscht oder vom Kopierkonstruktor aus unzugänglich ist, oder -
die Überladungsauflösung, wie sie angewendet wird, um
M's Move-Konstruktor zu finden
-
- 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 solcher Konstruktor wird von der Überlagerungsauflösung ignoriert (ansonsten würde er die Kopierinitialisierung von Rvalue-Werten verhindern).
Trivialer Move-Konstruktor
Der Move-Konstruktor 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 standardmäßig);
-
That keine virtuellen Memberfunktionen; -
That keine virtuellen Basisklassen; -
der für jede direkte Basis von
Tausgewählte Move-Konstruktor ist trivial; -
der für jedes nicht-statische Klassentyp-Member (oder Array von Klassentyp) von
Tausgewählte Move-Konstruktor ist trivial.
Ein trivialer Move-Konstruktor ist ein Konstruktor, der dieselbe Aktion ausführt wie der triviale Kopierkonstruktor, 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.
Berechtigter Move-Konstruktor
|
Ein Move-Konstruktor ist geeignet, wenn er nicht gelöscht ist. |
(until C++20) |
|
Ein Move-Konstruktor ist geeignet, wenn alle folgenden Bedingungen erfüllt sind:
|
(since C++20) |
Die Trivialität von berechtigten Move-Konstruktoren bestimmt, ob die Klasse ein Implicit-lifetime-Typ ist, und ob die Klasse ein trivially copyable type ist.
Hinweise
Um die strong exception guarantee zu ermöglichen, sollten benutzerdefinierte Move-Konstruktoren keine Ausnahmen werfen. Beispielsweise verlässt sich std::vector auf std::move_if_noexcept , um zwischen Move und Copy zu wählen, wenn Elemente neu positioniert werden müssen.
Wenn sowohl Kopier- 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 rvalue desselben Typs ist (ein xvalue wie das Ergebnis von std::move oder ein prvalue wie ein namenloses Temporärobjekt (bis C++17) ), und wählt den Kopierkonstruktor, wenn das Argument ein lvalue ist (benanntes Objekt oder eine Funktion/ein Operator, die eine lvalue-Referenz zurückgibt). Wenn nur der Kopierkonstruktor bereitgestellt wird, wählen alle Argumentkategorien diesen (sofern er eine Referenz auf const akzeptiert, da rvalues an const-Referenzen binden können), was das Kopieren zum Fallback für das Verschieben macht, wenn Verschieben nicht verfügbar ist.
Beispiel
#include <iomanip> #include <iostream> #include <string> #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)), // explizites Verschieben eines Members vom Klassentyp k(std::exchange(o.k, 0)) // explizites Verschieben eines Members vom Nicht-Klassentyp {} }; A f(A a) { return a; } struct B : A { std::string s2; int n; // impliziter Move-Konstruktor B::(B&&) // ruft A's Move-Konstruktor auf // ruft s2's Move-Konstruktor auf // und erstellt eine bitweise Kopie von n }; struct C : B { ~C() {} // Destruktor verhindert impliziten Move-Konstruktor C::(C&&) }; struct D : B { D() {} ~D() {} // Destruktor würde impliziten Move-Konstruktor D::(D&&) verhindern D(D&&) = default; // erzwingt trotzdem einen Move-Konstruktor }; int main() { std::cout << "Versuche A zu verschieben\n"; A a1 = f(A()); // Rückgabe per Wert move-konstruiert das Ziel // vom Funktionsparameter std::cout << "Vor dem Verschieben, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; A a2 = std::move(a1); // move-konstruiert von xvalue std::cout << "Nach dem Verschieben, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; std::cout << "\nVersuche B zu verschieben\n"; B b1; std::cout << "Vor dem Verschieben, b1.s = " << std::quoted(b1.s) << "\n"; B b2 = std::move(b1); // ruft impliziten Move-Konstruktor auf std::cout << "Nach dem Verschieben, b1.s = " << std::quoted(b1.s) << "\n"; std::cout << "\nVersuche C zu verschieben\n"; C c1; C c2 = std::move(c1); // ruft Kopierkonstruktor auf std::cout << "\nVersuche D zu verschieben\n"; D d1; D d2 = std::move(d1); }
Ausgabe:
Versuche A zu verschieben Vor dem Verschieben, a1.s = "test" a1.k = -1 Nach dem Verschieben, a1.s = "" a1.k = 0 Versuche B zu verschieben Vor dem Verschieben, b1.s = "test" Nach dem Verschieben, b1.s = "" Versuche C zu verschieben move failed! Versuche D zu verschieben
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-Konstruktoren
als gelöscht definiert sind, berücksichtigten keine mehrdimensionalen Array-Typen |
diese Typen berücksichtigen |
| CWG 1402 | C++11 |
ein standardmäßiger Move-Konstruktor, der einen
nicht-trivialen Copy-Konstruktor aufrufen würde, war als gelöscht definiert; ein gelöschter standardmäßiger Move-Konstruktor nahm weiterhin an der Überladungsauflösung teil |
erlaubt Aufruf eines solchen Copy-
Konstruktors; wird bei der Überladungs- auflösung ignoriert |
| CWG 1491 | C++11 |
ein standardmäßiger Move-Konstruktor einer Klasse mit einem nicht-statischen Daten-
member vom Rvalue-Referenztyp war als gelöscht definiert |
in diesem Fall nicht gelöscht |
| CWG 2094 | C++11 |
ein volatile-Subobjekt machte einen standardmäßigen
Move-Konstruktor nicht-trivial ( CWG issue 496 ) |
Trivialität nicht betroffen |
| CWG 2595 | C++20 |
ein Move-Konstruktor war nicht berechtigt, wenn es
einen anderen Move-Konstruktor gibt, der stärker eingeschränkt ist aber seine zugehörigen Constraints nicht erfüllt |
er kann in diesem Fall berechtigt sein |