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?
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.
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).
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:
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.
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
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
ouoverride
. Certaines bases de code et certains outils peuvent insister sur le remplacement des destructeurs, mais ce n'est pas la recommandation de ces directives.
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.
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.