Quand utiliser des destructeurs virtuels?

1487

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?

Lodle
la source
6
Voir ceci: Destructeur virtuel
Naveen
146
Chaque destructor bas est appelé peu importe quoi. virtuals'assure qu'il commence en haut au lieu du milieu.
Mooing Duck
@MooingDuck, c'est un commentaire quelque peu trompeur.
Euri Pinhollow
1
@FranklinYu c'est bien que vous ayez demandé parce que maintenant je ne vois aucun problème avec ce commentaire (sauf essayer de donner une réponse dans les commentaires).
Euri Pinhollow

Réponses:

1573

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:

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 virtual. Voyons maintenant l'extrait de code suivant:

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

Depuis la base de destructor n'est pas virtualet best un Base*pointage à un Derivedobjet, delete ba un comportement non défini :

[En delete b], si le type statique de l'objet à supprimer est différent de son type dynamique, le type statique doit être une classe de base du type dynamique de l'objet à supprimer et le type statique doit avoir un destructeur virtuel ou le le comportement n'est pas 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 virtualquand 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 deleteun 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 .

Luc Touraille
la source
174
Cela expliquerait pourquoi j'ai eu des fuites massives en utilisant une usine que j'ai faite auparavant. Tout a du sens maintenant. Merci
Lodle
8
Eh bien, c'est un mauvais exemple car il n'y a pas de membres de données. Que faire si Baseet Derivedont 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?
bobobobo
28
Extrait de l'article de Herb Sutter: "Directive n ° 4: Un destructeur de classe de base doit être soit public et virtuel, soit protégé et non virtuel."
Sundae
3
Aussi de l'article - «si vous supprimez polymorphiquement sans destructeur virtuel, vous invoquez le spectre redouté de« comportement indéfini », un spectre que je préférerais personnellement ne pas rencontrer même dans une ruelle modérément bien éclairée, merci beaucoup. lol
Bondolin
219

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 génère les informations suivantes:

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

#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 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.

Tunvir Rahman Tusher
la source
1
«constructeur virtuel n'est pas possible» signifie que vous n'avez pas besoin d'écrire le constructeur virtuel par vous-même. La construction d'un objet dérivé doit suivre la chaîne de construction du dérivé à la base. Vous n'avez donc pas besoin d'écrire le mot-clé virtuel pour votre constructeur. Merci
Tunvir Rahman Tusher
4
@Murkantilism, "les constructeurs virtuels ne peuvent pas être faits" est vrai en effet. Un constructeur ne peut pas être marqué comme virtuel.
cmeub
1
@cmeub, Mais il y a un idiome pour réaliser ce que vous attendez d'un constructeur virtuel. Voir parashift.com/c++-faq-lite/virtual-ctors.html
cape1232
@TunvirRahmanTusher pourriez-vous s'il vous plaît expliquer pourquoi le destructeur de base est appelé ??
rimalonfire
@rimiro C'est automatique par c ++. vous pouvez suivre le lien stackoverflow.com/questions/677620/…
Tunvir Rahman Tusher
195

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.

