web-dev-qa-db-fra.com

La règle de trois devient la règle de cinq avec C ++ 11?

Ainsi, après avoir regardé cette magnifique conférence sur les références rvalue, je pensais que chaque classe bénéficierait d’un tel "constructeur de mouvements", template<class T> MyClass(T&& other) edit et bien sûr un "opérateur d'assignation de déplacement", template<class T> MyClass& operator=(T&& other) comme le souligne Philipp dans sa réponse, s'il a alloué des membres de manière dynamique, ou stocke généralement des pointeurs. Tout comme vous devriez avoir un copieur-copieur, un opérateur d’attribution et un destructeur si les points mentionnés précédemment s’appliquent. Pensées?

324
Xeo

Je dirais que la règle de trois devient la règle de trois, quatre et cinq:

Chaque classe doit explicitement définir exactement l’un des ensembles de fonctions membres suivantes:

  • None
  • Destructeur, constructeur de copie, opérateur d'affectation de copie

En outre, chaque classe qui définit explicitement un destructeur peut définir explicitement un constructeur de déplacement et/ou un opérateur d’affectation de déplacement.

Généralement, l’un des ensembles suivants de fonctions membres spéciales est utile:

  • None (pour de nombreuses classes simples où les fonctions de membre spéciales générées implicitement sont correctes et rapides)
  • Destructeur, constructeur de copie, opérateur d'affectation de copie (dans ce cas, la classe ne sera pas déplaçable)
  • Destructeur, constructeur de déplacement, opérateur d'affectation de déplacement (dans ce cas, la classe ne sera pas copiable, utile pour les classes de gestion de ressources où la ressource sous-jacente n'est pas copiable)
  • Destructeur, constructeur de copie, opérateur d'affectation de copie, constructeur de déplacement (en raison de l'élision de la copie, il n'y a pas de surcharge si l'opérateur d'affectation de copie prend son argument par valeur)
  • Destructeur, constructeur de copie, opérateur d'affectation de copie, constructeur de déplacement, opérateur d'affectation de déplacement

Notez que le constructeur de mouvement et l'opérateur d'affectation de déplacement ne seront pas générés pour une classe qui déclare explicitement l'une des autres fonctions membres spéciales. Cet constructeur de copie et cet opérateur d'affectation de copie ne seront pas générés pour une classe qui déclare explicitement un constructeur de déplacement ou un déplacement. opérateur d’affectation, et qu’une classe avec un destructeur explicitement déclaré et un constructeur de copie défini implicitement ou un opérateur d’affectation de copie défini implicitement est considérée comme obsolète. En particulier, la classe de base polymorphe C++ 03 parfaitement valide suivante:

class C {
  virtual ~C() { }   // allow subtype polymorphism
};

devrait être réécrit comme suit:

class C {
  C(const C&) = default;               // Copy constructor
  C(C&&) = default;                    // Move constructor
  C& operator=(const C&) = default;  // Copy assignment operator
  C& operator=(C&&) = default;       // Move assignment operator
  virtual ~C() { }                     // Destructor
};

Un peu gênant, mais probablement meilleur que l’alternative (génération automatique de toutes les fonctions membres spéciales).

Contrairement à la règle des Trois Grands, où le non-respect de la règle peut causer de graves dommages, ne pas déclarer explicitement le constructeur de déplacement et l'opérateur d'affectation de déplacement est généralement correct, mais souvent sous-optimal en termes d'efficacité. Comme indiqué ci-dessus, les opérateurs de déplacement et d'assignation de déplacement ne sont générés que s'il n'y a pas de constructeur de copie, d'opérateur d'attribution de copie ou de destructeur explicitement déclaré. Ce comportement n'est pas symétrique par rapport au comportement traditionnel de C++ 03 en ce qui concerne la génération automatique du constructeur de copie et l'opérateur d'affectation de copie, mais il est beaucoup plus sûr. Ainsi, la possibilité de définir des constructeurs de déplacement et des opérateurs d’affectation de déplacement est très utile et crée de nouvelles possibilités (classes uniquement mobiles), mais les classes qui adhèrent à la règle C++ 03 du Big Three fonctionneront toujours.

Pour les classes de gestion de ressources, vous pouvez définir le constructeur de copie et l'opérateur d'affectation de copie comme supprimés (qui comptent comme définition) si la ressource sous-jacente ne peut pas être copiée. Souvent, vous souhaitez toujours déplacer le constructeur et déplacer l'opérateur d'affectation. Les opérateurs d'assignation de copie et de déplacement seront souvent implémentés avec swap, comme en C++ 03. Si vous avez un constructeur de déplacement et un opérateur d'affectation de déplacement, la spécialisation std::swap n'aura plus aucune importance, car le nom générique std::swap utilise le constructeur de déplacement et l'opérateur de l'affectation de déplacement, le cas échéant, et cela devrait être assez rapide.

