web-dev-qa-db-fra.com

Pourquoi devrais-je déclarer un destructeur virtuel pour une classe abstraite en C ++?

Je sais que c’est une bonne pratique de déclarer des destructeurs virtuels pour les classes de base en C++, mais est-il toujours important de déclarer des destructeurs virtual même pour des classes abstraites fonctionnant comme des interfaces? Veuillez donner quelques raisons et exemples.

157
Kevin

C'est encore plus important pour une interface. Tout utilisateur de votre classe aura probablement un pointeur sur l'interface, pas un pointeur sur l'implémentation concrète. Lorsqu'ils viennent pour le supprimer, si le destructeur n'est pas virtuel, ils appellent le destructeur de l'interface (ou la valeur par défaut fournie par le compilateur, si vous n'en avez pas spécifié un), et non le destructeur de la classe dérivée. Fuite de mémoire instantanée.

Par exemple

class Interface
{
   virtual void doSomething() = 0;
};

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

void myFunc(void)
{
   Interface* p = new Derived();
   // The behaviour of the next line is undefined. It probably 
   // calls Interface::~Interface, not Derived::~Derived
   delete p; 
}
183
Airsource Ltd

La réponse à votre question est souvent, mais pas toujours. Si votre classe abstraite interdit aux clients d'appeler delete sur un pointeur vers celle-ci (ou si cela est indiqué dans sa documentation), vous êtes libre de ne pas déclarer un destructeur virtuel.

Vous pouvez interdire aux clients d'appeler delete sur un pointeur vers celui-ci en protégeant son destructeur. En procédant ainsi, il est parfaitement sûr et raisonnable d’omettre un destructeur virtuel.

Vous finirez par vous retrouver sans table de méthode virtuelle et signalerez à vos clients votre intention de la rendre non supprimable par un pointeur sur celle-ci. Vous avez donc des raisons de ne pas la déclarer virtuelle dans ces cas.

[Voir le point 4 de cet article: http://www.gotw.ca/publications/mill18.htm ]

37

J'ai décidé de faire des recherches et d'essayer de résumer vos réponses. Les questions suivantes vous aideront à déterminer le type de destructeur dont vous avez besoin:

  1. Votre classe est-elle destinée à être utilisée comme classe de base?
    • Non: Déclarez le destructeur public non virtuel pour éviter le v-pointer sur chaque objet de la classe. *.
    • Oui: lisez la question suivante.
  2. Votre classe est-elle abstraite? (c'est-à-dire des méthodes pures virtuelles?)
    • Non: essayez de rendre votre classe de base abstraite en modifiant la hiérarchie de votre classe.
    • Oui: lisez la question suivante.
  3. Souhaitez-vous autoriser la suppression polymorphe via un pointeur de base?
    • Non: Déclarez le destructeur virtuel protégé pour empêcher l'utilisation non désirée.
    • Oui: Déclarez le destructeur virtuel public (pas de surcharge dans ce cas).

J'espère que ça aide.

* Il est important de noter qu'il n'y a aucun moyen en C++ de marquer une classe comme finale (c'est-à-dire non sous-classable). Par conséquent, dans le cas où vous décidez de déclarer votre destructeur non public et virtuel, rappelez-vous d'avertir explicitement vos collègues programmeurs de ne pas en dériver. de votre classe.

Les références:

23
David Alfonso

Oui c'est toujours important. Les classes dérivées peuvent allouer de la mémoire ou conserver une référence à d'autres ressources qui devront être nettoyées lors de la destruction de l'objet. Si vous ne donnez pas de destructeurs virtuels à vos interfaces/classes abstraites, chaque fois que vous supprimez une instance de classe dérivée via une classe de base, le destructeur de votre classe dérivée ne sera pas appelé.

Par conséquent, vous ouvrez le potentiel de fuites de mémoire

class IFoo
{
  public:
    virtual void DoFoo() = 0;
};

class Bar : public IFoo
{
  char* dooby = NULL;
  public:
    virtual void DoFoo() { dooby = new char[10]; }
    void ~Bar() { delete [] dooby; }
};

IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted
7
OJ.

Ce n'est pas toujours requis, mais je trouve que c'est une bonne pratique. Cela permet de supprimer un objet dérivé en toute sécurité via un pointeur de type base.

Donc par exemple:

Base *p = new Derived;
// use p as you see fit
delete p;

est mal formé si Base n'a pas de destructeur virtuel, car il tentera de supprimer l'objet comme s'il s'agissait d'un Base *.

7
Evan Teran

Ce n'est pas seulement une bonne pratique. C'est la règle n ° 1 pour toute hiérarchie de classe.

  1. La classe la plus basique d'une hiérarchie en C++ doit avoir un destructeur virtuel

Maintenant pour le pourquoi. Prenez la hiérarchie typique des animaux. Les destructeurs virtuels passent par la répartition virtuelle comme tout autre appel de méthode. Prenons l'exemple suivant.

Animal* pAnimal = GetAnimal();
delete pAnimal;

Supposons que Animal est une classe abstraite. La seule façon pour C++ de connaître le destructeur approprié à appeler est via la répartition de méthode virtuelle. Si le destructeur n'est pas virtuel, il appellera simplement le destructeur d'Animal et ne détruira aucun objet dans les classes dérivées.

La raison pour laquelle le destructeur est virtuel dans la classe de base est qu’elle supprime simplement le choix des classes dérivées. Leur destructeur devient virtuel par défaut.

5
JaredPar

La réponse est simple, vous devez en avoir une virtuelle, sinon la classe de base ne serait pas une classe polymorphe complète.

    Base *ptr = new Derived();
    delete ptr; // Here the call order of destructors: first Derived then Base.

Vous préféreriez la suppression ci-dessus, mais si le destructeur de la classe de base n'est pas virtuel, seul le destructeur de la classe de base sera appelé et toutes les données de la classe dérivée resteront non supprimées.

3
fatma.ekici