1742 votes

Quand utiliser des destructeurs virtuels ?

J'ai une solide compréhension de la plupart OOP mais la chose qui me perturbe le plus, ce sont les destructeurs virtuels.

Je pensais que le destructeur était toujours appelé, quoi qu'il arrive et pour chaque objet de la chaîne.

Quand devez-vous les rendre virtuels et pourquoi ?

7 votes

Voir ça : Destructeur virtuel

177 votes

Chaque destructeur en bas est appelé quoi qu'il arrive. virtual s'assure qu'il commence en haut plutôt qu'au milieu.

18 votes

1780voto

Luc Touraille Points 29252

Les destructeurs virtuels sont utiles lorsque vous pouvez potentiellement supprimer une instance d'une classe dérivée par le biais d'un pointeur vers la classe de base :

class Base 
{
    // some virtual methods
};

class Derived : public Base
{
    ~Derived()
    {
        // Do some important cleanup
    }
};

Ici, vous remarquerez que je n'ai pas déclaré le destructeur de Base comme étant virtual . Maintenant, regardons l'extrait suivant :

Base *b = new Derived();
// use b
delete b; // Here's the problem!

Puisque le destructeur de Base n'est pas virtual y b est un Base* pointant vers un Derived objet, delete b tiene comportement indéfini :

[En delete b ], si le type statique de la de l'objet à supprimer est différent de son type dynamique, le type statique statique est une classe de base du type dynamique de l'objet à supprimer supprimé et le type statique doit avoir un destructeur virtuel ou le type comportement est indé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îne une fuite de ressources.

En résumé, il faut toujours faire en sorte que les destructeurs des classes de base virtual alors qu'ils sont destinés à être manipulés de manière polymorphe.

Si vous voulez empêcher la suppression d'une instance par le biais d'un pointeur de classe de base, vous pouvez faire en sorte que le destructeur de la classe de base soit protégé et non virtuel ; ce faisant, le compilateur ne vous laissera pas appeler delete sur un pointeur de classe de base.

Vous pouvez en savoir plus sur la virtualité et le destructeur virtuel de la classe de base dans cet article de Herb Sutter .

227 votes

Cela expliquerait pourquoi j'ai eu des fuites massives en utilisant une usine que j'ai faite avant. Tout a un sens maintenant. Merci

14 votes

C'est un mauvais exemple car il n'y a pas de membres de données. Et si Base y Derived ont todos des variables de stockage automatique ? c'est-à-dire qu'il n'y a pas de code personnalisé "spécial" ou supplémentaire à exécuter dans le destructeur. Est-il possible alors de ne pas écrire de destructeur du tout ? Ou est-ce que la classe dérivée toujours a une fuite de mémoire ?

4 votes

279voto

Un constructeur virtuel n'est pas possible mais un destructeur virtuel est possible. Expérimentons.......

#include <iostream>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

Le code ci-dessus donne le résultat suivant :

Base Constructor Called
Derived constructor called
Base Destructor called

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 se produire. Pour faire la chose appropriée, nous devons rendre le destructeur de base virtuel. Voyons maintenant ce qui se passe dans ce qui suit :

#include <iostream>

using namespace std;

class Base
{ 
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    virtual ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

La sortie a changé comme suit :

Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called

Donc la destruction du pointeur de base (qui prend une allocation sur l'objet dérivé !) suit la règle de destruction, c'est-à-dire d'abord le dérivé, puis la base. D'autre part, il n'y a rien comme un constructeur virtuel.

1 votes

" virtual constructor is not possible " signifie que vous ne devez pas écrire de constructeur virtuel par vous-même. La construction d'un objet dérivé doit suivre la chaîne de construction de la dérivée à la base. Vous ne devez donc pas écrire le mot clé virtual pour votre constructeur. Merci

5 votes

@Murkantilisme, "les constructeurs virtuels ne peuvent pas être faits" est vrai en effet. Un constructeur ne peut pas être marqué comme virtuel.

1 votes

@cmeub, Mais il existe un idiome pour obtenir ce que vous voulez d'un constructeur virtuel. Voir parashift.com/c++-faq-lite/acteursvirtuels.html

212voto

Bill the Lizard Points 147311

Déclarer les destructeurs virtuels dans les classes de base polymorphes. Il s'agit du point 7 de l'ouvrage de Scott Meyers C++ efficace . Meyers continue en résumant que si une classe a cualquier virtuelle, elle doit avoir un destructeur virtuel, et que les classes qui ne sont pas conçues pour être des classes de base ou qui ne sont pas conçues pour être utilisées de manière polymorphe doivent avoir un destructeur virtuel. no déclarer des destructeurs virtuels.

19 votes

+"Si une classe a une fonction virtuelle quelconque, elle doit avoir un destructeur virtuel, et que les classes qui ne sont pas conçues pour être des classes de base ou qui ne sont pas conçues pour être utilisées de manière polymorphe ne doivent pas déclarer de destructeurs virtuels" : Existe-t-il des cas dans lesquels il est judicieux d'enfreindre cette règle ? Sinon, serait-il judicieux que le compilateur vérifie cette condition et émette une erreur si elle n'est pas satisfaite ?

0 votes

@Giorgio Je ne connais pas d'exception à la règle. Mais je ne me considère pas comme un expert en C++, donc vous pouvez poster cette question séparément. Un avertissement du compilateur (ou un avertissement d'un outil d'analyse statique) me semble logique.

12 votes

Les classes peuvent être conçues pour ne pas être supprimées par le biais d'un pointeur d'un certain type, tout en ayant des fonctions virtuelles - l'exemple typique est une interface de callback. On ne supprime pas son implémentation par le biais d'un pointeur d'interface de rappel, car cela ne sert qu'à s'abonner, mais elle possède des fonctions virtuelles.

49voto

BigSandwich Points 1372

Sachez également que la suppression d'un pointeur de classe de base lorsqu'il n'y a pas de destructeur virtuel entraînera l'apparition de comportement indéfini . Une chose que j'ai apprise tout récemment :

Comment doit se comporter la surcharge de delete en C++ ?

J'utilise le C++ depuis des années et j'arrive encore à me pendre.

0 votes

J'ai jeté un coup d'œil à votre question et j'ai vu que vous aviez déclaré le destructeur de base comme virtuel. Par conséquent, l'expression "supprimer un pointeur de classe de base lorsqu'il n'y a pas de destructeur virtuel entraînera un comportement indéfini" reste-t-elle valable par rapport à votre question ? Puisque, dans cette question, lorsque vous avez appelé delete, la classe dérivée (créée par son opérateur new) est d'abord vérifiée pour une version compatible. Comme elle en a trouvé une, elle a été appelée. Donc, ne pensez-vous pas qu'il serait préférable de dire que "la suppression d'un pointeur de classe de base lorsqu'il n'y a pas de destructeur entraînera un comportement non défini" ?

0 votes

C'est à peu près la même chose. Le constructeur par défaut n'est pas virtuel.

0 votes

@BigSandwich "me pendre" ? Tu veux dire une fuite de mémoire ?

44voto

yesraaj Points 12759

Rendez le destructeur virtuel lorsque votre classe est polymorphe.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X