web-dev-qa-db-fra.com

Constructeur / affectation de déplacement par défaut et constructeur / affectation de copie supprimé

Selon la norme,

Si la définition d'une classe X ne déclare pas explicitement un constructeur de déplacement, l'un sera implicitement déclaré comme défaut si et seulement si

- X n'a ​​pas de constructeur de copie déclaré par l'utilisateur,

- X n'a ​​pas d'opérateur d'affectation de copie déclaré par l'utilisateur,

- X n'a ​​pas d'opérateur d'affectation de déplacement déclaré par l'utilisateur, et

- X n'a ​​pas de destructeur déclaré par l'utilisateur.

Maintenant, ce qui suit ne parvient pas à compiler

# include <utility>

class Foo
{
public:
  Foo() = default;
  Foo(Foo const &) = delete;
};

int main()
{
  Foo f;
  Foo g(std::move(f)); // compilation fails here
  return 0;
}

Il semble donc qu'une fonction supprimée soit considérée comme définie par l'utilisateur, ce qui est logique (ce n'est pas son implémentation par défaut). Cependant, dans ce cas particulier, comment le constructeur/affectation par défaut du constructeur/affectation de copie supprimée serait-il déplacé?

Je pense que cette question a une importance pratique car la génération manuelle et esp. la maintenance de ces fonctions par défaut est sujette aux erreurs, tandis que dans le même temps, l'augmentation (juste) de l'utilisation de classes telles que std::unique_ptr car les membres de la classe ont fait des classes non copiables des bêtes beaucoup plus communes qu'avant.

20
P-Gn

user-declared signifie soit fourni par l'utilisateur ( défini par l'utilisateur), explicitement par défaut (= default) ou explicitement supprimé (= delete) contrairement à implicitement par défaut/supprimé (comme votre constructeur de déplacement).

Donc dans votre cas, oui le constructeur de déplacement est implicitement supprimé parce que le copy-constructor est explicitement supprimé (et donc déclaré par l'utilisateur).

Cependant, dans ce cas particulier, comment supprimer le constructeur par défaut/le mess d'affectation déplacer le constructeur/l'affectation par défaut?

Ce ne serait pas le cas, mais la norme ne fait pas la différence entre ce cas et un cas compliqué.

La réponse la plus courte est que le fait d'avoir un constructeur de mouvement implicitement défini avec un constructeur de copie explicitement supprimé pourrait être dangereux dans certains cas, le même lorsque vous avez un destructeur défini par l'utilisateur et aucun défini par l'utilisateur constructeur de copie (voir règle de trois/cinq/zéro ). Maintenant, vous pouvez affirmer qu'un destructeur défini par l'utilisateur ne supprime pas le constructeur de copie, mais il s'agit simplement d'un défaut dans le langage qui ne peut pas être supprimé car il casserait beaucoup d'anciens ( mauvais) programme. Pour citer Bjarne Stroustrup:

Dans un monde idéal, je pense que nous déciderions de "pas de génération" par défaut et fournirons une notation très simple pour "donnez-moi toutes les opérations habituelles". [...] De plus, une politique "pas d'opérations par défaut" entraîne des erreurs de compilation (que nous devrions avoir un moyen facile de corriger), tandis qu'une politique de génération d'opérations par défaut entraîne des problèmes qui ne peuvent pas être détectés avant l'exécution.

Vous pouvez en savoir plus à ce sujet dans N3174 = 10-0164 .

Notez que la plupart des gens suivent règle de trois/cinq/zéro , et à mon avis, vous devriez. En supprimant implicitement le constructeur de déplacement par défaut, la norme vous "protège" contre les erreurs et aurait dû vous protéger longtemps auparavant en supprimant le constructeur de copie dans certains cas (voir l'article de Bjarne).

Lectures complémentaires si vous êtes intéressé:

Je pense que cette question a une importance pratique car la génération manuelle et esp. la maintenance de ces fonctions par défaut est sujette aux erreurs, tandis que dans le même temps, l'augmentation (juste) de l'utilisation de classes telles que std::unique_ptr car les membres de la classe ont fait des classes non copiables des bêtes beaucoup plus communes qu'avant.

Marquer le constructeur de déplacement comme explicitement par défaut résoudra ce problème:

class Foo {
public:
  Foo() = default;
  Foo(Foo const &) = delete;
  Foo(Foo&&) = default;
};

Vous obtenez un objet non copiable avec un constructeur de déplacement par défaut, et à mon avis, ces déclarations explicites sont meilleures que celles implicites (par exemple, en déclarant uniquement le constructeur de déplacement comme default sans supprimer le constructeur de copie).

23
Holt

Comme vous l'avez dit, du §12.8

Si la définition d'une classe X ne déclare pas explicitement un constructeur de déplacement, un sera implicitement déclaré comme défaut si et seulement si

  • X n'a ​​pas de constructeur de copie déclaré par l'utilisateur,

  • [...]

Notez le déclaré par l'utilisateur . Mais si vous regardez le §8.4.3:

Une définition de fonction du formulaire:

attribut-spécificateur-seqopt décl-spécificateur-seqopt déclarant virt-specifier-seqopt = supprimer;

est appelé supprimé définition. Une fonction avec une suppression définition est également appelée fonction supprimée.

Un programme qui fait référence à une fonction supprimée implicitement ou explicitement, autre que le déclarer, est mal formé.

Ainsi, la norme définit les fonctions deleted comme déclarées par l'utilisateur (comme vous pouvez le voir ci-dessus), même si elles sont deleted et ne peuvent pas être utilisées.

Ensuite, selon le §12.8, le constructeur de déplacement implicite n'est pas défini, en raison de la déclaration de l'utilisateur (avec = delete;) constructeur de copie.

2
Rakete1111