81 votes

surcharge 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 la 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 un destructeur virtuel correct dans ce cas ?

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

8 votes

Autant le faire static_assert(std::has_virtual_destructor<Parent>::value, "contract violated");

3 votes

Notez qu'il n'est pas toujours nécessaire que le destructeur de la classe de base soit virtuel. Ce n'est donc (peut-être) une bonne idée que si c'est une exigence.

3 votes

Si cela fonctionne, j'aime bien, mais celui de Milleniumbug est meilleur (intention beaucoup plus claire). D'autre part, Stroustrup déteste les constructions "standard de codage" qui protègent contre les erreurs courantes, et insiste sur le fait que le compilateur devrait générer des avertissements appropriés, à la place.

33voto

Kuba Ober Points 18926

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 un destructeur virtuel correct dans ce cas ?

Oui, c'est correct. Sur n'importe quel compilateur sain d'esprit, si le code compile sans erreur, cette définition du 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 la classe de base est modélisé : cela imposera à 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 par magie. Mais si vous avez des coéquipiers qui aiment changer les choses sans vérifier le code qui dépend de ce qu'ils sont susceptibles de casser, il est préférable de laisser la définition du destructeur - comme une couche supplémentaire de protection.

40 votes

Notez que parfois, vous êtes votre propre coéquipier décédé :(

24voto

SergeyA Points 2159

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, quelle que soit la façon dont il est déclaré - ou pas déclaré du tout (c'est-à-dire en utilisant un destructeur implicitement déclaré).

5 votes

La question concerne les cas où la classe de base doit avoir un destructeur virtuel, mais n'en a pas.

5 votes

@SergeyA Ce n'est pas une réponse à la question.

9voto

cyberbisson Points 123

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. Il y aura une erreur de compilation si le destructeur de la classe dérivée croit qu'il surcharge quelque chose, mais il n'y a rien à surcharger. Cela vous donne également un endroit pratique pour laisser la documentation générée, si vous le faites.

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

  • C'est un peu bizarre et arriéré pour la classe dérivée d'imposer le comportement de la classe de base.
  • Si vous définissez un destructeur dans l'en-tête (ou si vous le mettez en ligne), vous introduisez la possibilité d'erreurs de compilation bizarres. 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) cherchera le destructeur de 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 qu'il est peut-être préférable d'ignorer quelque chose si cela ne sert à rien. Si vous avez vraiment besoin d'imposer un destructeur virtuel dans la classe de base de la classe parente, quelqu'un a suggéré d'utiliser static_assert de concert avec std::has_virtual_destructor qui produira des résultats plus cohérents, à mon avis.

2 votes

La solution à ce problème (classes déclarées en avant) est d'implémenter le destructeur dans votre unité de compilation. Par exemple, dans votre fichier .cpp, derived::~derived() = default; Vous pouvez utiliser = default dans votre unité de compilation.

8voto

hakun bahun Points 21

Je pense que "override" est un peu trompeur sur destructor. Quand vous surchargez une fonction virtuelle, vous la remplacez. Les destructeurs sont enchaînés, donc vous ne pouvez pas surcharger destructeur littéralement.

3 votes

Je ne dirais pas ça. Les fonctions surchargées doivent accomplir la même tâche sémantique (bien que les versions dérivées doivent être plus spécialisées). De plus, le remplacement n'est pas total. Cela signifie simplement que l'appel se résout au type de l'objet par défaut. Vous pouvez toujours appeler explicitement les fonctions de la classe de base en utilisant l'opérateur scope. Voir ici stackoverflow.com/questions/38010286/

3 votes

Override spécifie une propriété requise de l'implémentation de la classe de base : cette fonction doit avoir été virtuelle. En fait, il est parfaitement raisonnable de dire ~Derived() override = default; L'utilisation de la surcharge est le seul moyen de garantir que la classe de base a été correctement définie. Ceci est particulièrement important lors de la déclaration de modèles qui dérivent d'un paramètre de modèle, et qui doivent garantir que la base déclare correctement son destructeur.

0 votes

@Speed8ump Il ne s'agit pas des fonctions. Cela n'a pas beaucoup de sens de surcharger un constructeur. Il y a une discussion intéressante à ce sujet ici github.com/isocpp/CppCoreGuidelines/issues/721

4voto

queueoverflow Points 1094

El Référence CPP dit que override s'assure que la fonction est virtual et qu'elle remplace effectivement une fonction virtuelle. Ainsi, le override permet de s'assurer que le destructeur est virtuel.

Si vous spécifiez override mais pas = default alors vous obtiendrez une erreur d'édition de liens.

Vous n'avez rien à faire. En laissant le Child dtor undefined 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 donnera dtor . Si vous retirez le virtual à l'adresse Parent::~Parent Cependant, il ne produira rien car il s'agit d'un comportement non défini, comme indiqué dans les commentaires.

Un bon style serait de ne pas mentionner Child::~Child du tout. Si vous ne pouvez pas avoir confiance dans le fait que la classe de base l'a déclarée virtuelle, alors votre proposition de override y = default fonctionnera ; j'espère qu'il existe de meilleures façons de s'en assurer, au lieu d'encombrer votre code de ces déclarations de destructeurs.

5 votes

Si vous retirez le virtual à l'adresse Parent::~Parent vous aurez comportement indéfini . Il se peut qu'il ne produise rien. Il peut afficher une boîte de dialogue d'erreur fatale. Ou écraser le fichier de données avec lequel vous travaillez.

1 votes

Intéressant ! J'ai supposé qu'il fallait simplement appeler le dtor de base sur la sous-classe, ce qui entraînerait un nettoyage bien défini mais probablement indésirable des membres de base mais pas des membres dérivés. J'ai mis à jour ma réponse pour intégrer votre commentaire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X