Een move constructor van klasse T
is een niet-template constructor waarvan de eerste parameter T&&, const T&&, volatile T&&, of const volatile T&& is, en ofwel zijn er geen andere parameters, of de rest van de parameters hebben allemaal standaardwaarden.
Inhoud
- 1 Syntax
- 2 Uitleg
- 3 Impliciet gedefinieerde move constructor
- 4 Verwijderde impliciet gedefinieerde move constructor
- 5 Triviale move constructor
- 6 Eligible move constructor
- 7 Impliciet-gedefinieerde move constructor
- 8 Opmerkingen
- 9 Voorbeeld
- 10 Defectmeldingen
- 11 Zie ook
Syntax
class_name ( class_name && ) |
(1) | (sinds C++11) | |||||||
class_name ( class_name && ) = standaard; |
(2) | (sinds C++11) | |||||||
class_name ( class_name && ) = delete; |
(3) | (sinds C++11) | |||||||
Waarbij class_name de naam moet zijn van de huidige klasse (of de huidige instantiatie van een klassemal), of, wanneer deze wordt gedeclareerd op namespace scope of in een friend-declaratie, een gekwalificeerde klassenaam moet zijn.
Uitleg
De move constructor wordt typisch aangeroepen wanneer een object wordt geïnitialiseerd (door directe-initialisatie of kopie-initialisatie) van rvalue (xvalue of prvalue) (tot C++17)xvalue (sinds C++17) van hetzelfde type, inclusief
- initialisatie: T a = std::move(b); of T a(std::move(b));, waarbij
b
van het typeT
is; - functie argument passing: f(std::move(a));, waarbij
a
van het typeT
is enf
void f(T t) is; - functie return: return a; binnen een functie zoals T f(), waarbij
a
van het typeT
is die een move constructor heeft.
Wanneer de initializer een prvalue is, wordt de aanroep van de move constructor vaak geoptimaliseerd (tot C++17)nooit gemaakt (sinds C++17), zie copy elision.
Move constructors “stelen” meestal de bronnen die het argument bevat (bijv.b.v. pointers naar dynamisch-toegewezen objecten, file descriptors, TCP sockets, I/O streams, running threads, etc.) in plaats van er kopieën van te maken, en laten het argument in een geldige maar verder onbepaalde staat. Bijvoorbeeld, verhuizen van een std::string of van een std::vector kan resulteren in het leeg laten van het argument. Op dit gedrag mag echter niet vertrouwd worden. Voor sommige types, zoals std::unique_ptr, is de status van het argument volledig gespecificeerd.
Implicly-declared move constructor
Als er geen door de gebruiker gedefinieerde move constructors zijn voorzien voor een klassetype (struct, class, of union), en al het volgende is waar:
- er zijn geen door de gebruiker gedeclared copy constructors;
- er zijn geen door de gebruiker gedeclared copy assignment operators;
- er zijn geen door de gebruiker gedeclared move assignment operators;
- er is geen door de gebruiker gedeclared destructor.
dan zal de compiler een move constructor declareren als een niet-expliciet inline public
lid van zijn klasse met de signatuur T::T(T&&)
.
Een klasse kan meerdere move constructors hebben, bijvoorbeeld zowel T::T(const T&&) als T::T(T&&). Als er enkele door de gebruiker gedefinieerde move constructors aanwezig zijn, kan de gebruiker nog steeds het genereren van de impliciet gedeclareerde move constructor forceren met het sleutelwoord default
.
De impliciet-declared (of defaulted bij de eerste declaratie) move constructor heeft een exception specificatie zoals beschreven in dynamic exception specification (tot C++17)exception specification (sinds C++17)
Deleted implicitly-declared move constructor
De impliciet-declared of defaulted move constructor voor klasse T
is gedefinieerd als verwijderd als een van de volgende waar is:
-
T
heeft niet-statische gegevensleden die niet kunnen worden verplaatst (hebben verwijderde, ontoegankelijke of dubbelzinnige verplaatsingsconstructors); -
T
heeft een directe of virtuele basisklasse die niet kan worden verplaatst (heeft verwijderde, ontoegankelijke of dubbelzinnige verplaatsingsconstructors); -
T
heeft een directe of virtuele basisklasse met een verwijderde of ontoegankelijke destructor; -
T
is een union-achtige klasse en heeft een variabel lid met een niet-triviale verplaatsingsconstructor.
Een defaulted move constructor die verwijderd is, wordt genegeerd door overload resolution (anders zou het copy-initialization van rvalue voorkomen).
Triviale move constructor
De move constructor voor klasse T
is triviaal als al het volgende waar is:
- hij is niet door de gebruiker voorzien (dat wil zeggen, hij is impliciet gedefinieerd of defaulted);
-
T
heeft geen virtuele lidfuncties; -
T
heeft geen virtuele basisklassen; - de verplaatsingsconstructor geselecteerd voor elke directe basis van
T
is triviaal; - de verplaatsingsconstructor geselecteerd voor elk niet-statisch klassentype (of array van klassentype) lid van
T
is triviaal.
Een triviale move constructor is een constructor die dezelfde actie uitvoert als de triviale copy constructor, dat wil zeggen, maakt een kopie van de objectrepresentatie als door std::memmove. Alle gegevenstypen die compatibel zijn met de C-taal (POD-typen) zijn triviaal verplaatsbaar.
In aanmerking komende move-constructor
Een move-constructor komt in aanmerking als deze niet wordt verwijderd. |
(tot C++20) |
Een move-constructor komt in aanmerking als
|
(sinds C++20) |
Trivialiteit van in aanmerking komende move constructors bepaalt of de klasse een implicit-lifetime type is, en of de klasse een triviaal kopieerbaar type is.
Impliciet-definieerbare move constructor
Als de impliciet-declareerbare move constructor niet verwijderd of triviaal is, wordt deze gedefinieerd (dat wil zeggen, er wordt een functielichaam gegenereerd en gecompileerd) door de compiler als deze odr-gebruikt wordt of nodig is voor constante evaluatie. Voor union-types kopieert de impliciet gedefinieerde move constructor de objectrepresentatie (zoals door std::memmove). Voor niet-union-klassetypen (class en struct) voert de move-constructor een volledige member-wise move uit van de bases en niet-statische leden van het object, in hun initialisatievolgorde, met behulp van directe initialisatie met een xvalue-argument. Als dit voldoet aan de eisen van een constexpr constructor, is de gegenereerde move constructor constexpr
.
Notes
Om de sterke uitzonderingsgarantie mogelijk te maken, moeten door de gebruiker gedefinieerde move constructors geen uitzonderingen gooien. Bijvoorbeeld, std::vector vertrouwt op std::move_if_noexcept om te kiezen tussen move en copy wanneer de elementen moeten worden verplaatst.
Als zowel de constructor van copy als die van move aanwezig zijn en er geen andere constructors zijn, selecteert overload resolution de constructor van move als het argument een r-waarde van hetzelfde type is (een x-waarde zoals het resultaat van std::move of een pr-waarde zoals een naamloze tijdelijke (tot C++17)), en selecteert de constructor van copy als het argument een l-waarde is (een naamloos object of een functie/operator die een l-waarde referentie retourneert). Als alleen de kopieer constructor beschikbaar is, selecteren alle argument categorieën deze (zolang het een verwijzing naar const neemt, omdat rvalues kunnen binden aan const verwijzingen), waardoor kopiëren de fallback wordt voor verplaatsen, als verplaatsen niet beschikbaar is.
Een constructor wordt een “verplaats constructor” genoemd als het een rvalue verwijzing als parameter neemt. Het is niet verplicht om iets te verplaatsen, de klasse hoeft geen resource te hebben die verplaatst moet worden en een ‘move constructor’ kan mogelijk geen resource verplaatsen zoals in het toelaatbare (maar misschien niet verstandige) geval waarin de parameter een const rvalue reference is (const T&&).
Voorbeeld
#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);}
Uitvoer:
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
Defectmeldingen
De volgende gedragsveranderende defectmeldingen zijn met terugwerkende kracht toegepast op eerder gepubliceerde C++-normen.
DR | Toegepast op | Gedrag zoals gepubliceerd | Correct gedrag |
---|---|---|---|
CWG 1402 | C++11 | een defaulted move constructor die een niet-triviale copy constructor zou aanroepen werd verwijderd; een defaulted move constructor die is verwijderd doet nog steeds mee aan overload resolution |
laat aanroep van zo’n kopie constructor toe; genegeerd in overload resolution |
CWG 2094 | C++11 | een vluchtig subobject gemaakt van een defaulted move constructor niettriviaal (CWG496) |
trivialiteit niet aangetast |
Zie ook
- converterende constructor
- kopieeropdracht
- kopieerconstructor
- copy elision
- default constructor
- destructor
- explicit
- initialization
- aggregate initialization
- constante initialisatie
- kopie-initialisatie
- standaardinitialisatie
- directe initialisatie
- lijstinitialisatie
- lijstinitialisatie
- referentie-initialisatie
- waarde-initialisatie
- nul-initialisatie
- move-toewijzing
- nieuw