Quand utiliser des destructeurs virtuels?

J'ai une solide compréhension de la plupart des théories OO, mais la seule chose qui me confond beaucoup, 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 êtes-vous censé les rendre virtuels et pourquoi?

1242
demandé sur j0k 2009-01-20 15:58:21

15 réponses

Les Destructeurs virtuels sont utiles lorsque vous pouvez supprimer une instance d'une classe dérivée via 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é que le Destructeur De La Base était virtual. Maintenant, jetons un coup d'oeil à l'extrait suivant:

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

Puisque le Destructeur De La Base n'est pas virtual et que b est un Base* pointant vers un objet Derived, delete b a un comportement indéfini :

[Dans delete b], si le type statique du objet à être supprimé est différent de son type dynamique, le statique le type est une classe de base du type dynamique de l'objet à supprimé et le type statique doit avoir un destructeur virtuel ou l' le comportement est indéfini.

Dans la plupart des implémentations, l'appel au destructeur sera résolu comme n'importe quel 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, entraînant une fuite de ressources.

, Pour résumer, toujours faites les destructeurs des classes de base virtual quand ils sont destinés à être manipulés polymorphiquement.

Si vous voulez empêcher la suppression d'une instance via un pointeur de classe de base, vous pouvez protéger le destructeur de classe de base et le rendre non virtuel; ce faisant, le compilateur ne vous laissera pas appeler delete sur un pointeur de classe de base.

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

1340
répondu Luc Touraille 2018-02-28 18:02:10

Déclare des destructeurs virtuels dans des classes de base polymorphes. C'est L'article 7 de Scott Meyers' Effective C++. Meyers continue 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 polymorphiquement devraientPas déclarer des destructeurs virtuels.

168
répondu Bill the Lizard 2009-01-20 13:11:51

Un constructeur virtuel n'est pas possible mais un destructeur virtuel est possible. Laissez-nous expérimenter....

#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 produit ce qui suit:

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 être arrivé. Pour faire la chose appropriée, nous devons rendre le destructeur de base virtuel. Maintenant voyons ce qui se passe dans le suivant:

#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é!) suivez la règle de destruction, c'est-à-dire d'abord le dérivé puis la base. D'un autre côté pour le constructeur, il n'y a rien de tel que le constructeur virtuel.

159
répondu Tunvir Rahman Tusher 2018-03-31 09:50:41

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

Comment remplacer delete en C++ devrait-il se comporter?

J'utilise C++ depuis des années et je parviens toujours à me pendre.

38
répondu BigSandwich 2017-05-23 12:10:45

Rendez le destructeur virtuel chaque fois que votre classe est polymorphe.

30
répondu yesraaj 2009-01-20 13:02:36

Appeler destructor via un pointeur vers une classe de base

struct Base {
  virtual void f() {}
  virtual ~Base() {}
};

struct Derived : Base {
  void f() override {}
  ~Derived() override {}
};

Base* base = new Derived;
base->f(); // calls Derived::f
base->~Base(); // calls Derived::~Derived

L'appel 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 pour base->~Base() - sa principale fonction - Derived::~Derived() sera appelée.

La même chose se produit lorsque destructor est appelé indirectement, par exemple delete base;. L'instruction delete appellera {[4] } qui sera envoyée à Derived::~Derived().

Classe abstraite avec non-virtuel destructeur

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-le simplement protected pour qu'il ne soit pas appelé accidentellement:

// library.hpp

struct Base {
  virtual void f() = 0;

protected:
  ~Base() = default;
};

void CallsF(Base& base);
// CallsF is not going to own "base" (i.e. call "delete &base;").
// It will only call Base::f() so it doesn't need to access Base::~Base.

//-------------------
// application.cpp

struct Derived : Base {
  void f() override { ... }
};

int main() {
  Derived derived;
  CallsF(derived);
  // No need for virtual destructor here as well.
}
10
répondu Abyx 2015-05-18 13:38:00

J'aime penser aux interfaces et aux implémentations des interfaces. En C++ speak interface est pure classe virtuelle. Destructor fait partie de l'interface et devrait être implémenté. Par conséquent destructor devrait être virtuel pur. Que diriez-vous constructeur? Le constructeur ne fait pas partie de l'interface car l'objet est toujours instancié explicitement.

7
répondu Dragan Ostojic 2012-11-08 16:28:11

