Namespaces
Variants

Move constructors

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-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)
**Hinweis:** Da alle relevanten Textelemente innerhalb von ` `, ` ` 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:
  • gegeben den Klassentyp als T , ist der erste Parameter vom Typ T && , const T && , volatile T && oder const volatile T && , und
  • entweder gibt es keine weiteren Parameter, oder alle anderen Parameter haben Standardargumente
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

1) Deklaration eines Move-Konstruktors innerhalb der Klassendefinition.
2-4) Definition eines Move-Konstruktors innerhalb der Klassendefinition.
3) Der Move-Konstruktor ist explizit defaulted.
4) Der Move-Konstruktor ist gelöscht.
5,6) Definition eines Move-Konstruktors außerhalb der Klassendefinition (die Klasse muss eine Deklaration enthalten (1) ).
6) Der Move-Konstruktor ist explizit als Standard deklariert.
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 T ist;
  • Funktionsargumentübergabe: f ( std :: move ( a ) ) ; , wobei a vom Typ T ist und f als void f ( T t ) deklariert ist;
  • Funktionsrückgabe: return a ; innerhalb einer Funktion wie T f ( ) , wobei a vom Typ T ist 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:

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

  • M verfü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);
  • T hat keine virtuellen Memberfunktionen;
  • T hat keine virtuellen Basisklassen;
  • der für jede direkte Basis von T ausgewählte Move-Konstruktor ist trivial;
  • der für jedes nicht-statische Klassentyp-Member (oder Array von Klassentyp) von T ausgewä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

Siehe auch