web-dev-qa-db-fra.com

Un constructeur de déplacement `= par défaut` est-il équivalent à un constructeur de déplacement par membre?

Est-ce

struct Example { 
    int a, b; 
    Example(int mA, int mB) : a{mA}, b{mB}               { }
    Example(const Example& mE) : a{mE.a}, b{mE.b}        { }
    Example(Example&& mE) : a{move(mE.a)}, b{move(mE.b)} { }
    Example& operator=(const Example& mE) { a = mE.a; b = mE.b; return *this; } 
    Example& operator=(Example&& mE)      { a = move(mE.a); b = move(mE.b); return *this; } 
}

équivalent à cela

struct Example { 
    int a, b; 
    Example(int mA, int mB) : a{mA}, b{mB} { }
    Example(const Example& mE)            = default;
    Example(Example&& mE)                 = default;
    Example& operator=(const Example& mE) = default;
    Example& operator=(Example&& mE)      = default;
}

?

86
Vittorio Romeo

Oui, les deux sont les mêmes.

Mais

struct Example { 
    int a, b; 
    Example(int mA, int mB) : a{mA}, b{mB} { }
    Example(const Example& mE)            = default;
    Example(Example&& mE)                 = default;
    Example& operator=(const Example& mE) = default;
    Example& operator=(Example&& mE)      = default;
}

Cette version vous permettra de sauter la définition du corps.

Cependant, vous devez suivre certaines règles lorsque vous déclarez explicitly-defaulted-functions:

8.4.2 Fonctions par défaut explicites [dcl.fct.def.default]

Une définition de fonction du formulaire:

  attribute-specifier-seqopt decl-specifier-seqopt declarator virt-specifier-seqopt = default ;

