Move constructors
Un constructeur de déplacement est un constructeur qui peut être appelé avec un argument du même type de classe et copie le contenu de l'argument, en modifiant éventuellement l'argument.
Table des matières |
Syntaxe
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) | ||||||||
| class-name | - | la classe dont le constructeur de déplacement est déclaré |
| parameter-list | - |
une
liste de paramètres
non vide satisfaisant toutes les conditions suivantes :
|
| single-parameter-list | - | une liste de paramètres d'un seul paramètre, qui est de type T && , const T && , volatile T && ou const volatile T && et n'a pas d'argument par défaut |
| function-body | - | le corps de fonction du constructeur de déplacement |
Explication
struct X { X(X&& other); // constructeur de déplacement // X(X other); // Erreur : type de paramètre incorrect }; union Y { Y(Y&& other, int num = 1); // constructeur de déplacement avec paramètres multiples // Y(Y&& other, int num); // Erreur : `num` n'a pas d'argument par défaut };
Le constructeur de déplacement est généralement appelé lorsqu'un objet est initialisé (par initialisation directe ou initialisation par copie ) à partir d'une rvalue (xvalue ou prvalue) (jusqu'à C++17) xvalue (depuis C++17) du même type, y compris
-
initialisation :
T a
=
std
::
move
(
b
)
;
ou
T a
(
std
::
move
(
b
)
)
;
, où
b
est de type
T; -
passage d'argument de fonction :
f
(
std
::
move
(
a
)
)
;
, où
a
est de type
Tet f est void f ( T t ) ; -
retour de fonction :
return
a
;
à l'intérieur d'une fonction telle que
T f
(
)
, où
a
est de type
Tqui possède un constructeur de déplacement.
Lorsque l'initialiseur est une prvalue, l'appel au constructeur de déplacement est souvent optimisé (jusqu'à C++17) jamais effectué (depuis C++17) , voir copy elision .
Les constructeurs de déplacement transfèrent généralement les ressources détenues par l'argument (par exemple, les pointeurs vers des objets alloués dynamiquement, les descripteurs de fichiers, les sockets TCP, les handles de thread, etc.) plutôt que d'en faire des copies, et laissent l'argument dans un état valide mais autrement indéterminé. Étant donné que le constructeur de déplacement ne modifie pas la durée de vie de l'argument, le destructeur sera généralement appelé sur l'argument ultérieurement. Par exemple, déplacer à partir d'un std::string ou d'un std::vector peut entraîner que l'argument soit laissé vide. Pour certains types, tels que std::unique_ptr , l'état après déplacement est entièrement spécifié.
Constructeur de déplacement implicitement déclaré
Si aucun constructeur de déplacement défini par l'utilisateur n'est fourni pour un type de classe, et que toutes les conditions suivantes sont vraies :
- il n'y a pas de copy constructors déclarés par l'utilisateur ;
- il n'y a pas de copy assignment operators déclarés par l'utilisateur ;
- il n'y a pas de move assignment operators déclarés par l'utilisateur ;
- il n'y a pas de destructor déclaré par l'utilisateur.
Ensuite, le compilateur déclarera un constructeur de déplacement comme membre explicit inline public non-explicite de sa classe avec la signature T :: T ( T && ) .
Une classe peut avoir plusieurs constructeurs de déplacement, par exemple à la fois T :: T ( const T && ) et T :: T ( T && ) . Si certains constructeurs de déplacement définis par l'utilisateur sont présents, l'utilisateur peut toujours forcer la génération du constructeur de déplacement déclaré implicitement avec le mot-clé default .
Le constructeur de déplacement implicitement déclaré (ou par défaut lors de sa première déclaration) possède une spécification d'exception comme décrit dans la spécification d'exception dynamique (jusqu'à C++17) la spécification noexcept (depuis C++17) .
Constructeur de déplacement implicitement défini
Si le constructeur de déplacement implicitement déclaré n'est ni supprimé ni trivial, il est défini (c'est-à-dire qu'un corps de fonction est généré et compilé) par le compilateur s'il est ODR-use ou nécessaire pour l'évaluation constante . Pour les types union, le constructeur de déplacement implicitement défini copie la représentation de l'objet (comme par std::memmove ). Pour les types classe non-union, le constructeur de déplacement effectue un déplacement complet membre par membre des sous-objets de base directs et des sous-objets membres, dans leur ordre d'initialisation, en utilisant l'initialisation directe avec un argument xvalue . Pour chaque membre de données non statique de type référence, le constructeur de déplacement lie la référence au même objet ou fonction auquel la référence source est liée.
Si cela satisfait aux exigences d'un
constexpr
constructeur
(jusqu'en C++23)
constexpr
fonction
(depuis C++23)
, le constructeur de déplacement généré est
constexpr
.
Constructeur de déplacement supprimé
Le constructeur de déplacement implicitement déclaré ou explicitement par défaut pour la classe
T
est défini comme supprimé si
T
possède un
sous-objet potentiellement construit
de type classe
M
(ou éventuellement un tableau multidimensionnel de celui-ci) tel que
-
Mpossède un destructeur qui est supprimé ou inaccessible depuis le constructeur de copie, ou -
la résolution de surcharge appliquée pour trouver le constructeur de déplacement de
M
-
- ne donne pas lieu à un candidat utilisable, ou
- dans le cas où le sous-objet est un variant member , sélectionne une fonction non triviale.
Un tel constructeur est ignoré par la résolution de surcharge (sinon il empêcherait l'initialisation par copie à partir d'une rvalue).
Constructeur de déplacement trivial
Le constructeur de déplacement pour la classe
T
est trivial si toutes les conditions suivantes sont vraies :
- il n'est pas fourni par l'utilisateur (c'est-à-dire qu'il est implicitement défini ou par défaut) ;
-
Tn'a pas de fonctions membres virtuelles ; -
Tn'a pas de classes de base virtuelles ; -
le constructeur de déplacement sélectionné pour chaque base directe de
Test trivial ; -
le constructeur de déplacement sélectionné pour chaque membre de type classe (ou tableau de type classe) non statique de
Test trivial.
Un constructeur de déplacement trivial est un constructeur qui effectue la même action que le constructeur de copie trivial, c'est-à-dire qu'il fait une copie de la représentation de l'objet comme si par std::memmove . Tous les types de données compatibles avec le langage C sont trivialement déplaçables.
Constructeur de déplacement éligible
|
Un constructeur de déplacement est éligible s'il n'est pas supprimé. |
(jusqu'à C++20) |
|
Un constructeur de déplacement est éligible si toutes les conditions suivantes sont satisfaites :
|
(depuis C++20) |
La trivialité des constructeurs de déplacement éligibles détermine si la classe est un type à durée de vie implicite , et si la classe est un type trivialement copiable .
Notes
Pour permettre la garantie forte d'exception , les constructeurs de déplacement définis par l'utilisateur ne devraient pas lever d'exceptions. Par exemple, std::vector s'appuie sur std::move_if_noexcept pour choisir entre le déplacement et la copie lorsque les éléments doivent être relocalisés.
Si les constructeurs de copie et de déplacement sont tous deux fournis et qu'aucun autre constructeur n'est viable, la résolution de surcharge sélectionne le constructeur de déplacement si l'argument est un rvalue du même type (un xvalue tel que le résultat de std::move ou un prvalue tel qu'un temporaire sans nom (jusqu'à C++17) ), et sélectionne le constructeur de copie si l'argument est un lvalue (objet nommé ou fonction/opérateur retournant une référence lvalue). Si seul le constructeur de copie est fourni, toutes les catégories d'arguments le sélectionnent (à condition qu'il prenne une référence vers const, car les rvalues peuvent se lier aux références const), ce qui fait de la copie la solution de repli pour le déplacement lorsque celui-ci n'est pas disponible.
Exemple
#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)), // déplacement explicite d'un membre de type classe k(std::exchange(o.k, 0)) // déplacement explicite d'un membre de type non-classe {} }; A f(A a) { return a; } struct B : A { std::string s2; int n; // constructeur de déplacement implicite B::(B&&) // appelle le constructeur de déplacement de A // appelle le constructeur de déplacement de s2 // et effectue une copie bit à bit de n }; struct C : B { ~C() {} // le destructeur empêche le constructeur de déplacement implicite C::(C&&) }; struct D : B { D() {} ~D() {} // le destructeur empêcherait le constructeur de déplacement implicite D::(D&&) D(D&&) = default; // force malgré tout un constructeur de déplacement }; int main() { std::cout << "Tentative de déplacement de A\n"; A a1 = f(A()); // le retour par valeur construit par déplacement la cible // à partir du paramètre de fonction std::cout << "Avant déplacement, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; A a2 = std::move(a1); // construit par déplacement à partir d'une xvalue std::cout << "Après déplacement, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; std::cout << "\nTentative de déplacement de B\n"; B b1; std::cout << "Avant déplacement, b1.s = " << std::quoted(b1.s) << "\n"; B b2 = std::move(b1); // appelle le constructeur de déplacement implicite std::cout << "Après déplacement, b1.s = " << std::quoted(b1.s) << "\n"; std::cout << "\nTentative de déplacement de C\n"; C c1; C c2 = std::move(c1); // appelle le constructeur de copie std::cout << "\nTentative de déplacement de D\n"; D d1; D d2 = std::move(d1); }
Sortie :
Tentative de déplacement de A Avant déplacement, a1.s = "test" a1.k = -1 Après déplacement, a1.s = "" a1.k = 0 Tentative de déplacement de B Avant déplacement, b1.s = "test" Après déplacement, b1.s = "" Tentative de déplacement de C move failed! Tentative de déplacement de D
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | Appliqué à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 1353 | C++11 |
les conditions où les constructeurs de déplacement par défaut sont
définis comme supprimés ne prenaient pas en compte les types de tableaux multidimensionnels |
prendre en compte ces types |
| CWG 1402 | C++11 |
un constructeur de déplacement par défaut qui appellerait
un constructeur de copie non trivial était défini comme supprimé ; un constructeur de déplacement par défaut qui est supprimé participait toujours à la résolution de surcharge |
permet l'appel à un tel constructeur
de copie ; rendu ignoré dans la résolution de surcharge |
| CWG 1491 | C++11 |
un constructeur de déplacement par défaut d'une classe avec un membre de données
non statique de type référence rvalue était défini comme supprimé |
non supprimé dans ce cas |
| CWG 2094 | C++11 |
un sous-objet volatile rendait un constructeur de déplacement
par défaut non trivial ( CWG issue 496 ) |
la trivialité n'est pas affectée |
| CWG 2595 | C++20 |
un constructeur de déplacement n'était pas éligible s'il existe
un autre constructeur de déplacement plus contraint mais ne satisfaisant pas ses contraintes associées |
il peut être éligible dans ce cas |