Pohybový konstruktor třídy T
je nešablonový konstruktor, jehož první parametr je T&&, const T&&, volatile T&& nebo const volatile T&& a buď nemá žádné další parametry, nebo mají všechny ostatní parametry výchozí hodnoty.
Obsah
- 1 Syntaxe
- 2 Vysvětlení
- 3 Implicitně deklarovaný konstruktor move
- 4 Odstraněný implicitně deklarovaný konstruktor move
- 5 Triviální konstruktor move
- 6 Způsobilý konstruktor move
- 7 Implicitně-definovaný move konstruktor
- 8 Poznámky
- 9 Příklad
- 10 Hlášení závad
- 11 Viz také
Syntaxe
class_name ( class_name && ) |
(1) | (od C++11) | |||||||
class_name ( class_name && ) = výchozí; |
(2) | (od C++11) | |||||||
class_name ( class_name && ) = delete; |
(3) | (od C++11) | |||||||
Kde class_name musí pojmenovávat aktuální třídu (nebo aktuální instanci šablony třídy), nebo, je-li deklarováno v oboru jmenného prostoru nebo v deklaraci přítele, musí být kvalifikovaným jménem třídy.
Vysvětlení
Konstruktor move se typicky volá při inicializaci objektu (přímou inicializací nebo kopírováním) z rvalue (xvalue nebo prvalue) (do C++17)xvalue (od C++17) stejného typu, včetně
- inicializace: T a = std::move(b); nebo T a(std::move(b));, kde
b
je typuT
; - předávání argumentů funkce: f(std::move(a));, kde
a
je typuT
af
je void f(T t); - návrat funkce: return a; uvnitř funkce jako T f(), kde
a
je typuT
, která má konstruktor move.
Pokud je inicializátorem prvalue, volání move konstruktoru se často optimalizuje (do C++17)nikdy se neprovádí (od C++17), viz copy elision.
Move konstruktory typicky „kradou“ prostředky držené argumentem (např.např. ukazatele na dynamicky alokované objekty, deskriptory souborů, TCP sokety, I/O proudy, běžící vlákna atd.), místo aby vytvářely jejich kopie, a ponechávají argument v nějakém platném, ale jinak neurčitém stavu. Například přechod ze std::string nebo ze std::vector může vést k tomu, že argument zůstane prázdný. Na toto chování by se však nemělo spoléhat. U některých typů, jako je například std::unique_ptr, je stav přesunu z plně specifikován.
Implicitně deklarovaný konstruktor move
Pokud pro typ třídy (struct, class nebo union) nejsou k dispozici žádné uživatelsky definované konstruktory move a platí všechny následující podmínky:
- nejsou žádné uživatelsky deklarované kopírovací konstruktory;
- nejsou žádné uživatelsky deklarované operátory přiřazení kopií;
- nejsou žádné uživatelsky deklarované operátory přiřazení přesunů;
- není žádný uživatelsky deklarovaný destruktor.
tak kompilátor deklaruje konstruktor přesunu jako neexplicitní inline public
člen své třídy se signaturou T::T(T&&)
.
Třída může mít více konstruktorů přesunu, např. jak T::T(const T&&), tak T::T(T&&). Pokud jsou přítomny některé uživatelem definované konstruktory pohybu, může si uživatel ještě vynutit vygenerování implicitně deklarovaného konstruktoru pohybu pomocí klíčového slova default
.
Implicitně deklarovaný (nebo při první deklaraci výchozí) move konstruktor má specifikaci výjimky popsanou ve specifikaci dynamických výjimek (do C++17)specifikaci výjimek (od C++17)
Odstraněný implicitně deklarovaný move konstruktor
Implicitně deklarovaný nebo výchozí move konstruktor pro třídu T
je definován jako odstraněný, pokud je pravdivá některá z následujících hodnot:
-
T
má nestatické datové členy, které nelze přesunout (mají smazané, nedostupné nebo nejednoznačné move konstruktory); -
T
má přímou nebo virtuální základní třídu, kterou nelze přesunout (má smazané, nedostupné nebo nejednoznačné move konstruktory); -
T
má přímou nebo virtuální bázovou třídu se smazaným nebo nedostupným destruktorem; -
T
je třída podobná unii a má variantní člen s netriviálním konstruktorem přesunu.
Defaultní konstruktor move, který je smazán, je ignorován při řešení přetížení (jinak by zabránil kopírování inicializace z rvalue).
Triviální konstruktor přesunu
Konstruktor přesunu pro třídu T
je triviální, pokud platí všechny následující podmínky:
- není uživatelský (to znamená, že je implicitně definovaný nebo výchozí);
-
T
nemá žádné virtuální členské funkce; -
T
nemá žádné virtuální základní třídy; - konstruktor pohybu vybraný pro každou přímou bázi
T
je triviální; - konstruktor pohybu vybraný pro každý nestatický člen typu třídy (nebo pole typu třídy)
T
je triviální.
Triviální move konstruktor je konstruktor, který provádí stejnou akci jako triviální kopírovací konstruktor, tj. vytváří kopii reprezentace objektu jako pomocí std::memmove. Všechny datové typy kompatibilní s jazykem C (typy POD) jsou triviálně přesunutelné.
Způsobilý konstruktor přesunu
Konstruktor přesunu je způsobilý, pokud není smazán. |
(do C++20) |
Konstruktor přesunu je způsobilý, jestliže
|
(od C++20) |
Trivialita způsobilých konstruktérů přesunu určuje, zda je třída implicitně-životním typem a zda je třída triviálně kopírovatelným typem.
Implicitně definovaný move konstruktor
Jestliže implicitně deklarovaný move konstruktor není ani smazatelný, ani triviální, je definován (tj. je vygenerováno a zkompilováno tělo funkce) kompilátorem, je-li odr-užit nebo potřebný pro vyhodnocení konstanty. Pro typy union implicitně definovaný konstruktor move kopíruje reprezentaci objektu (jako u std::memmove). U nesjednocených třídních typů (class a struct) provádí konstruktor move úplný přesun bází a nestatických členů objektu v jejich inicializačním pořadí pomocí přímé inicializace s argumentem xvalue. Pokud toto splňuje požadavky konstruktoru constexpr, je vygenerovaný konstruktor move constexpr
.
Poznámky
Aby bylo možné zaručit silné výjimky, neměly by uživatelsky definované konstruktory move vyhazovat výjimky. Například std::vector spoléhá na std::move_if_noexcept, aby se rozhodl mezi move a copy, když je třeba prvky přemístit.
Jsou-li k dispozici konstruktory copy i move a žádné jiné konstruktory nejsou životaschopné, rozlišení přetížení vybere konstruktor move, pokud je argumentem rvalue stejného typu (xvalue, například výsledek std::move, nebo prvalue, například bezejmenný temporary (do C++17)), a vybere konstruktor copy, pokud je argumentem lvalue (pojmenovaný objekt nebo funkce/operátor vracející odkaz na lvalue). Pokud je uveden pouze kopírovací konstruktor, vyberou jej všechny kategorie argumentů (pokud přebírá referenci na const, protože rvalues se mohou vázat na reference const), čímž se kopírování stává nouzovým řešením pro přesun, pokud přesun není k dispozici.
Konstruktor se nazývá „přesunovací konstruktor“, pokud jako parametr přebírá referenci na rvalue. Není povinen nic přesouvat, třída nemusí mít prostředek, který má být přesunut, a ‚move konstruktor‘ nemusí být schopen přesunout prostředek jako v přípustném (ale možná ne rozumném) případě, kdy je parametrem const rvalue reference (const T&&).
Příklad
#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);}
Výstup:
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
Zprávy o závadách
Následující zprávy o závadách měnících chování byly zpětně aplikovány na dříve vydané normy C++.
DR | Aplikováno na | Chování ve zveřejněné podobě | Správné chování |
---|---|---|---|
CWG 1402 | C++11 | byl odstraněn výchozí konstruktor move, který by volal netriviální konstruktor copy; přednastavený konstruktor přesunu, který byl odstraněn , se stále podílel na řešení přetížení |
umožňuje volání takového kopírovacího konstruktoru; byl ignorován při řešení přetížení |
CWG 2094 | C++11 | volatilní podobjekt vytvořený z defaultního konstruktoru přesunu, který nenítriviální (CWG496) |
trivialita není dotčena |
Viz také
- konvertující konstruktor
- kopírující přiřazení
- kopírující konstruktor
- kopírování elize
- výchozí konstruktor
- destruktor
- explicitní
- inicializace
- souhrnná inicializace
- konstantní inicializace
- kopírovací inicializace
- výchozí inicializace
- přímá inicializace
- inicializátor seznamu
- inicializace seznamu
- referenční inicializace
- inicializace hodnoty
- nulová inicializace
- přiřazení pohybu
- nový