Avec les fonctions membres explicitement supprimées dans C++ 11, est-il toujours utile d'hériter d'une classe de base non copiable?
Je parle de l'astuce où vous héritez en privé d'une classe de base qui a un constructeur de copie privé ou supprimé et une affectation de copie (par exemple boost::noncopyable
).
Les avantages mis en avant dans cette question sont-ils toujours applicables à C++ 11?
Je ne comprends pas pourquoi certaines personnes prétendent qu'il est plus facile de rendre une classe non copiable en C++ 11.
En C++ 03:
private:
MyClass(const MyClass&) {}
MyClass& operator=(const MyClass&) {}
En C++ 11:
MyClass(const MyClass&) = delete;
MyClass& operator=(const MyClass&) = delete;
MODIFIER:
Comme beaucoup de gens l'ont souligné, c'était une erreur de fournir des corps vides (c'est-à-dire {}) pour le constructeur de copie privée et l'opérateur d'affectation de copie, car cela permettrait à la classe elle-même d'invoquer ces opérateurs sans aucune erreur. J'ai commencé par ne pas ajouter le {}, mais j'ai rencontré quelques problèmes avec l'éditeur de liens qui m'ont fait ajouter le {} pour une raison stupide (je ne me souviens pas des circonstances). Je sais mieux savoir. :-)
Bon ça:
private:
MyClass(const MyClass&) {}
MyClass& operator=(const MyClass&) {}
Techniquement, permet toujours à MyClass
d'être copié par les membres et amis. Bien sûr, ces types et fonctions sont théoriquement sous votre contrôle, mais la classe est toujours copiable. Au moins avec boost::noncopyable
Et = delete
, personne peut copier la classe.
Je ne comprends pas pourquoi certaines personnes prétendent qu'il est plus facile de rendre une classe non copiable en C++ 11.
Ce n'est pas tant "plus facile" que "plus facilement digestible".
Considère ceci:
class MyClass
{
private:
MyClass(const MyClass&) {}
MyClass& operator=(const MyClass&) {}
};
Si vous êtes un programmeur C++ qui a lu un texte d'introduction sur C++, mais a peu d'exposition au C++ idiomatique (c'est-à-dire: un lot de programmeurs C++), c'est ... déroutant. Il déclare les constructeurs de copie et les opérateurs d'affectation de copie, mais ils sont vides. Alors pourquoi les déclarer? Oui, ils sont private
, mais cela ne soulève que plus questions: pourquoi les rendre privés?
Pour comprendre pourquoi cela empêche la copie, vous devez réaliser qu'en les déclarant privés, vous faites en sorte que les non-membres/amis ne puissent pas les copier. Ce n'est pas immédiatement évident pour le novice. Le message d'erreur qu'ils obtiennent lorsqu'ils essaient de le copier n'est pas non plus.
Maintenant, comparez-le à la version C++ 11:
class MyClass
{
public:
MyClass(const MyClass&) = delete;
MyClass& operator=(const MyClass&) = delete;
};
Que faut-il pour comprendre que cette classe ne peut pas être copiée? Rien de plus que de comprendre ce que signifie la syntaxe = delete
. Tout livre expliquant les règles de syntaxe de C++ 11 vous dira exactement ce que cela fait. L'effet de ce code est évident pour l'utilisateur C++ inexpérimenté.
Ce qui est génial avec cet idiome, c'est qu'il devient un idiome parce que c'est la façon la plus claire et la plus évidente de dire exactement ce que vous voulez dire.
Même boost::noncopyable
Nécessite un peu plus de réflexion. Oui, cela s'appelle "non copiable", donc c'est auto-documenté. Mais si vous ne l'avez jamais vu auparavant, cela soulève des questions. Pourquoi dérivez-vous de quelque chose qui ne peut pas être copié? Pourquoi mes messages d'erreur parlent-ils du constructeur de copie de boost::noncopyable
? Etc. Encore une fois, la compréhension de l'idiome nécessite plus d'effort mental.
La première chose est que, comme le soulignent d'autres avant moi, vous avez mal utilisé l'idiome, vous déclarez privé et ne le faites pas définir :
class noncopyable {
noncopyable( noncopyable const & );
noncopyable& operator=( noncopyable const & );
};
Où le type de retour du operator=
peut être pratiquement n'importe quoi. À ce stade, si vous lisez ce code dans un en-tête, que signifie-t-il réellement? Il ne peut être copié que par des amis ou ne peut-il jamais être copié? Notez que si vous fournissez une définition comme dans votre exemple, cela indique que je ne peux être copié qu'à l'intérieur de la classe et par des amis , c'est le manque d'une définition ce qui se traduit par Je ne peux pas être copié . Mais l'absence d'une définition dans l'en-tête n'est pas synonyme d'un manque de définition partout , comme cela pourrait être défini dans le fichier cpp.
C'est là qu'hériter d'un type appelé non copiable rend explicite que l'intention est d'éviter les copies, de la même manière que si vous écriviez manuellement le code ci-dessus vous devez documenter avec un commentaire dans la ligne indiquant que l'intention est de désactiver les copies.
C++ 11 ne change rien à cela, il rend simplement la documentation explicite dans le code. Dans la même ligne que vous déclarez le constructeur de copie supprimé, vous êtes informé que vous le souhaitez complètement désactivé .
En dernier commentaire, la fonctionnalité en C++ 11 ne consiste pas seulement à écrire non copiable avec moins de code ou mieux, mais plutôt à inhiber le compilateur de générer du code que vous ne voulez pas générer. Ce n'est qu'une utilisation de cette fonctionnalité.
En plus des points soulevés par d'autres ...
Le fait d'avoir un constructeur de copie privée et un opérateur d'affectation de copie que vous ne définissez pas empêche quiconque de faire des copies. Cependant, si une fonction membre ou une fonction ami tente de faire une copie, ils recevront une erreur de temps de liaison. S'ils tentent de le faire là où vous avez explicitement supprimé ces fonctions, ils recevront une erreur de compilation.
Je fais toujours mes erreurs le plus tôt possible. Faire en sorte que les erreurs d'exécution se produisent au point d'erreur au lieu de plus tard (donc faites que l'erreur se produise lorsque vous modifiez la variable au lieu de la lire). Transformez toutes les erreurs d'exécution en erreurs au moment de la liaison afin que le code n'ait jamais la possibilité de se tromper. Transformez toutes les erreurs de temps de liaison en erreurs de compilation pour accélérer le développement et afficher des messages d'erreur légèrement plus utiles.
Il est plus lisible et permet au compilateur de donner de meilleures erreurs.
Le "supprimé" est plus clair pour un lecteur, surtout s'il survole la classe. De même, le compilateur peut vous dire que vous essayez de copier un type non copiable, au lieu de vous donner une erreur générique "essayant d'accéder au membre privé".
Mais vraiment, c'est juste une fonctionnalité pratique.
Les gens ici recommandent que vous déclariez les fonctions membres sans les définir. Je voudrais souligner qu'une telle approche n'est pas portable. Certains compilateurs/éditeurs de liens exigent que si vous déclarez une fonction membre, vous devez également la définir, même si elle n'est pas utilisée. Si vous utilisez uniquement VC++, GCC, clang, vous pouvez vous en tirer, mais si vous essayez d'écrire du code vraiment portable, certains autres compilateurs (par exemple Green Hills) échoueront.