Bill le lézard
la source
14
+ "Si une classe a une fonction virtuelle, elle doit 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 doivent pas déclarer de destructeurs virtuels.": Y a-t-il des cas où il est logique de enfreindre cette règle? Sinon, serait-il logique que le compilateur vérifie cette condition et émette une erreur s'il n'est pas satisfait?
Giorgio
@Giorgio Je ne connais aucune exception à la règle. Mais je ne me considérerais pas comme un expert C ++, donc vous voudrez peut-être publier cela comme une question distincte. Un avertissement du compilateur (ou un avertissement provenant d'un outil d'analyse statique) est logique pour moi.
Bill the Lizard
10
Les classes peuvent être conçues pour ne pas être supprimées via le pointeur d'un certain type, tout en ayant des fonctions virtuelles - un exemple typique est une interface de rappel. On ne supprime pas son implémentation via un pointeur d'interface de rappel car c'est uniquement pour l'abonnement, mais il a des fonctions virtuelles.
dascandy
3
Exactement @dascandy - que ou toutes les nombreuses autres situations où nous utilisons le comportement polymorphique , mais ne fonctionne pas la gestion du stockage via des pointeurs - par exemple le maintien des objets automatiques ou statique durée, avec des pointeurs seulement utilisés comme voies d'observation. Aucun besoin / but dans la mise en œuvre d'un destructeur virtuel dans de tels cas. Comme nous ne faisons que citer des gens ici, je préfère Sutter d'en haut: "Directive # 4: Un destructeur de classe de base doit être soit public et virtuel, soit protégé et non virtuel." Ce dernier garantit à quiconque essaie accidentellement de supprimer via un pointeur de base de voir l'erreur de leur chemin
underscore_d
1
@Giorgio Il y a en fait une astuce que l'on peut utiliser et éviter un appel virtuel à un destructeur: lier via une référence const un objet dérivé à une base, comme const Base& = make_Derived();. Dans ce cas, le destructeur de la Derivedvaleur 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 .
vsoftco
46

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.

BigSandwich
la source
J'ai jeté un coup d'œil à votre question et j'ai vu que vous aviez déclaré le destructeur de base virtuel. Est-ce que "la suppression d'un pointeur de classe de base quand il n'y a pas de destructeur virtuel entraînera un comportement indéfini" reste valable par rapport à votre question? Puisque, dans cette question, lorsque vous avez appelé delete, la classe dérivée (créée par son nouvel opérateur) est d'abord vérifiée pour une version compatible. Puisqu'il en a trouvé un, il a été appelé. 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 indéfini»?
ubuntugod
C'est à peu près la même chose. Le constructeur par défaut n'est pas virtuel.
BigSandwich
41

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

yesraaj
la source
13

Appel de destructeur 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 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 pour base->~Base()- sa fonction prioritaire - le Derived::~Derived()sera appelé.

La même chose se produit lorsque le destructeur est appelé indirectement, par exemple delete base;. La deletedéclaration appellera base->~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 protectedsorte 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.
}
Abyx
la source
Est-il nécessaire de déclarer explicitement ~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é)?
Ponkadoodle
@Wallacoloo non, ne le déclarez que lorsque c'est nécessaire. Par exemple, pour mettre en protectedsection, ou pour s'assurer qu'il est virtuel en utilisant override.
Abyx
9

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.

Dragan Ostojic
la source
2
C'est une perspective différente sur la même question. Si nous pensons en termes d'interfaces au lieu de classe de base vs classe dérivée, c'est une conclusion naturelle: si cela fait partie de l'interface, rendez-la virtuelle. Si ce n'est pas le cas.
Dragan Ostojic
2
+1 pour indiquer la similitude du concept d' interface OO et d'une classe virtuelle pure C ++ . En ce qui concerne le destructeur devrait être mis en œuvre : c'est souvent inutile. À moins qu'une classe ne gère une ressource telle que la mémoire brute allouée dynamiquement (par exemple, pas via un pointeur intelligent), un descripteur de fichier ou un descripteur de base de données, l'utilisation du destructeur par défaut créé par le compilateur convient parfaitement dans les classes dérivées. Et notez que si un destructeur (ou n'importe quelle fonction) est déclaré virtualdans une classe de base, il est automatiquement virtualdans une classe dérivée, même s'il ne l'est pas.
DavidRR
Cela manque le détail crucial que le destructeur ne fait pas nécessairement partie de l'interface. On peut facilement programmer des classes qui ont des fonctions polymorphes mais que l'appelant ne gère pas / n'est pas autorisé à supprimer. Un destructeur virtuel n'a alors plus de raison d'être. Bien sûr, pour garantir cela, le destructeur non virtuel - probablement par défaut - doit être non public. Si je devais deviner, je dirais que de telles classes sont plus souvent utilisées en interne pour des projets, mais cela ne les rend pas moins pertinentes comme exemple / nuance dans tout cela.
underscore_d
8

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:

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

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é.