Les classes qui ne sont pas destinées à la gestion des ressources (c'est-à-dire, aucun destructeur non vide) ou au polymorphisme de sous-type (c'est-à-dire, aucun destructeur virtuel) ne doivent déclarer aucune des cinq fonctions membres spéciales; ils seront tous générés automatiquement et se comporteront correctement et rapidement.

310
Philipp

Je ne peux pas croire que personne ne soit lié à this .

Fondamentalement, l'article plaide en faveur de la "règle de zéro". Il n'est pas approprié que je cite l'article en entier, mais je crois que c'est le point principal:

Les classes comportant des destructeurs personnalisés, des constructeurs copie/déplacement ou des opérateurs d'affectation copie/déplacement doivent traiter exclusivement avec la propriété. Les autres classes ne doivent pas avoir de destructeurs personnalisés, de constructeurs copier/déplacer ni d’opérateurs d’affectation copier/déplacer.

Aussi ce bit est important à mon humble avis:

Des classes communes "propriété dans un paquet" sont incluses dans la bibliothèque standard: std::unique_ptr et std::shared_ptr. Grâce à l'utilisation d'objets deleter personnalisés, les deux ont été rendus suffisamment flexibles pour gérer pratiquement tout type de ressource.

67
NoSenseEtAl

Je ne pense pas, la règle de trois est une règle générale qui stipule qu'une classe qui implémente l'un des éléments suivants, mais pas tous, est probablement un buggy.

  1. Copier le constructeur
  2. Opérateur d'assignation
  3. Destructeur

Cependant, laisser le constructeur ou l'opérateur d'assignation de déplacement ne signifie pas un bogue. Cela peut être une opportunité manquée lors de l'optimisation (dans la plupart des cas) ou que la sémantique du déplacement ne soit pas pertinente pour cette classe, mais qu'il ne s'agit pas d'un bogue.

Il peut être préférable de définir un constructeur de déplacement lorsque cela est pertinent, mais ce n'est pas obligatoire. Il existe de nombreux cas dans lesquels un constructeur de déplacement n'est pas pertinent pour une classe (par exemple, std::complex) et toutes les classes qui se comportent correctement en C++ 03 continueront de se comporter correctement en C++ 0x, même si elles n'en ont pas. t définir un constructeur de déménagement.

18
Motti

Oui, je pense que ce serait bien de fournir un constructeur de déménagement pour de telles classes, mais rappelez-vous que:

  • Ce n'est qu'une optimisation.

    L'implémentation d'un ou deux constructeurs de copie, d'opérateurs d'assignation ou de destructeurs sera probablement source de bogues, mais l'absence de constructeur de déplacement réduira potentiellement les performances.

  • Le constructeur de déplacement ne peut pas toujours être appliqué sans modifications.

    Certaines classes ont toujours leurs pointeurs attribués, et donc ces classes suppriment toujours leurs pointeurs dans le destructeur. Dans ces cas, vous devrez ajouter des vérifications supplémentaires pour indiquer si leurs pointeurs sont alloués ou ont été déplacés (sont maintenant nuls).

14
peoro

Voici une brève mise à jour sur l'état actuel et les développements connexes depuis le 24 janvier 2011.

Selon la norme C++ 11 (voir l’annexe D [depr.impldec]):

La déclaration implicite d'un constructeur de copie est obsolète si la classe a un opérateur d'affectation de copie déclaré par l'utilisateur ou un destructeur déclaré par l'utilisateur. La déclaration implicite d'un opérateur d'affectation de copie est déconseillée si la classe dispose d'un constructeur de copie déclaré par l'utilisateur ou d'un destructeur déclaré par l'utilisateur.

Il était en fait proposé de rendre obsolète le comportement déconseillé , donnant à C++ 14 une véritable "règle de cinq" au lieu de la "règle de trois" traditionnelle. En 2013, le groupe de travail électronique a voté contre cette proposition devant être mise en œuvre en C++ 2014. La principale justification de la décision sur la proposition avait trait aux préoccupations générales quant à la violation du code existant.

Récemment, il a été proposé d'adapter à nouveau le libellé de C++ 11 afin de respecter la règle informelle des cinq, à savoir que

