web-dev-qa-db-fra.com

remplacement par défaut du destructeur virtuel

Tout le monde sait que le destructeur de la classe de base doit généralement être virtuel. Mais qu'en est-il du destructeur de classe dérivée? En C++ 11, nous avons le mot-clé "override" et la possibilité d'utiliser explicitement le destructeur par défaut.

struct Parent
{
  std::string a;
  virtual ~Parent()
  {
  }

};

struct Child: public Parent
{
  std::string b;
  ~Child() override = default;
};

Est-il correct d'utiliser les deux mots clés "override" et "= default" dans le destructeur de la classe Child? Le compilateur générera-t-il le destructeur virtuel correct dans ce cas?

Si oui, alors pouvons-nous penser que c'est un bon style de codage, et devons-nous toujours déclarer les destructeurs des classes dérivées de cette façon pour nous assurer que les destructeurs de classe de base sont virtuels?

32
Sandro

Est-il correct d'utiliser les deux mots clés "override" et "= default" dans le destructeur de la classe Child? Le compilateur générera-t-il le destructeur virtuel correct dans ce cas?

Oui c'est correct. Sur n'importe quel compilateur sensé, si le code compile sans erreur, cette définition de destructeur sera un no-op: son absence ne doit pas changer le comportement du code.

peut-on penser que c'est un bon style de codage

C'est une question de préférence. Pour moi, cela n'a de sens que si le type de classe de base est basé sur des modèles: il imposera alors à la classe de base d'avoir un destructeur virtuel. Sinon, lorsque le type de base est fixe, je considérerais ce code comme du bruit. Ce n'est pas comme si la classe de base allait changer comme par magie. Mais si vous avez des coéquipiers impassibles qui aiment changer les choses sans vérifier le code qui dépend de ce qu'ils peuvent éventuellement casser, il est préférable de laisser la définition du destructeur - comme une couche de protection supplémentaire.

15
Kuba Ober

override n'est rien de plus qu'un filet de sécurité. Le destructeur de la classe enfant sera toujours virtuel si le destructeur de la classe de base est virtuel, peu importe comment il est déclaré - ou pas du tout déclaré (c'est-à-dire en utilisant un déclaré implicitement).

11
SergeyA

Il y a (au moins) une raison pour utiliser override ici - vous vous assurez que le destructeur de la classe de base est toujours virtuel. Ce sera une erreur de compilation si le destructeur de la classe dérivée croit qu'il remplace quelque chose, mais il n'y a rien à remplacer. Il vous donne également un endroit pratique pour laisser la documentation générée, si vous le faites.

D'un autre côté, Je peux penser à deux raisons de ne pas le faire:

  • C'est un peu bizarre et inversé pour la classe dérivée d'appliquer le comportement de la classe de base.
  • Si vous définissez un destructeur dans l'en-tête (ou si vous le faites en ligne), vous introduisez la possibilité d'erreurs de compilation impaires. Disons que votre classe ressemble à ceci:

    struct derived {
        struct impl;
        std::unique_ptr<derived::impl> m_impl;
        ~derived() override = default;
    };
    

    Vous obtiendrez probablement une erreur de compilation car le destructeur (qui est en ligne avec la classe ici) recherchera le destructeur pour la classe incomplète, derived::impl.

    C'est ma façon détournée de dire que chaque ligne de code peut devenir un handicap, et peut-être est-il préférable de sauter quelque chose s'il ne fonctionne rien. Si vous avez vraiment vraiment besoin d'appliquer un destructeur virtuel dans la classe de base à partir de la classe parente, quelqu'un a suggéré d'utiliser static_assert de concert avec std::has_virtual_destructor, ce qui produira des résultats plus cohérents, à mon humble avis.

6
cyberbisson

Je pense que "déroger" est en quelque sorte trompeur sur le destructeur. Lorsque vous remplacez la fonction virtuelle, vous la remplacez. Les destructeurs sont enchaînés, vous ne pouvez donc pas remplacer littéralement le destructeur

3
hakun bahun

Selon le CppCoreGuidelines C.128 le destructeur de la classe dérivée ne doit pas être déclaré virtual ou override.

Si un destructeur de classe de base est déclaré virtuel, il faut éviter de déclarer les destructeurs de classe dérivés virtual ou override. Certaines bases de code et certains outils peuvent insister sur le remplacement des destructeurs, mais ce n'est pas la recommandation de ces directives.

2
Alexandre A.

Le référence CPP indique que override s'assure que la fonction est virtual et qu'elle remplace en effet une fonction virtuelle. Ainsi, le mot clé override garantirait que le destructeur est virtuel.

Si vous spécifiez override mais pas = default, vous obtiendrez une erreur de l'éditeur de liens.

Vous n'avez pas besoin de faire quoi que ce soit. Laisser le Child dtor non défini fonctionne très bien:

#include <iostream>

struct Notify {
    ~Notify() { std::cout << "dtor" << std::endl; }
};

struct Parent {
    std::string a;
    virtual ~Parent() {}
};

struct Child : public Parent {
    std::string b;
    Notify n;
};

int main(int argc, char **argv) {
    Parent *p = new Child();
    delete p;
}

Cela produira dtor. Si vous supprimez le virtual dans Parent::~Parent, cependant, il ne produira rien car c'est un comportement indéfini, comme indiqué dans les commentaires.

Le bon style serait de ne pas mentionner Child::~Child du tout. Si vous ne pouvez pas croire que la classe de base l'a déclarée virtuelle, votre suggestion avec override et = default marchera; J'espère qu'il existe de meilleures façons de garantir cela au lieu de salir votre code avec ces déclarations de destructeur.

1
Martin Ueding

Bien que les destructeurs ne soient pas hérités, il est clairement écrit dans la norme que les destructeurs virtuels des classes dérivées remplacent les destructeurs des classes de base.

De la norme C++ (10.3 Fonctions virtuelles)

6 Même si les destructeurs ne sont pas hérités, un destructeur dans une classe dérivée remplace un destructeur de classe de base déclaré virtuel; voir 12.4 et 12.5.

D'autre part, il est également écrit (9.2 Membre de classe)

8 Un seq-spéci fi cateur doit contenir au plus un de chaque virt-spéci fi cateur. Un virt-speci fi er-seq ne doit apparaître que dans la déclaration de ne fonction membre virtuelle (10.3).

Bien que les destructeurs soient appelés comme des fonctions membres spéciales, ils sont également des fonctions membres.

Je suis sûr que la norme C++ doit être modifiée de telle sorte qu'il ne soit pas ambigu qu'un destructeur puisse avoir virt-specifier override. À l'heure actuelle, ce n'est pas clair.

0
Vlad from Moscow