Namensräume
Varianten
Aktionen

Move-Konstruktoren

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
Standardkonstruktor
Kopierkonstruktor
Move-Konstruktor (C++11)
Templates
Sonstiges
 
 

Ein Move-Konstruktor ist ein Konstruktor, der mit einem Argument desselben Klassentyps aufgerufen werden kann und den Inhalt des Arguments kopiert, wobei das Argument möglicherweise mutiert wird.

Inhalt

[bearbeiten] Syntax

Klassenname (Parameterliste ); (1)
Klassenname (Parameterliste ) Funktionsrumpf (2)
Klassenname (Einzelparameterliste ) = default; (3)
Klassenname (Parameterliste ) = delete; (4)
Klassenname ::Klassenname (Parameterliste ) Funktionsrumpf (5)
Klassenname ::Klassenname (Einzelparameterliste ) = default; (6)
Klassenname - die Klasse, deren Move-Konstruktor deklariert wird
parameter-liste - 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 anderen Parameter oder alle anderen Parameter haben Standardargumente
Einzelparameterliste - 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-Konstruktors

[bearbeiten] Erklärung

1) Deklaration eines Move-Konstruktors innerhalb der Klassendefinition.
2-4) Definition eines Move-Konstruktors innerhalb der Klassendefinition.
3) Der Move-Konstruktor wird explizit standardmäßig festgelegt.
4) Der Move-Konstruktor wird gelöscht.
5,6) Definition eines Move-Konstruktors außerhalb der Klassendefinition (die Klasse muss eine Deklaration (1) enthalten).
6) Der Move-Konstruktor wird explizit standardmäßig festgelegt.
struct X
{
    X(X&& other); // move constructor
//  X(X other);   // Error: incorrect parameter type
};
 
union Y
{
    Y(Y&& other, int num = 1); // move constructor with multiple parameters
//  Y(Y&& other, int num);     // Error: `num` has no default argument
};

Der Move-Konstruktor wird typischerweise aufgerufen, wenn ein Objekt initialisiert wird (durch direkte Initialisierung oder Kopierinitialisierung) aus 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; in einer Funktion wie T f(), wobei a vom Typ T ist, der einen Move-Konstruktor besitzt.

Wenn der Initialisierer ein prvalue ist, wird der Aufruf des Move-Konstruktors oft optimiert(bis C++17)nie gemacht(seit C++17), siehe Kopier-Elision.

Move-Konstruktoren übertragen typischerweise die vom Argument gehaltenen Ressourcen (z.B. Zeiger auf dynamisch allokierte Objekte, Dateideskriptoren, TCP-Sockets, Thread-Handles usw.), anstatt Kopien davon zu erstellen, und hinterlassen das Argument in einem gültigen, aber ansonsten unbestimmten Zustand. Da der Move-Konstruktor die Lebensdauer des Arguments nicht ändert, wird der Destruktor typischerweise zu einem späteren Zeitpunkt für das Argument aufgerufen. Beispielsweise kann das Verschieben eines std::string oder eines std::vector dazu führen, dass das Argument leer bleibt. Für einige Typen, wie z.B. std::unique_ptr, ist der verschobene Zustand vollständig spezifiziert.

[bearbeiten] Implizit deklarierter Move-Konstruktor

Wenn für einen Klassentyp keine benutzerdefinierten Move-Konstruktoren bereitgestellt werden und alle der folgenden Bedingungen erfüllt sind

Dann deklariert der Compiler einen Move-Konstruktor als nicht-explizites inline public Mitglied seiner Klasse mit der Signatur T::T(T&&).

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 die Generierung des implizit deklarierten Move-Konstruktors mit dem Schlüsselwort default erzwingen.

Der implizit deklarierte (oder bei seiner ersten Deklaration standardmäßig festgelegte) Move-Konstruktor hat eine Ausnahmespezifikation wie in dynamische Ausnahmespezifikation(bis C++17)noexcept-Spezifikation(seit C++17) beschrieben.

[bearbeiten] Implizit definierter Move-Konstruktor

Wenn der implizit deklarierte Move-Konstruktor weder gelöscht noch trivial ist, wird er (d.h. ein Funktionsrumpf wird generiert und kompiliert) vom Compiler definiert, wenn er durch ODR-benutzt oder für die konstante Auswertung benötigt wird. Für Union-Typen kopiert der implizit definierte Move-Konstruktor die Objekt-Repräsentation (wie bei std::memmove). Für Nicht-Union-Klassentypen führt der Move-Konstruktor eine vollständige elementweise Verschiebung der direkten Basis-Subobjekte und Mitgliedssubobjekte des Objekts in ihrer Initialisierungsreihenfolge durch, mittels direkter Initialisierung mit einem xvalue-Argument. Für jedes nicht-statische Datenmitglied eines Referenztyps 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.

[bearbeiten] Gelöschter Move-Konstruktor

