144 votes

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

Est-ce que c'est

struct Example { 
    string a, b; 

    Example(Example&& mE) : a{move(mE.a)}, b{move(mE.b)} { }
    Example& operator=(Example&& mE) { a = move(mE.a); b = move(mE.b); return *this; } 
}

équivalent à ceci

struct Example { 
    string a, b;

    Example(Example&& mE)            = default;
    Example& operator=(Example&& mE) = default;
}

?

70voto

Pierre Fourgeaud Points 10249

Oui, les deux sont identiques.

Mais

struct Example { 
    string a, b; 

    Example(Example&& mE)            = default;
    Example& operator=(Example&& mE) = default;
}

Cette version vous permettra d'éviter la définition du corps.

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

8.4.2 Fonctions à défaut explicite [dcl.fct.def.default]

Une définition de fonction de la forme :

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

est appelé un Défauts explicites définition. Une fonction qui est explicitement définie par défaut doit

  • être une fonction membre spéciale,

  • ont le même type de fonction déclarée (à l'exception d'éventuelles divergences) ref-qualificateurs 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 "référence à des éléments non constants". T ", où T est le nom de la classe de la fonction membre) comme si elle avait été déclarée implicitement,

  • n'ont pas d'arguments par défaut.

32voto

Shafik Yaghmour Points 42198

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

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

est équivalent à :

Example(Example&& mE)                 = default;

nous pouvons le voir en allant dans le projet de norme C++11 section 12.8 Copier et déplacer les objets de la classe paragraphe 13 qui dit ( l'accent est mis sur l'avenir ):

Un constructeur de copie/déplacement par défaut et non défini comme supprimé. est défini implicitement s'il est odrused (3.2) ou lorsqu'il est explicitement par défaut après sa première déclaration. [Note : Le constructeur copy/move est implicitement défini même si l'implémentation a élidé son utilisation odr (3.2, 12.2). -fin de note ][...]

et paragraphe 15 qui dit :

Le site constructeur de copie/déplacement implicitement défini pour une classe non syndiquée X effectue un copier/déplacer membre par membre de ses bases et membres . [ Note : Les initialisateurs de type brace-or-equal des membres de données non statiques sont ignorés. Voir également l'exemple en 12.6.2. -note de fin ] 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. membres dans un constructeur défini par l'utilisateur (voir 12.6.2). Que x soit soit le paramètre du constructeur ou, pour le constructeur de déplacement, une xvalue se référant au paramètre. Chaque base ou membre de données non statique est copié/déplacé de la manière appropriée à son type :

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

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

31voto

anton_rh Points 1582

Est un =default Le constructeur de déplacement est é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 se produit au lieu du déplacement. Pourquoi ? Parce que si une classe possède ne serait-ce qu'un seul membre non déplaçable, alors la fonction explicitement en défaut de paiement Le constructeur de mouvement est implicitement supprimé (quel jeu de mots). Donc, quand vous exécutez has_nonmovable d = std::move(c) le constructeur de copie est en fait appelé, parce que 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 constructeur move par l'expression has_nonmovable(has_nonmovable &&) = default ).

Mais si le constructeur de move de non_movable n'a pas été déclaré du tout, le constructeur de move serait utilisé pour movable (et pour chaque membre qui a le constructeur move) et le constructeur copy serait utilisé pour nonmovable (et pour chaque membre qui ne définit pas le constructeur move). 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 - des empreintes :

movable::copy
nonmovable::copy

Donc probablement en mettant =default partout a encore du sens. Cela ne signifie pas que vos expressions de mouvement bougeront toujours, mais cela augmente les chances de le faire.

Une dernière mise à jour : Mais si l'on commente la ligne has_nonmovable(const has_nonmovable &) = default; soit, 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 :sigh :

-2voto

Emilio Garavaglia Points 9189

À part les cas très pathologiques... OUI.

Pour être plus précis, vous devez également prendre en compte les bases éventuelles. Example peut avoir, avec exactement les mêmes règles. D'abord les bases - dans l'ordre de déclaration - puis les membres, toujours dans l'ordre de déclaration.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X