Quand utiliser les destructeurs virtuels?

j'ai une compréhension solide de la plupart des théories de OO, mais la seule chose qui me confond beaucoup est les destructeurs virtuels.

j'ai pensé que le destructeur est 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
la source

15 ответов

les destructeurs virtuels sont utiles lorsque vous pouvez supprimer une instance d'une classe dérivée à travers 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 la 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 et b est un Base* pointant vers un Derived objet, delete b a comportement non défini :

[In delete b ], si le type statique de la de l'objet à supprimer est différent de son type dynamique, 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, résultant en une fuite de ressources.

pour résumer, toujours faire des destructeurs de classes de base virtual quand ils sont censés être manipulés polymorphiquement.

si vous voulez empêcher la suppression d'une instance à travers un pointeur de classe de base, vous pouvez faire en sorte que la classe de base destructor soit protégée et non virtuelle; en faisant ainsi, 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 de classe de base virtuelle dans cet article de Herb Sutter .

1340
répondu Luc Touraille 2018-02-28 21:02:10
la source

déclare Les Destructeurs virtuels dans les classes de base polymorphes. C'est L'article 7 dans le de Scott Meyers "efficace c++ . Meyers poursuit en résumant que si une classe A n'importe quelle "fonction virtuelle 151940920", 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 devraient et non déclarer destructeurs virtuels.

168
répondu Bill the Lizard 2009-01-20 16:11:51
la source

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 faire 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

ainsi la destruction de pointeur de base (qui prennent une allocation sur l'Objet dérivé!) suivre la règle de destruction I. e d'abord le dérivé puis la base. D'un autre côté pour le constructeur il n'y a rien comme le constructeur virtuel.

159
répondu Tunvir Rahman Tusher 2018-03-31 12:50:41
la source

sachez aussi que supprimer un pointeur de classe de base quand il n'y a pas de destructeur virtuel se traduira par comportement non défini . Quelque chose que j'ai appris tout récemment:

comment se comporter en cas de suppression dans C++?

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

38
répondu BigSandwich 2017-05-23 15:10:45
la source

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

30
répondu yesraaj 2009-01-20 16:02:36
la source

appel à un destructeur par l'intermédiaire d'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

destructeur Virtuel appel n'est pas différent de tout autre appel de fonction virtuelle.

Pour base->f() , l'appel sera envoyé à Derived::f() , et c'est la même chose pour base->~Base() - sa fonction dominante - la Derived::~Derived() sera appelée.

il en est de même lorsque le destructeur est appelé indirectement, par exemple delete base; . Le La déclaration delete appellera base->~Base() qui sera envoyé à Derived::~Derived() .

classe abstraite avec destructeur non virtuel

Si vous n'allez pas supprimer l'objet par l'intermédiaire d'un pointeur vers la classe de base - il n'est pas nécessaire d'avoir un destructeur virtuel. Il suffit de le faire 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 16:38:00
la source

j'aime penser aux interfaces et aux implémentations d'interfaces. En C++, speak interface est une classe virtuelle pure. Destructor fait partie de l'interface et devrait être mis en œuvre. Par conséquent destructor devrait être purement virtuel. Que diriez-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 20:28:11
la source

Pour être simple, Destructeur virtuel est de détruire les ressources dans un ordre approprié, lorsque vous supprimez une classe de base pointeur pointant vers l'objet de classe dérivée.

 #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 08:33:24
la source

mot-clé virtuel pour destructeur est nécessaire lorsque vous voulez différents destructeurs doivent suivre l'ordre approprié pendant que les objets sont supprimés par 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 alors les objets seront détruits dans un ordre(d'abord Objet dérivé puis base ). Si votre destructeur de classe dérivé N'est pas virtuel alors seul l'objet de classe de base sera supprimé(parce que le pointeur est de classe de base " Base * myObj"). Donc il y aura une fuite de mémoire pour l'Objet dérivé.

5
répondu Mukul Kashmira 2015-01-29 10:11:34
la source

Les Destructeurs de classe de base virtuelle sont des "meilleures pratiques" - vous devriez toujours les utiliser pour éviter (difficile à détecter) les fuites de mémoire. En les utilisant, vous pouvez être sûr que tous les destructeurs dans la chaîne d'héritage de vos classes sont appelés (dans l'ordre approprié). Hériter d'une classe de base en utilisant virtual destructor rend le destructeur de la classe hériter automatiquement virtuel, aussi (donc vous n'avez pas à retaper 'virtual' dans la déclaration de destructeur de classe hériter).

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

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

un destructeur de classe est une fonction avec le même nom de la classe précédente avec ~ qui va réattribuer la mémoire qui est attribuée par la classe. Pourquoi nous avons besoin d'un destructeur virtuel

voir l'exemple suivant avec quelques fonctions virtuelles

l'échantillon dit aussi comment vous pouvez 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;
}

À partir de l'échantillon ci-dessus, vous pouvez voir que le destructeur pour les deux classes MakeUpper et MakeLower n'est pas appelé.

voir l'échantillon suivant 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 de temps d'exécution le plus dérivé de la classe pour qu'il puisse effacer l'objet d'une 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 22:33:49
la source

j'ai pensé qu'il serait bénéfique de discuter du comportement" non défini", ou du moins du comportement" crash " Non défini qui peut se produire lors de la suppression par une classe de base(/struct) sans un destructeur virtuel, ou plus précisément pas de vtable. Le code ci-dessous énumère 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 suis il suffit de souligner la raison pour laquelle vous pouvez vous retrouver avec un crash si votre classe de base(/struct) n'a pas de vtable et votre classe dérivée(/struct) le fait et 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 de l'accident.

si vous utilisez le code ci-dessus, vous verrez clairement quand le problème se produit. Quand ce pointeur de la classe de base (/struct) est différent de ce pointeur de la classe dérivée(/struct), vous allez rencontrer ce problème. Dans l'exemple ci-dessus, struct a et b n'ont pas vtables. les structures c et d ont vtables. Ainsi, un pointeur a ou b vers une instance d'objet c ou d sera fixé pour tenir compte du vtable. Si vous passez ce pointeur a ou b pour supprimer il va se planter en raison de l'adresse étant invalide à la routine libre du tas.

si vous prévoyez de supprimer les instances dérivées qui ont vtables à partir des pointeurs de classe de base, vous devez assurez-vous que la classe de base a une table. Une façon de le faire est d'ajouter un destructeur virtuel, qui vous voudrez, de toute façon pour nettoyer correctement les ressources.

1
répondu nickdu 2017-03-23 21:22:30
la source

quand vous devez appeler destructeur de classe dérivé de classe de base. vous devez déclarer destructeur de classe de base virtuel en classe de base.

0
répondu user2641018 2014-09-09 11:02:02
la source

je pense que le cœur de cette question Est sur les méthodes virtuelles et le polymorphisme, pas le destructeur en particulier. 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;
}

sera imprimé:

This is B.

sans virtual il imprimera:

This is A.

et maintenant vous devez comprendre quand utiliser des destructeurs virtuels.

0
répondu gonjay 2017-07-23 12:49:18
la source

toute classe héritée publiquement, polymorphique 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, la classe dérivée destructor est appelée, alors la classe de base constructor. Si ce n'est pas virtuel, seul le destructeur de classe de base est appelé.

-1
répondu Syed H 2015-01-02 19:27:51
la source

Autres questions sur c++ polymorphism virtual-destructor