En move-konstruktør af klasse T
er en ikke-skabelonkonstruktør, hvis første parameter er T&&, const T&&, volatile T&& eller const volatile T&&, og enten er der ingen andre parametre, eller også har resten af parametrene alle standardværdier.
Indhold
- 1 Syntaks
- 2 Forklaring
- 3 Implicit deklareret flyttekonstruktør
- 4 Slettet implicit deklareret flyttekonstruktør
- 5 Triviel flyttekonstruktør
- 6 Eligible flyttekonstruktør
- 7 Implicit-defineret move-konstruktør
- 8 Noter
- 9 Eksempel
- 10 Fejlrapporter
- 11 Se også
Syntaks
class_name ( class_name && ) |
(1) | (siden C++11) | |||||||
class_name ( class_name && ) = standard; |
(2) | (siden C++11) | |||||||
class_name ( class_name && ) = delete; |
(3) | (siden C++11) | |||||||
Hvor class_name skal navngive den aktuelle klasse (eller den aktuelle instantiering af en klasseskabelon), eller, når den er erklæret i navnerumsområdet eller i en friend-deklaration, skal det være et kvalificeret klassenavn.
Forklaring
Den move-konstruktør kaldes typisk, når et objekt initialiseres (ved direkte initialisering eller kopi-initialisering) fra rvalue (xvalue eller prvalue) (indtil C++17)xvalue (siden C++17) af samme type, herunder
- initialisering: T a = std::move(b); eller T a(std::move(b)));;, hvor
b
er af typenT
; - overdragelse af funktionsargumenter: f(std::move(a));;, hvor
a
er af typenT
ogf
er void f(T t); - funktionsretur: return a; inde i en funktion som T f(), hvor
a
er af typenT
, som har en move-konstruktør.
Når initialisatoren er en pr-værdi, er move-konstruktorkaldet ofte optimeret ud (indtil C++17)aldrig foretaget (siden C++17), se copy elision.
Move-konstruktører “stjæler” typisk de ressourcer, som argumentet har (f.eks.f.eks. pointers til dynamisk allokerede objekter, fildeskriptorer, TCP-socket, I/O-strømme, kørende tråde osv.) i stedet for at lave kopier af dem, og efterlader argumentet i en eller anden gyldig, men ellers ubestemt tilstand. F.eks. kan flytning fra en std::string eller fra en std::vector resultere i, at argumentet efterlades tomt. Man bør dog ikke stole på denne adfærd. For nogle typer, som f.eks. std::unique_ptr, er den flyttede tilstand fuldt specificeret.
Implicit deklareret move-konstruktør
Hvis der ikke er fastsat brugerdefinerede move-konstruktører for en klassetype (struct, class eller union), og alle følgende forhold er sande:
- der er ingen brugerdeklarerede kopikonstruktører;
- der er ingen brugerdeklarerede kopitildelingsoperatører;
- der er ingen brugerdeklarerede flyttetildelingsoperatører;
- der er ingen brugerdeklareret destruktor.
så vil compileren deklarere en move-konstruktør som et ikke-eksplicit inline public
medlem af sin klasse med signaturen T::T(T&&)
.
En klasse kan have flere move-konstruktører, f.eks. både T::T(const T&&) og T::T(T&&). Hvis der findes nogle brugerdefinerede move-konstruktører, kan brugeren stadig tvinge genereringen af den implicit deklarerede move-konstruktør frem med nøgleordet default
.
Den implicit-deklarerede (eller standardiserede ved dens første deklaration) move-konstruktør har en undtagelsesspecifikation som beskrevet i dynamisk undtagelsesspecifikation (indtil C++17)undtagelsesspecifikation (siden C++17)
Slettet implicit-deklareret move-konstruktør
Den implicit-deklarerede eller standardiserede move-konstruktør for klasse T
er defineret som slettet, hvis en af følgende er sand:
-
T
har ikke-statiske datamedlemmer, der ikke kan flyttes (har slettede, utilgængelige eller tvetydige move-konstruktører); -
T
har en direkte eller virtuel basisklasse, der ikke kan flyttes (har slettede, utilgængelige eller tvetydige move-konstruktører); -
T
har en direkte eller virtuel basisklasse med en slettet eller utilgængelig destruktor; -
T
er en union-lignende klasse og har et variantmedlem med en ikke-triviel flyttekonstruktør.
En standardiseret move-konstruktør, der er slettet, ignoreres af overload-opløsning (ellers ville det forhindre kopi-initialisering fra rvalue).
Triviel move-konstruktør
Men move-konstruktøren for klasse T
er triviel, hvis alt det følgende er sandt:
- den er ikke brugerforsynet (hvilket betyder, at den er implicit defineret eller standardiseret);
-
T
har ingen virtuelle medlemsfunktioner; -
T
har ingen virtuelle basisklasser; - den valgte move-konstruktør for hver direkte base af
T
er triviel; - den valgte move-konstruktør for hvert ikke-statisk klassetype-medlem (eller array af klassetype-medlem) af
T
er triviel.
En trivial move-konstruktør er en konstruktør, der udfører den samme handling som den triviale copy-konstruktør, dvs. laver en kopi af objektrepræsentationen som ved std::memmove. Alle datatyper, der er kompatible med C-sproget (POD-typer), er trivielt flytbare.
Støtteberettiget move-konstruktør
En move-konstruktør er støtteberettiget, hvis den ikke er slettet. |
(indtil C++20) |
En flyttekonstruktør er kvalificeret, hvis
|
(siden C++20) |
Trivialitet af kvalificerede move-konstruktører bestemmer, om klassen er en implicit-lifetime-type, og om klassen er en trivielt kopierbar type.
Implicit-defineret move-konstruktør
Hvis den implicit-deklarerede move-konstruktør hverken er slettet eller triviel, defineres den (dvs. der genereres og kompileres en funktionskrop) af compileren, hvis den odr-bruges eller er nødvendig for konstant-evaluering. For unionstyper kopierer den implicit-definerede move-konstruktør objektrepræsentationen (som ved std::memmove). For ikke-union-klassetyper (class og struct) udfører move-konstruktøren en fuldstændig medlemsvis flytning af objektets baser og ikke-statiske medlemmer i deres initialiseringsrækkefølge ved hjælp af direkte initialisering med et xvalue-argument. Hvis dette opfylder kravene til en constexpr-konstruktør, er den genererede move-konstruktør constexpr
.
Bemærkninger
For at gøre den stærke undtagelsesgaranti mulig bør brugerdefinerede move-konstruktører ikke kaste undtagelser. For eksempel er std::vector afhængig af std::move_if_noexcept for at vælge mellem move og copy, når elementerne skal flyttes.
Hvis der leveres både copy- og move-konstruktører, og ingen andre konstruktører er levedygtige, vælger overload-opløsning move-konstruktøren, hvis argumentet er en r-værdi af samme type (en x-værdi som f.eks. resultatet af std::move eller en pr-værdi som f.eks. en navnløs midlertidig (indtil C++17)), og vælger copy-konstruktøren, hvis argumentet er en l-værdi (navngivet objekt eller en funktion/operator, der returnerer en l-værdi-reference). Hvis kun copy-konstruktøren er angivet, vælger alle argumentkategorier den (så længe den tager en reference til const, da rværdier kan binde til const-referencer), hvilket gør kopiering til fallback for moving, når moving ikke er tilgængelig.
En konstruktør kaldes en “move-konstruktør”, når den tager en rvalue-reference som parameter. Den er ikke forpligtet til at flytte noget, klassen er ikke forpligtet til at have en ressource, der skal flyttes, og en ‘move-konstruktør’ er måske ikke i stand til at flytte en ressource som i det tilladte (men måske ikke fornuftige) tilfælde, hvor parameteren er en const rvalue-reference (const T&&).
Eksempel
#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);}
Output:
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
Fejlrapporter
De følgende adfærdsændrende fejlrapporter blev anvendt med tilbagevirkende kraft på tidligere offentliggjorte C++-standarder.
DR | Anvendt på | Adfærd som offentliggjort | Korrekt adfærd |
---|---|---|---|
CWG 1402 | C++11 | en standardiseret move-konstruktør, der ville anråbe en ikke-triviel kopi-konstruktør, blev slettet; en standardiseret move-konstruktør, der er slettet , deltog stadig i overload-opløsningen |
tillader kald til en sådan kopikonstruktør; gjorde ignoreret i overload-opløsning |
CWG 2094 | C++11 | et flygtigt underobjekt lavet af en defaulted move-konstruktør, der ikketrivial (CWG496) |
trivialitet ikke påvirket |
Se også
- konvertere konstruktør
- kopi-tildeling
- kopikopieringskonstruktør
- kopi-elision
- standardkonstruktor
- destruktor
- eksplicit
- initialisering
- aggregeret initialisering
- konstant initialisering
- kopiinitialisering
- standardinitialisering
- direkte initialisering
- initialiseringsliste
- listeinitialisering
- referenceinitialisering
- referenceinitialisering
- værdiinitialisering
- nulinitialisering
- flytte tildeling
- ny