J'étais en train de lire Professional C++ (5th edition) de Marc Gregoire lorsque j'ai vu qu'il conseillais de toujours déclarer les destructeurs comme virtuels.
Pourtant ça me paraît être une aberration... Pour moi, il me paraît logique que le destructeur doit être virtuel si et seulement si la classe est prévue pour être une base classe. Le destructeur de la base classe doit être virtuel notamment pour prévoir la destruction correcte de la derived classe dans ce cas de figure :
Mais sinon c'est inutile et néfaste d'un point de vue performance non ?
Pour l'instant j'utilise la façon de faire de Effective C++: 55 Specific Ways to Improve Your Programs and Designs (3rd edition) de Scott Meyers qui est de ne définir le destructeur comme virtuel uniquement lorsque la classe est héritée par des classes filles. Scott Meyers dit même que définir tous les destructeurs comme virtuels peut entraîner des problèmes de performance non-négligeables. Exemple : une petite classe de 2 int qui représente donc 64 bits en mémoire risque de faire 128 bits si on ajoute un destructeur virtuel et donc une vtable. Cela peut empêcher les instances de la classe d'être stockées dans les registres si la classe dépasse 64 bits et empêcher la portabilité avec d'autres langages comme le langage C qui ne possède pas le keyword virtuel.
Effective C++ est cependant un vieux livre... Il est sorti en 2005 et peut-être plus d'actualité avec le standard actuel.
Je suis aussi allé sur stackoverflow pour essayer de tirer ça au clair mais la plupart des topics datent d'avant le C++11... et à l'époque on dirait que les discussions tournaient principalement autour de l'absence d'un keyword final (qui existait déjà en Java) pour empêcher les classes avec des destructeurs non-virtuels d'être héritées... J'aimerais bien lancer un topic pour discuter de ce qu'il en est sur stackoverflow depuis l'arrivée du C++11 mais je suis question-banned depuis longtemps et même avec un nouveau pseudo, mon topic sera traité en tant que duplicate et je serai renvoyé vers les vieilles discussions pré-C++11.
Un expert ici pour débunker cette histoire ? Qui a raison entre Marc Gregoire et Scott Meyers ?
PS : je suis preneur si vous avez des recommandations de bons livres de C++ qui sont à la page (je connais déjà les basiques C++ Primer et Effective Modern C++)
- Edité par ThomasAirain 24 janvier 2024 à 23:40:31
J'étais en train de lire Professional C++ (5th edition) de Marc Gregoire lorsque j'ai vu qu'il conseillais de toujours déclarer les destructeurs comme virtuels.
Tu as vu ça où dans le livre ?
EDIT: ok, trouvé (je pense)
ThomasAirain a écrit:
The Need for virtual Destructors (p. 348)
Destructors should almost always be virtual. Making your destructors non-virtual can easily result in situations in which memory is not freed by object destruction. Only for a class that is marked as final could you make its destructor non-virtual.
Globalement, c'est la même idée : soit la classe est dérivable et le destructeur doit être virtuel, soit la classe ne l'est pas et devrait être final. Final n'existait pas encore a l'époque de Effective C++.
"Destructors should almost always be virtual. Making your destructors non-virtual can easily result in situations in which memory is not freed by object destruction. Only for a class that is marked as final could you make its destructor non-virtual."
"WARNING : Unless you have a specific reason not to or the class is marked as final, destructors should be marked as virtual."
On dirait qu'il ne dit pas explicitement de toujours mettre le destructeur comme virtuel mais il incite à mettre le destructeur quasiment toujours virtuel.
- Edité par ThomasAirain 25 janvier 2024 à 0:03:55
Destructors should almost always be virtual. Making your destructors non-virtual can easily result in situations in which memory is not freed by object destruction. Only for a class that is marked as final could you make its destructor non-virtual.
Globalement, c'est la même idée : soit la classe est dérivable et le destructeur doit être virtuel, soit la classe ne l'est pas et devrait être final. Final n'existait pas encore a l'époque de Effective C++.
Marc Gregoiredit à quelqu'un qui débute: si vous de voulez pas avoir de problèmes (de mauvaise destruction), utilisez un destructeur virtuel. Scott Meyers, est lui un expert qui s'adresse à des personnes averties. Il précise que c'est parfois non nécessaire, voire contre-productif.
Un destructeur protected, est suffisant dans certains cas. Un destructeur public et non virtual peut même parfois être suffisant. Mais il faut alors prendre d'autres précautions, ça devient vraiment "osé".
Aucun risque, car on n'héritera jamais de cette classe:
struct Base final {
};
Pas de risque (a priori, voir plus bas), on ne pourra pas détruire la classe Derivee dans un contexte où elle est référencée comme Base
struct Base { protected:
~Toto() = default;
};
Plus osé, si la Derivee est toujours créée via un std::shared_ptr<Derivee>:
struct Base {
//protected: ~Base() = default;
};
struct Derivee : public Base {
};
std::shared_ptr<Base> ptsh_base = std::make_shared<Derivee>(); // OK
std::unique_ptr<Base> ptun_base = std::make_unique<Derivee>(); // Aïe
Le shared_ptr<> est porteur du destructeur de Derivee, mais l'unique_ptr<> lui va appeler le destructeur de Base! Il faut a minima indiquer le destructeur de Base comme protected, l'unique_ptr<> ne compile alors plus, et le shared_ptr<> reste utilisable.
Et pour les 2 derniers exemples, si on ajoute un friend à Base, il pourra alors détruire une Base qui est en fait une Derivee, et on aura alors un problème.
Donc la réponse de Marc Gregoire est la plus sécuritaire, Celle de Scott Meyers nécessite une lucidité supplémentaire.
Systématiser les destructeurs virtuels est assez ridicule en ce qui me concerne -- je range ça dans la même catégorie que définir toutes les fonctions comme virtuelles "parce que on ne sait pas où on va". Le dtr virtuel répond à un besoin bien précis. Si dans le doute il faut une règle simplificatrice: "s'il y a un virtuel quelques part => destructeur public et virtuel (ou protégé et non-virtuel)". Et aussi penser à activer les warnings!
Ce que je systématise en revanche, c'est l'interdiction de copie dès qu'il y a un virtual.
Dès que je vois des classes copiables avec un destructeur virtuel, si c'est sur du code que je maintiens, je nettoie toujours. Copiabilité ou virtualité, il n'en restera qu'un.
En règle post 2011, je rajouterai qu'à défaut d'écrire ses propres capsules RAII (ou des scoped_logger), un bon destructeur est defaulté, ou non indiqué.
Quant à final, je m'étais posé la question de flagguer ainsi toutes mes classes valeurs. Seul problème, cela interdit l'import de code via héritage privé. Qui lui a parfois du sens. Du coup, mes classes valeur ne sont jamais finales. Et donc clairement, je ne suis pas d'accord avec la recommandation simplifiée de l'auteur.
@lmghs Je pense pareil que toi. Pour moi, le destructeur ne doit pas toujours être virtuel sauf dans le cas d'une base classe où là cela a du sens car il y aura la présence de l'héritage.
Je viens de relire C++ Primer, qui est souvent considéré comme la référence loin devant les autres du C++, et il est beaucoup plus évasif que Professional C++ sur la question.
C++ Primer indique clairement que le destructeur doit être virtuel lorsqu'il y a héritage. Donc cela est admis.
En revanche il n'indique pas les exceptions à cette règle. Il indique qu'une base class doit "généralement" définir un destructeur virtuel. Donc C++ Primer insinue que ce n'est pas une règle qui est toujours à appliquer (du coup ça aurait pu être bien d'indiquer QUAND le destructeur ne doit pas être virtuel mais bon passons).
Quand au mot clé final, C++ Primer dit que le programmeur PEUT l'utiliser mais il ne recommande pas spécialement, ni n'oblige spécialement le programmeur à l'utiliser.
En gros, je trouve ce que dit C++ Primer est tout à fait juste mais le livre ne se mouille pas trop. C++ Primer dit qu'il faut utiliser le destructeur virtuel et le mot clé final lorsque cela est opportun mais cela est sujet à interprétation d'où l'éclosion des débats sur ce sujet.
- Edité par ThomasAirain 3 février 2024 à 23:27:06
Ce genre de considérations avec règles et étude des exceptions à la règle, tu les trouveras ailleurs. Typiquement, dans le /C++ Coding Style/ de Sutter & Alexandrescu, qui a atterri dans les C++ Core Guidelines.
Après, "le virtual ou copie il faut choisir", il est présent en filigrane dans les C++ Core Guideline, mais c'est surtout dans la communauté francophone qu'on le rabâche.
Les recommandations dans les ouvrages anglophones ça sera juste: "si pas besoin de polymorphisme dynamique, alors pas de virtual". Et encore, ce n'est pas une évidence/un réflexe pour tous avec les choix de certains langages (Java, Python...). Pire, à une époque j'ai vu le "foutons des virtuels partout, ça sera plus simple pour la suite".
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Discord NaN. Mon site.
Discord NaN. Mon site.
Discord NaN. Mon site.
Discord NaN. Mon site.