Der implizit deklarierte oder explizit standardmäßig festgelegte Move-Konstruktor für die Klasse T wird als gelöscht definiert, wenn T ein potenziell zu konstruierendes Subobjekt vom Klassentyp M hat (oder möglicherweise ein mehrdimensionales Array davon), so dass

  • M einen Destruktor hat, der gelöscht oder vom Kopierkonstruktor aus nicht zugänglich ist, oder
  • die Überladungsauflösung zur Ermittlung des Move-Konstruktors von M
  • nicht zu einem benutzbaren Kandidaten führt, oder
  • im Falle des Subobjekts als Varianten-Mitglied eine nicht-triviale Funktion auswählt.

Ein solcher Konstruktor wird von der Überladungsauflösung ignoriert (andernfalls würde er die Kopierinitialisierung aus einem rvalue verhindern).

[bearbeiten] Trivialer Move-Konstruktor

Der Move-Konstruktor für die Klasse T ist trivial, wenn alle der folgenden Bedingungen erfüllt sind

  • er nicht benutzerdefiniert ist (d.h., er ist implizit definiert oder standardmäßig festgelegt);
  • T keine virtuellen Memberfunktionen hat;
  • T keine virtuellen Basisklassen hat;
  • der für jede direkte Basis von T ausgewählte Move-Konstruktor trivial ist;
  • der für jedes nicht-statische Mitglied vom Klassentyp (oder Array von Klassentyp) von T ausgewählte Move-Konstruktor trivial ist.

Ein trivialer Move-Konstruktor ist ein Konstruktor, der die gleiche Aktion wie der triviale Kopierkonstruktor ausführt, d.h. eine Kopie der Objekt-Repräsentation erstellt, als ob durch std::memmove. Alle mit der C-Sprache kompatiblen Datentypen sind trivial verschiebbar.

[bearbeiten] Berechtigter Move-Konstruktor

Ein Move-Konstruktor ist berechtigt, wenn er nicht gelöscht ist.

(bis C++20)

Ein Move-Konstruktor ist berechtigt, wenn alle folgenden Bedingungen erfüllt sind

(seit C++20)

Die Trivialität von berechtigten Move-Konstruktoren bestimmt, ob die Klasse ein implizit-Lebenszeit-Typ ist und ob die Klasse ein trivial kopierbarer Typ ist.

[bearbeiten] Hinweise

Um den starken Ausnahmegarantie zu ermöglichen, sollten benutzerdefinierte Move-Konstruktoren keine Ausnahmen auslösen. Zum Beispiel verwendet std::vector std::move_if_noexcept, um zwischen Verschieben und Kopieren zu wählen, wenn Elemente neu positioniert werden müssen.

Wenn sowohl Kopier- als auch Move-Konstruktoren bereitgestellt werden und keine anderen Konstruktoren möglich sind, 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äres Objekt(bis C++17)), und wählt den Kopierkonstruktor, wenn das Argument ein lvalue ist (benanntes Objekt oder eine Funktion/ein Operator, der eine lvalue-Referenz zurückgibt). Wenn nur der Kopierkonstruktor bereitgestellt wird, wählen alle Argumentkategorien ihn aus (solange er eine Referenz auf `const` nimmt, da rvalues an const-Referenzen gebunden werden können), was das Kopieren zum Fallback für das Verschieben macht, wenn das Verschieben nicht verfügbar ist.

[bearbeiten] 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)),       // 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 << "\nTrying 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 << "\nTrying to move C\n";
    C c1;
    C c2 = std::move(c1); // calls copy constructor
 
    std::cout << "\nTrying to move D\n";
    D d1;
    D d2 = std::move(d1);
}

Ausgabe

Trying to move A
Before move, a1.s = "test" a1.k = -1
After move, a1.s = "" a1.k = 0
 
Trying to move B
Before move, b1.s = "test"
After move, b1.s = ""
 
Trying to move C
move failed!
 
Trying to move D

[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 festgelegte Move-Konstruktoren
als gelöscht definiert werden, berücksichtigten keine mehrdimensionalen Array-Typen
diese Typen berücksichtigen
CWG 1402 C++11 ein standardmäßig festgelegter Move-Konstruktor, der
einen nicht-trivialen Kopierkonstruktor aufrufen würde, wurde als
gelöscht definiert; ein standardmäßig festgelegter Move-Konstruktor, der
gelöscht ist, nahm weiterhin an der Überladungsauflösung teil
ermöglicht den Aufruf eines solchen Kopierens
Konstruktor; wurde ignoriert
in der Überladungsauflösung
CWG 1491 C++11 ein standardmäßig festgelegter Move-Konstruktor einer Klasse mit einem nicht-statischen Daten-
Mitglied mit rvalue-Referenztyp wurde als gelöscht definiert
in diesem Fall nicht gelöscht
CWG 2094 C++11 ein volatiles Subobjekt machte einen standardmäßig
festgelegten Move-Konstruktor nicht-trivial (CWG-Problem 496)
Trivialität nicht betroffen
CWG 2595 C++20 ein Move-Konstruktor war nicht berechtigt, wenn es
einen anderen Move-Konstruktor gab, der eingeschränkter war
aber seine zugehörigen Constraints nicht erfüllte
er kann in diesem Fall berechtigt sein

[bearbeiten] Siehe auch