est appelé une définition par défaut explicite . Une fonction explicitement par défaut doit

  • être une fonction membre spéciale,

  • ont le même type de fonction déclaré (sauf pour les qualificatifs ref éventuellement différents et sauf que dans le cas d'un constructeur de copie ou d'un opérateur d'affectation de copie, le type de paramètre peut être une "référence à un non-const T", où T est le nom de la classe de la fonction membre) comme si elle avait été implicitement déclarée,

  • pas d'arguments par défaut.

44
Pierre Fourgeaud

Oui, un constructeur de déplacement par défaut effectuera un déplacement par membre de sa base et de ses membres, donc:

Example(Example&& mE) : a{move(mE.a)}, b{move(mE.b)} { }

est équivalent à:

Example(Example&& mE)                 = default;

nous pouvons le voir en allant à la projet de norme C++ 11 section 12.8 Copier et déplacer des objets de classe paragraphe 13 qui dit ( je mets l'accent à l'avenir):

Un constructeur de copie/déplacement par défaut et non défini comme supprimé est implicitement défini s'il est erroné (3.2) ou lorsqu'il est explicitement par défaut après son premier déclaration. [Remarque: le constructeur copier/déplacer est implicitement défini même si l'implémentation a élidé son utilisation odr (3.2, 12.2). —Fin noter] [...]

et le paragraphe 15 qui dit:

Le constructeur de copie/déplacement défini implicitement pour une classe non-union X effectue une copie/déplacement par membre de son bases et membres . [Remarque: les initialiseurs d'accolade ou d'égalité des membres de données non statiques sont ignorés. Voir également l'exemple au 12.6.2. —Fin note] L'ordre d'initialisation est le même que l'ordre d'initialisation des bases et des membres dans un constructeur défini par l'utilisateur (voir 12.6.2). Soit x le paramètre du constructeur ou, pour le constructeur de déplacement, une valeur x faisant référence au paramètre. Chaque membre de données de base ou non statique est copié/déplacé de la manière appropriée à son type:

  • si le membre est un tableau, chaque élément est initialisé directement avec le sous-objet correspondant de x;
  • si un membre m a le type de référence rvalue T &&, il est initialisé directement avec static_cast (x.m);
  • sinon, la base ou le membre est initialisé directement avec la base ou le membre correspondant de x.

Les sous-objets de classe de base virtuelle ne doivent être initialisés qu'une seule fois par le constructeur de copie/déplacement défini implicitement (voir 12.6.2).

19
Shafik Yaghmour

Un constructeur de mouvement =default Est-il équivalent à un constructeur de déplacement par membre?

Oui. Mise à jour: Eh bien, pas toujours. Regardez cet exemple:

#include <iostream>

struct nonmovable
{
    nonmovable() = default;

    nonmovable(const nonmovable  &) = default;
    nonmovable(      nonmovable &&) = delete;
};

struct movable
{
    movable() = default;

    movable(const movable  &) { std::cerr << "copy" << std::endl; }
    movable(      movable &&) { std::cerr << "move" << std::endl; }
};

struct has_nonmovable
{
    movable    a;
    nonmovable b;

    has_nonmovable() = default;

    has_nonmovable(const has_nonmovable  &) = default;
    has_nonmovable(      has_nonmovable &&) = default;
};

int main()
{
    has_nonmovable c;
    has_nonmovable d(std::move(c)); // prints copy
}

Il imprime:

copy

http://coliru.stacked-crooked.com/a/62c0a0aaec15b0eb

Vous avez déclaré le constructeur de déplacement par défaut, mais la copie a lieu au lieu de se déplacer. Pourquoi? Parce que si une classe a même un seul membre non mobile, alors le constructeur explicitement par défaut move est implicitement supprimé (un jeu de mots pareil). Ainsi, lorsque vous exécutez has_nonmovable d = std::move(c), le constructeur de copie est en fait appelé, car le constructeur de déplacement de has_nonmovable Est supprimé (implicitement), il n'existe tout simplement pas (même si vous avez explicitement déclaré le déplacement constructeur par l'expression has_nonmovable(has_nonmovable &&) = default).

Mais si le constructeur de déplacement de non_movable N'était pas du tout déclaré, le constructeur de déplacement serait utilisé pour movable (et pour chaque membre qui a le constructeur de déplacement) et le constructeur de copie serait utilisé pour nonmovable (et pour chaque membre qui ne définit pas le constructeur de déplacement). Voir l'exemple:

#include <iostream>

struct nonmovable
{
    nonmovable() = default;

    nonmovable(const nonmovable  &) { std::cerr << "nonmovable::copy" << std::endl; }
    //nonmovable(      nonmovable &&) = delete;
};

struct movable
{
    movable() = default;

    movable(const movable  &) { std::cerr << "movable::copy" << std::endl; }
    movable(      movable &&) { std::cerr << "movable::move" << std::endl; }
};

struct has_nonmovable
{
    movable    a;
    nonmovable b;

    has_nonmovable() = default;

    has_nonmovable(const has_nonmovable  &) = default;
    has_nonmovable(      has_nonmovable &&) = default;
};

int main()
{
    has_nonmovable c;
    has_nonmovable d(std::move(c));
}

Il imprime:

movable::move
nonmovable::copy

http://coliru.stacked-crooked.com/a/420cc6c80ddac407

Mise à jour: Mais si vous commentez la ligne has_nonmovable(has_nonmovable &&) = default;, alors la copie sera utilisée pour les deux membres: http: //coliru.stacked-crooked.com/a/171fd0ce335327cd - impressions:

movable::copy
nonmovable::copy

Donc, mettre probablement =default Partout a toujours du sens. Cela ne signifie pas que vos expressions de mouvement se déplaceront toujours, mais cela augmente les chances.

Encore une mise à jour: Mais si vous commentez la ligne has_nonmovable(const has_nonmovable &) = default; non plus, alors le résultat sera:

movable::move
nonmovable::copy

Donc, si vous voulez savoir ce qui se passe dans votre programme, faites tout par vous-même: soupir:

7
anton_rh