Pour être simple, Le destructeur virtuel 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é.

 #include<iostream>
 using namespace std;
 class B{
    public:
       B(){
          cout<<"B()\n";
       }
       virtual ~B(){ 
          cout<<"~B()\n";
       }
 };
 class D: public B{
    public:
       D(){
          cout<<"D()\n";
       }
       ~D(){
          cout<<"~D()\n";
       }
 };
 int main(){
    B *b = new D();
    delete b;
    return 0;
 }

OUTPUT:
B()
D()
~D()
~B()

==============
If you don't give ~B()  as virtual. then output would be 
B()
D()
~B()
where destruction of ~D() is not done which leads to leak

6
répondu Prakash GiBBs 2016-08-26 05:33:24

Le mot-clé virtuel pour destructor est nécessaire lorsque vous voulez que différents destructeurs suivent le bon ordre pendant que les objets sont supprimés via le pointeur de classe de base. par exemple:

Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ; 

Si votre destructeur de classe dérivé est virtuel, les objets seront déstruits dans un ordre(d'abord Objet dérivé puis base ). Si votre destructeur de classe dérivé n'est pas virtuel, seul l'objet de classe de base sera supprimé (car le pointeur est de la classe de base "Base * myObj"). Donc il y aura une fuite de mémoire pour objet dérivé.

5
répondu Mukul Kashmira 2015-01-29 07:11:34

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). Hériter d'une classe de base à l'aide de Virtual destructor rend le destructeur de la classe héritée automatiquement virtuel (vous n'avez donc pas besoin de retaper 'virtual' dans la déclaration de destructor de classe héritée).

2
répondu Trantor 2017-07-17 07:44:24

Qu'est Ce qu'un destructeur virtuel ou comment utiliser un destructeur virtuel

Un destructeur de classe est une fonction du même nom que la classe précédente avec ~ qui réallouera la mémoire allouée par la classe. Pourquoi nous avons besoin d'un destructeur virtuel

Voir l'exemple suivant avec certaines fonctions virtuelles

L'exemple indique également comment convertir une lettre en haut ou en bas

#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
  //void convertch(){};
  virtual char* convertChar() = 0;
  ~convertch(){};
};

class MakeLower :public convertch
{
public:
  MakeLower(char *passLetter)
  {
    tolower = true;
    Letter = new char[30];
    strcpy(Letter, passLetter);
  }

  virtual ~MakeLower()
  {
    cout<< "called ~MakeLower()"<<"\n";
    delete[] Letter;
  }

  char* convertChar()
  {
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] + 32;
    return Letter;
  }

private:
  char *Letter;
  bool tolower;
};

class MakeUpper : public convertch
{
public:
  MakeUpper(char *passLetter)
  {
    Letter = new char[30];
    toupper = true;
    strcpy(Letter, passLetter);
  }

  char* convertChar()
  {   
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] - 32;
    return Letter;
  }

  virtual ~MakeUpper()
  {
    cout<< "called ~MakeUpper()"<<"\n";
    delete Letter;
  }

private:
  char *Letter;
  bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{
  convertch *makeupper = new MakeUpper("hai"); 
  cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" ";     
  delete makeupper;
  convertch *makelower = new MakeLower("HAI");;
  cout<<"Eneterd : HAI = " <<makelower->convertChar()<<" "; 
  delete makelower;
  return 0;
}

De l'exemple ci-dessus, vous pouvez voir que le destructeur pour MakeUpper et La classe MakeLower n'est pas appelée.

Voir l'exemple avec le destructeur virtuel

#include "stdafx.h"
#include<iostream>

using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
virtual ~convertch(){}; // defined the virtual destructor

};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
      delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] + 32;

}

return Letter;
}

