Namespaces
Variants

Move constructors

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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 :
  • étant donné le type de classe T , le premier paramètre est de type T && , const T && , volatile T && ou const volatile T && , et
  • soit il n'y a pas d'autres paramètres, soit tous les autres paramètres ont des arguments par défaut
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

1) Déclaration d'un constructeur de déplacement dans la définition de classe.
2-4) Définition d'un constructeur de déplacement à l'intérieur de la définition de classe.
3) Le constructeur de déplacement est explicitement par défaut.
4) Le constructeur de déplacement est supprimé.
5,6) Définition d'un constructeur de déplacement en dehors de la définition de classe (la classe doit contenir une déclaration (1) ).
6) Le constructeur de déplacement est explicitement défini par défaut.
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 T et 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 T qui 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 :

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

  • M possè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) ;
  • T n'a pas de fonctions membres virtuelles ;
  • T n'a pas de classes de base virtuelles ;
  • le constructeur de déplacement sélectionné pour chaque base directe de T est trivial ;
  • le constructeur de déplacement sélectionné pour chaque membre de type classe (ou tableau de type classe) non statique de T est 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

Voir aussi