Question Quand utiliser les destructeurs virtuels?


J'ai une solide compréhension de la plupart des OO, mais la seule chose qui me trouble beaucoup, ce sont les destructeurs virtuels.

Je pensais 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?


1206
2018-01-20 12:58


origine


Réponses:


Les destructeurs virtuels sont utiles lorsque vous pouvez supprimer une instance d'une classe dérivée via un pointeur sur 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 Base était virtual. Maintenant, jetons un coup d'œil à 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 indéfini:

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

Pour résumer, faites toujours des destructeurs de 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 rendre le destructeur de classe de base protégé et non virtuel; en faisant cela, 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.


1310
2018-01-20 13:04



Déclarez les destructeurs virtuels dans les classes de base polymorphes. Ceci est l'article 7 dans Scott Meyers C ++ efficace. Meyers continue à résumer que si une classe a tout fonction virtuelle, il 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 ne pas déclarer des destructeurs virtuels.


165
2018-01-20 13:11



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 les éléments suivants:

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 trouvé que seul le destructeur de base est call.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

Ainsi, 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 la dérivée puis la base. D'un autre côté pour le constructeur, il n'y a rien comme un constructeur virtuel.


153
2018-04-09 13:39



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 récemment:

Comment devrait se passer la suppression en C ++?

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


37
2018-01-21 01:09



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


30
2018-01-20 13:01



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 de destructeur virtuel n'est pas différent de tout autre appel de fonction virtuelle.

Pour base->f(), l'appel sera envoyé à Derived::f()et c'est pareil pour base->~Base() - sa fonction primordiale - la Derived::~Derived() sera appelé.

La même chose se produit lorsque le destructeur est appelé indirectement, par ex. delete base;. le delete déclaration appellera base->~Base() qui sera envoyé à Derived::~Derived().

Classe abstraite avec un 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. Fais le juste protected de sorte qu'il ne sera 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
2018-05-18 13:38



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


7
2017-11-08 16:28