J'ai une solide compréhension de la plupart des théories OO, mais la seule chose qui me déroute beaucoup, ce sont les destructeurs virtuels.
J'ai pensé que le destructeur est toujours appelé, peu importe quoi et pour chaque objet de la chaîne.
Quand êtes-vous censé les rendre virtuels et pourquoi?
virtual
s'assure qu'il commence en haut au lieu du milieu.Réponses:
Les destructeurs virtuels sont utiles lorsque vous pouvez potentiellement supprimer une instance d'une classe dérivée via un pointeur vers la classe de base:
Ici, vous remarquerez que je n'ai pas déclaré le destructeur de Base
virtual
. Voyons maintenant l'extrait de code suivant:Depuis la base de destructor n'est pas
virtual
etb
est unBase*
pointage à unDerived
objet,delete b
a un comportement non défini :Dans la plupart des implémentations, l'appel au destructeur sera résolu comme tout code non virtuel, ce qui signifie que le destructeur de la classe de base sera appelé mais pas celui de la classe dérivée, ce qui entraînera une fuite de ressources.
Pour résumer, faites toujours les destructeurs des classes de base
virtual
quand ils sont destinés à être manipulés de manière polymorphe.Si vous souhaitez empêcher la suppression d'une instance via un pointeur de classe de base, vous pouvez rendre le destructeur de classe de base protégé et non virtuel; ce faisant, le compilateur ne vous permettra pas d'appeler
delete
un pointeur de classe de base.Vous pouvez en savoir plus sur la virtualité et le destructeur de classe de base virtuelle dans cet article de Herb Sutter .
la source
Base
etDerived
ont toutes les variables de stockage automatiques? c'est-à-dire qu'il n'y a pas de code personnalisé "spécial" ou supplémentaire à exécuter dans le destructeur. Est-il alors acceptable de ne pas écrire de destructeurs? Ou la classe dérivée aura-t-elle toujours une fuite de mémoire?Un constructeur virtuel n'est pas possible mais un destructeur virtuel est possible. Laissez-nous expérimenter .......
Le code ci-dessus génère les informations suivantes:
La construction de l'objet dérivé suit la règle de construction mais lorsque nous supprimons le pointeur "b" (pointeur de base), nous avons constaté que seul le destructeur de base est appelé. Mais cela ne doit pas arriver. Pour faire ce qu'il faut, nous devons rendre le destructeur de base virtuel. Voyons maintenant ce qui se passe dans ce qui suit:
La sortie a changé comme suit:
Ainsi, la destruction du pointeur de base (qui prend une allocation sur un objet dérivé!) Suit la règle de destruction, c'est-à-dire d'abord le dérivé, puis la base. D'un autre côté, rien de tel qu'un constructeur virtuel.
la source
Déclarez les destructeurs virtuels dans les classes de base polymorphes. Il s'agit de l'article 7 du C ++ efficace de Scott Meyers . Meyers poursuit en résumant que si une classe a une fonction virtuelle, elle devrait avoir un destructeur virtuel, et que les classes non conçues pour être des classes de base ou non conçues pour être utilisées de manière polymorphe ne devraient pas déclarer de destructeurs virtuels.
la source
const Base& = make_Derived();
. Dans ce cas, le destructeur de laDerived
valeur sera appelé, même s'il n'est pas virtuel, donc on économise la surcharge introduite par vtables / vpointers. Bien entendu, la portée est assez limitée. Andrei Alexandrescu l'a mentionné dans son livre Modern C ++ Design .Sachez également que la suppression d'un pointeur de classe de base en l'absence de destructeur virtuel entraînera un comportement indéfini . Quelque chose que j'ai appris récemment:
Comment la suppression de la suppression en C ++ devrait-elle se comporter?
J'utilise C ++ depuis des années et je parviens toujours à me pendre.
la source
Rendez le destructeur virtuel chaque fois que votre classe est polymorphe.
la source
Appel de destructeur via un pointeur vers une classe de base
L'appel du destructeur virtuel n'est pas différent de tout autre appel de fonction virtuelle.
Pour
base->f()
, l'appel sera envoyé àDerived::f()
, et il en va de même pourbase->~Base()
- sa fonction prioritaire - leDerived::~Derived()
sera appelé.La même chose se produit lorsque le destructeur est appelé indirectement, par exemple
delete base;
. Ladelete
déclaration appellerabase->~Base()
qui sera envoyée àDerived::~Derived()
.Classe abstraite avec destructeur non virtuel
Si vous n'allez pas supprimer un objet via un pointeur vers sa classe de base - il n'est pas nécessaire d'avoir un destructeur virtuel. Faites en
protected
sorte qu'il ne soit pas appelé accidentellement:la source
~Derived()
dans toutes les classes dérivées, même si c'est juste~Derived() = default
? Ou est-ce que cela est impliqué par la langue (ce qui permet de l'omettre en toute sécurité)?protected
section, ou pour s'assurer qu'il est virtuel en utilisantoverride
.J'aime penser aux interfaces et aux implémentations d'interfaces. En langage C ++, l'interface est une classe virtuelle pure. Destructor fait partie de l'interface et devrait être implémenté. Par conséquent, le destructeur doit être purement virtuel. Et le constructeur? Le constructeur ne fait en fait pas partie de l'interface car l'objet est toujours instancié explicitement.
la source
virtual
dans une classe de base, il est automatiquementvirtual
dans une classe dérivée, même s'il ne l'est pas.Un mot-clé virtuel pour le destructeur est nécessaire lorsque vous souhaitez que différents destructeurs suivent l'ordre approprié pendant que les objets sont supprimés via le pointeur de classe de base. par exemple:
Si votre destructeur de classe de base est virtuel, les objets seront détruits dans un ordre (objet dérivé d'abord puis base). Si votre destructeur de classe de base n'est PAS virtuel, seul l'objet de classe de base sera supprimé (car le pointeur est de la classe de base "Base * myObj"). Il y aura donc une fuite de mémoire pour l'objet dérivé.
la source
Pour être simple, Virtual destructor consiste à détruire les ressources dans un ordre approprié, lorsque vous supprimez un pointeur de classe de base pointant vers un objet de classe dérivé.
la source
delete
un pointeur de base conduit à un comportement indéfini.Les destructeurs de classes de base virtuelles sont des "meilleures pratiques" - vous devez toujours les utiliser pour éviter les fuites de mémoire (difficiles à détecter). En les utilisant, vous pouvez être sûr que tous les destructeurs de la chaîne d'héritage de vos classes sont appelés (dans le bon ordre). L'héritage d'une classe de base à l'aide du destructeur virtuel rend automatiquement le destructeur de la classe héritée virtuel (vous n'avez donc pas besoin de retaper «virtuel» dans la déclaration du destructeur de classe héritée).
la source
Si vous utilisez
shared_ptr
(uniquement shared_ptr, pas unique_ptr), vous n'avez pas besoin d'avoir le destructeur de classe de base virtuel:production:
la source
virtual
mot-clé pourrait vous éviter bien des souffrances.Qu'est-ce qu'un destructeur virtuel ou comment utiliser le destructeur virtuel
Un destructeur de classe est une fonction portant le même nom que la classe précédant de ~ qui réallouera la mémoire allouée par la classe. Pourquoi nous avons besoin d'un destructeur virtuel
Voir l'exemple suivant avec quelques fonctions virtuelles
L'exemple indique également comment convertir une lettre en majuscule ou en minuscule
Dans l'exemple ci-dessus, vous pouvez voir que le destructeur des classes MakeUpper et MakeLower n'est pas appelé.
Voir l'exemple suivant avec le destructeur virtuel
Le destructeur virtuel appellera explicitement le destructeur de temps d'exécution le plus dérivé de la classe afin de pouvoir effacer l'objet de manière appropriée.
Ou visitez le lien
https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138
la source
lorsque vous devez appeler un destructeur de classe dérivé à partir de la classe de base. vous devez déclarer le destructeur de classe de base virtuelle dans la classe de base.
la source
Je pense que le cœur de cette question concerne les méthodes virtuelles et le polymorphisme, pas spécifiquement le destructeur. Voici un exemple plus clair:
Imprime:
Sans
virtual
cela, il imprimera:Et maintenant, vous devez comprendre quand utiliser des destructeurs virtuels.
la source
B b{}; A& a{b}; a.foo();
. La vérificationNULL
- qui devrait êtrenullptr
- avantdelete
ing - avec une indentation incorrecte - n'est pas requise:delete nullptr;
est définie comme un no-op. Si quelque chose, vous devriez avoir vérifié cela avant d'appeler->foo()
, sinon un comportement non défini peut se produire si le problème anew
échoué.)delete
unNULL
pointeur (c'est-à-dire que vous n'avez pas besoin duif (a != NULL)
garde).J'ai pensé qu'il serait bénéfique de discuter du comportement "indéfini", ou du moins du comportement "crash" indéfini qui peut se produire lors de la suppression via une classe de base (/ struct) sans destructeur virtuel, ou plus précisément sans vtable. Le code ci-dessous énumère quelques structures simples (la même chose serait vraie pour les classes).
Je ne dis pas si vous avez besoin de destructeurs virtuels ou non, bien que je pense qu'en général, c'est une bonne pratique de les avoir. Je souligne juste la raison pour laquelle vous pouvez vous retrouver avec un crash si votre classe de base (/ struct) n'a pas de table virtuelle et votre classe dérivée (/ struct) en a et vous supprimez un objet via une classe de base (/ struct) aiguille. Dans ce cas, l'adresse que vous transmettez à la routine libre du tas n'est pas valide et donc la raison du plantage.
Si vous exécutez le code ci-dessus, vous verrez clairement quand le problème se produit. Lorsque le pointeur this de la classe de base (/ struct) est différent du pointeur this de la classe dérivée (/ struct), vous allez rencontrer ce problème. Dans l'exemple ci-dessus, les structures a et b n'ont pas de vtables. les structures c et d ont des vtables. Ainsi, un pointeur a ou b vers une instance d'objet ac ou d sera fixé pour tenir compte de la table virtuelle. Si vous passez ce pointeur a ou b pour le supprimer, il se bloquera car l'adresse n'est pas valide pour la routine libre du tas.
Si vous prévoyez de supprimer des instances dérivées qui ont des tables vtables des pointeurs de classe de base, vous devez vous assurer que la classe de base a une table vtable. Une façon de le faire consiste à ajouter un destructeur virtuel, que vous voudrez peut-être de toute façon nettoyer correctement les ressources.
la source
Une définition de base
virtual
consiste à déterminer si une fonction membre d'une classe peut être remplacée dans ses classes dérivées.Le D-tor d'une classe est appelé essentiellement à la fin de la portée, mais il y a un problème, par exemple lorsque nous définissons une instance sur le tas (allocation dynamique), nous devons la supprimer manuellement.
Dès que l'instruction est exécutée, le destructeur de classe de base est appelé, mais pas pour le dérivé.
Un exemple pratique est quand, dans le champ de contrôle, vous devez manipuler des effecteurs, des actionneurs.
À la fin de la portée, si le destructeur de l'un des éléments de puissance (actionneur) n'est pas appelé, il y aura des conséquences fatales.
la source
Toute classe héritée publiquement, polymorphe ou non, doit avoir un destructeur virtuel. En d'autres termes, si elle peut être pointée par un pointeur de classe de base, sa classe de base devrait avoir un destructeur virtuel.
S'il est virtuel, le destructeur de classe dérivé est appelé, puis le constructeur de classe de base. S'il n'est pas virtuel, seul le destructeur de classe de base est appelé.
la source