web-dev-qa-db-fra.com

Dois-je utiliser des destructeurs virtuels par défaut?

J'ai une classe abstraite qui est déclarée comme suit:

class my_type {
public:
    virtual ~my_type() = default;
    virtual void do_something() = 0;
};

Est-il considéré comme une bonne pratique de déclarer le destructeur comme ceci, avec le mot clé default? Y a-t-il une meilleure façon?

Est également = 0 une manière moderne (C++ 11) de spécifier aucune implémentation par défaut, ou y a-t-il une meilleure façon?

30
Humam Helfawi

Oui, vous pouvez certainement utiliser = default Pour de tels destructeurs. Surtout si vous alliez simplement le remplacer par {}. Je pense que celui de = default Est plus agréable, car il est plus explicite, donc il attire immédiatement l'attention et ne laisse aucun doute.

Cependant, voici quelques notes à prendre en compte lors de cette opération.

Lorsque vous = default Un destructeur dans le fichier d'en-tête (voir modifier) (ou toute autre fonction spéciale pour cette question), c'est essentiellement le définir dans l'en-tête. Lors de la conception d'une bibliothèque partagée, vous souhaiterez peut-être explicitement que le destructeur ne soit fourni que par la bibliothèque plutôt que dans l'en-tête, afin de pouvoir le modifier plus facilement à l'avenir sans nécessiter une reconstruction du binaire dépendant. Mais encore une fois, c'est pour quand la question n'est pas simplement si à = default Ou à {}.


EDIT: Comme Sean l'a bien noté dans les commentaires, vous pouvez également utiliser = default En dehors de la déclaration de classe, qui obtient le meilleur des deux mondes ici.


L'autre différence technique cruciale est que la norme dit qu'une fonction explicitement par défaut qui ne peut pas être générée, ne sera tout simplement pas générée. Prenons l'exemple suivant:

struct A { ~A() = delete; };
struct B : A { ~B() {}; }

Cela ne compilerait pas , car vous obligez le compilateur à générer le code spécifié (et ses conditions implicites, telles que l'appel du destructeur de A) pour le destructeur de B --et il ne peut pas, car le destructeur de A est supprimé. Considérez ceci, cependant:

struct A { ~A() = delete; };
struct B : A { ~B() = default; }

En fait, compilerait , car le compilateur voit que ~B() ne peut pas être généré, donc il ne le génère tout simplement pas --et le déclare supprimé. Cela signifie que vous n'obtiendrez l'erreur que lorsque vous essayez réellement d'utiliser/call B::~B().

Cela a au moins deux implications dont vous devez être conscient:

  1. Si vous cherchez à obtenir l'erreur simplement lors de la compilation de tout ce qui inclut la déclaration de classe, vous ne l'obtiendrez pas, car elle est techniquement valide.
  2. Si vous utilisez initialement toujours = default Pour de tels destructeurs, vous n'aurez pas à vous soucier de la suppression du destructeur de votre super classe, s'il s'avère que c'est ok et que vous ne l'utilisez jamais vraiment. C'est une sorte d'utilisation exotique, mais dans cette mesure, il est plus correct et à l'épreuve du temps. Vous n'obtiendrez l'erreur que si vous utilisez vraiment le destructeur - sinon, vous serez laissé seul.

Donc, si vous optez pour une approche de programmation défensive, vous voudrez peut-être simplement utiliser {}. Sinon, il vaut probablement mieux = default Ing, car cela permet de mieux suivre les instructions programmatiques minimales nécessaires pour obtenir une base de code correcte et fonctionnelle et éviter les conséquences imprévues.1.


Quant à = 0: Oui, c'est toujours la bonne façon de le faire. Mais veuillez noter qu'en fait, il ne spécifie pas qu'il n'y a "aucune implémentation par défaut", mais plutôt que (1) La classe n'est pas instanciable; et (2) Toutes les classes dérivées doivent remplacer cette fonction (bien qu'elles puissent utiliser une implémentation facultative fournie par la super classe). En d'autres termes, vous pouvez à la fois définir une fonction et la déclarer comme virtuelle pure. Voici un exemple:

struct A { virtual ~A() = 0; }
A::~A() = default;

Cela garantira ces contraintes sur A (et son destructeur).


1) Un bon exemple de la raison pour laquelle cela peut être utile de manière inattendue est la façon dont certaines personnes ont toujours utilisé return avec des parenthèses, puis C++ 14 a ajouté decltype(auto) ce qui a essentiellement créé une différence technique entre son utilisation avec et sans parenthèses.

28
Yam Marcovic