private:
char *Letter;
bool tolower;

};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{

size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] - 32;
}
return Letter;
}
virtual ~MakeUpper()
{
      cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{

convertch *makeupper = new MakeUpper("hai");

cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" \n";

delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<"\n ";


delete makelower;
return 0;
}

Le destructeur virtuel appellera explicitement le destructeur d'exécution le plus dérivé de la classe afin qu'il puisse 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

1
répondu user2578542 2016-01-29 19:33:49

J'ai pensé qu'il serait bénéfique de discuter du comportement" indéfini", ou du moins du comportement indéfini" crash " qui peut se produire lors de la suppression via une classe de base(/struct) sans destructeur virtuel, ou plus précisément pas de vtable. Le code ci-dessous liste quelques structures simples (la même chose serait vraie pour les classes).

#include <iostream>
using namespace std;

struct a
{
    ~a() {}

    unsigned long long i;
};

struct b : a
{
    ~b() {}

    unsigned long long j;
};

struct c : b
{
    ~c() {}

    virtual void m3() {}

    unsigned long long k;
};

struct d : c
{
    ~d() {}

    virtual void m4() {}

    unsigned long long l;
};

int main()
{
    cout << "sizeof(a): " << sizeof(a) << endl;
    cout << "sizeof(b): " << sizeof(b) << endl;
    cout << "sizeof(c): " << sizeof(c) << endl;
    cout << "sizeof(d): " << sizeof(d) << endl;

    // No issue.

    a* a1 = new a();
    cout << "a1: " << a1 << endl;
    delete a1;

    // No issue.

    b* b1 = new b();
    cout << "b1: " << b1 << endl;
    cout << "(a*) b1: " << (a*) b1 << endl;
    delete b1;

    // No issue.

    c* c1 = new c();
    cout << "c1: " << c1 << endl;
    cout << "(b*) c1: " << (b*) c1 << endl;
    cout << "(a*) c1: " << (a*) c1 << endl;
    delete c1;

    // No issue.

    d* d1 = new d();
    cout << "d1: " << d1 << endl;
    cout << "(c*) d1: " << (c*) d1 << endl;
    cout << "(b*) d1: " << (b*) d1 << endl;
    cout << "(a*) d1: " << (a*) d1 << endl;
    delete d1;

    // Doesn't crash, but may not produce the results you want.

    c1 = (c*) new d();
    delete c1;

    // Crashes due to passing an invalid address to the method which
    // frees the memory.

    d1 = new d();
    b1 = (b*) d1;
    cout << "d1: " << d1 << endl;
    cout << "b1: " << b1 << endl;
    delete b1;  

/*

    // This is similar to what's happening above in the "crash" case.

    char* buf = new char[32];
    cout << "buf: " << (void*) buf << endl;
    buf += 8;
    cout << "buf after adding 8: " << (void*) buf << endl;
    delete buf;
*/
}

Je ne suggère pas si vous avez besoin de destructeurs virtuels ou non, bien que je pense en général que c'est une bonne pratique de les avoir. Je signale juste la raison pour laquelle vous peut se retrouver avec un plantage si votre classe de base (/struct) n'a pas de vtable et que votre classe dérivée(/struct) le fait et que vous supprimez un objet via un pointeur de classe de base(/struct). Dans ce cas, l'adresse que vous passez à la routine libre du tas est invalide et donc la raison du crash.

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érente de ce pointeur de la classe dérivée(/struct) vous allez courir dans 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 c ou d sera corrigé pour tenir compte de la vtable. Si vous passez ce pointeur a ou b à supprimer, il se bloque car l'adresse n'est pas valide pour la routine libre du tas.

Si vous envisagez de supprimer les instances dérivées qui ont des vtables des pointeurs de classe de base, vous devez vous assurer que la classe de base a une vtable. Une façon de le faire est d'ajouter un destructeur virtuel, que vous voudrez peut-être de toute façon nettoyer correctement les ressources.

1
répondu nickdu 2017-03-23 18:22:30

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.

0
répondu user2641018 2014-09-09 07:02:02

Je pense que le cœur de cette question concerne les méthodes virtuelles et le polymorphisme, pas le destructeur spécifiquement. Voici un exemple plus clair:

class A
{
public:
    A() {}
    virtual void foo()
    {
        cout << "This is A." << endl;
    }
};

class B : public A
{
public:
    B() {}
    void foo()
    {
        cout << "This is B." << endl;
    }
};

int main(int argc, char* argv[])
{
    A *a = new B();
    a->foo();
    if(a != NULL)
    delete a;
    return 0;
}

Imprimera:

This is B.

Sans virtual il va imprimer:

This is A.

Et maintenant vous devez comprendre quand utiliser des destructeurs virtuels.

0
répondu gonjay 2017-07-23 09:49:18

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.

Si virtuel, le destructeur de classe dérivé est appelé, puis le constructeur de classe de base. Si ce n'est pas virtuel, seul le destructeur de classe de base est appelé.

-1
répondu Syed H 2015-01-02 16:27:51