aucune fonction de copie, fonction de déplacement ou destructeur ne sera généré par le compilateur si l'une de ces fonctions est fournie par l'utilisateur.

Si elle est approuvée par le groupe de travail électronique, la "règle" sera probablement adoptée pour C++ 17.

8
Andrey Rekalo

En gros, c'est comme ça: si vous ne déclarez aucune opération de déplacement, vous devez respecter la règle des trois. Si vous déclarez une opération de déplacement, il n'y a pas de mal à "violer" la règle de trois, car la génération d'opérations générées par le compilateur est devenue très restrictive. Même si vous ne déclarez pas les opérations de déplacement et ne respectez pas la règle de trois, un compilateur C++ 0x vous avertira au cas où une fonction spéciale aurait été déclarée par l'utilisateur et que d'autres fonctions spéciales auraient été générées automatiquement à cause d'un maintenant obsolète "règle de compatibilité C++ 03".

Je pense qu'il est prudent de dire que cette règle devient un peu moins importante. Le vrai problème en C++ 03 est que, pour implémenter différentes sémantiques de copie, il était nécessaire de déclarer tous toutes les fonctions spéciales associées, de sorte qu'aucune d'entre elles ne soit générée par le compilateur (ce qui autrement ferait une erreur). Mais C++ 0x modifie les règles relatives à la génération de fonctions de membre spéciales. Si l'utilisateur déclare seulement une de ces fonctions pour changer la sémantique de la copie, cela empêchera le compilateur de générer automatiquement les fonctions spéciales restantes. Cela est utile car une déclaration manquante transforme une erreur d'exécution en une erreur de compilation (ou au moins un avertissement). En tant que mesure de compatibilité C++ 03, certaines opérations sont toujours générées, mais cette génération est considérée comme obsolète et devrait au moins produire un avertissement en mode C++ 0x.

En raison des règles plutôt restrictives relatives aux fonctions spéciales générées par le compilateur et à la compatibilité C++ 03, la règle de trois reste la règle de trois.

Voici quelques exemples qui devraient convenir aux dernières règles C++ 0x:

template<class T>
class unique_ptr
{
   T* ptr;
public:
   explicit unique_ptr(T* p=0) : ptr(p) {}
   ~unique_ptr();
   unique_ptr(unique_ptr&&);
   unique_ptr& operator=(unique_ptr&&);
};

Dans l'exemple ci-dessus, il n'est pas nécessaire de déclarer l'une des autres fonctions spéciales comme étant supprimée. Ils ne seront tout simplement pas générés en raison des règles restrictives. La présence d'opérations de déplacement déclarées par l'utilisateur désactive les opérations de copie générées par le compilateur. Mais dans un cas comme celui-ci:

template<class T>
class scoped_ptr
{
   T* ptr;
public:
   explicit scoped_ptr(T* p=0) : ptr(p) {}
   ~scoped_ptr();
};

un compilateur C++ 0x est maintenant censé générer un avertissement concernant les opérations de copie éventuellement générées par le compilateur et susceptibles d’être incorrectes. Ici, la règle de trois points doit être respectée. Dans ce cas, un avertissement est tout à fait approprié et donne à l'utilisateur la possibilité de gérer le bogue. Nous pouvons nous débarrasser du problème via des fonctions supprimées:

template<class T>
class scoped_ptr
{
   T* ptr;
public:
   explicit scoped_ptr(T* p=0) : ptr(p) {}
   ~scoped_ptr();
   scoped_ptr(scoped_ptr const&) = delete;
   scoped_ptr& operator=(scoped_ptr const&) = delete;
};

Ainsi, la règle de trois s'applique toujours ici simplement en raison de la compatibilité C++ 03.

4
sellibitze

Nous ne pouvons pas dire que la règle de 3 devienne la règle de 4 (ou 5) maintenant sans rompre tout le code existant qui applique la règle de 3 et n'implémente aucune forme de sémantique de déplacement.

La règle de 3 signifie que si vous en implémentez un, vous devez implémenter les 3.

Aussi pas au courant, il y aura un mouvement généré automatiquement. La "règle de 3" a pour objectif leur existence automatique. Si vous en implémentez une, il est fort probable que l'implémentation par défaut des deux autres est fausse.

3
CashCow

Dans le cas général, alors oui, la règle de trois vient de devenir la règle de cinq, avec l'opérateur d'affectation de mouvement et le constructeur de mouvement ajoutés. Cependant, pas toutes les classes sont copiables et mobiles, certaines ne sont que mobiles , certains sont simplement copiables.

2
Puppy