Mukul Kashmira
la source
7

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é.

 #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

Prakash GiBBs
la source
Ne pas avoir le destructeur virtuel de base et appeler deleteun pointeur de base conduit à un comportement indéfini.
James Adkison du
@JamesAdkison pourquoi cela conduit-il à un comportement indéfini ??
rimalonfire
@rimiro C'est ce que dit la norme . Je n'ai pas de copie mais le lien vous amène à un commentaire où quelqu'un fait référence à l'emplacement dans la norme.
James Adkison
@rimiro "Si la suppression, par conséquent, peut être effectuée par polymorphisme via l'interface de classe de base, alors elle doit se comporter virtuellement et doit être virtuelle. En effet, le langage l'exige - si vous supprimez polymorphiquement sans destructeur virtuel, vous invoquez le spectre redouté de "comportement indéfini", un spectre que je préférerais personnellement ne pas rencontrer même dans une ruelle modérément bien éclairée, merci beaucoup. " ( gotw.ca/publications/mill18.htm ) - Herb Sutter
James Adkison
4

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).

Trantor
la source
4

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:

#include <iostream>
#include <memory>

using namespace std;

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

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

int main()
{
    shared_ptr<Base> b(new Derived());
}

production:

Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called
Zhenxiao Hao
la source
Bien que cela soit possible, je découragerais quiconque de l'utiliser. Les frais généraux d'un destructeur virtuel sont minuscules et cela permet simplement de gâcher, spécialement par un programmeur moins expérimenté, qui ne le sait pas. Ce petit virtualmot-clé pourrait vous éviter bien des souffrances.
Michal Štein
3

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

#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;
}

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

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

user2578542
la source
2

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.

user2641018
la source
2

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:

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;
}

Imprime:

This is B.

Sans virtualcela, il imprimera:

This is A.

Et maintenant, vous devez comprendre quand utiliser des destructeurs virtuels.

gonjay
la source
Non, cela ne fait que rechaper les bases des fonctions virtuelles, ignorant totalement la nuance de quand / pourquoi le destructeur devrait être un - ce qui n'est pas aussi intuitif, d'où la raison pour laquelle l'OP a posé la question. (Aussi, pourquoi l'allocation dynamique inutile ici? Il suffit de le faire B b{}; A& a{b}; a.foo();. La vérification NULL- qui devrait être nullptr- avant deleteing - 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 a newéchoué.)
underscore_d
2
Il est prudent d'appeler deleteun NULLpointeur (c'est-à-dire que vous n'avez pas besoin du if (a != NULL)garde).
James Adkison
@SaileshD Oui, je sais. C'est ce que j'ai dit dans mon commentaire
James Adkison
1

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).

#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 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.

nickdu
la source
0

Une définition de base virtualconsiste à 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.

#include <iostream>

class Mother{

public:

    Mother(){

          std::cout<<"Mother Ctor"<<std::endl;
    }

    virtual~Mother(){

        std::cout<<"Mother D-tor"<<std::endl;
    }


};

class Child: public Mother{

    public:

    Child(){

        std::cout<<"Child C-tor"<<std::endl;
    }

    ~Child(){

         std::cout<<"Child D-tor"<<std::endl;
    }
};

int main()
{

    Mother *c = new Child();
    delete c;

    return 0;
}
rekkalmd
la source
-1

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é.

Syed H
la source
Je dirais que cela n'est nécessaire que "s'il peut être signalé par un pointeur de classe de base" et peut être supprimé publiquement. Mais je suppose que cela ne fait pas de mal de prendre l'habitude d'ajouter des détecteurs virtuels au cas où ils pourraient devenir nécessaires plus tard.
underscore_d