69 votes

Empêcher l'héritage des classes en C++

Récemment, un de mes amis m'a demandé comment empêcher l'héritage des classes en C++. Il voulait que la compilation échoue.

J'y ai réfléchi et j'ai trouvé 3 réponses. Je ne suis pas sûr de savoir laquelle est la meilleure.

1) Constructeur(s) privé(s)

class CBase
{

public:

 static CBase* CreateInstance() 
 { 
  CBase* b1 = new CBase();
  return b1;
 }

private:

 CBase() { }
 CBase(CBase3) { }
 CBase& operator=(CBase&) { }

};

2) Utilisation de CSealed classe de base, ctor privé & héritage virtuel

class CSealed
{

private:

 CSealed() {
 }

 friend class CBase;
};

class CBase : virtual CSealed
{

public:

 CBase() {
 }

};

3) Utilisation d'un CSealed classe de base, ctor protégé & héritage virtuel

class CSealed
{

protected:

 CSealed() {
 }

};

class CBase : virtual CSealed
{

public:

 CBase() {
 }

};

Toutes les méthodes ci-dessus permettent de s'assurer que CBase ne peut plus être héritée. Ma question est la suivante :

  1. Quelle est la meilleure méthode ? Existe-t-il d'autres méthodes ?

  2. Les méthodes 2 et 3 ne fonctionneront pas si le CSealed est héritée virutalement. Comment cela se fait-il ? Est-ce que cela a quelque chose à voir avec le ptr vdisp ?

PS :

Le programme ci-dessus a été compilé dans le compilateur MS C++ (Visual Studio). référence : http://www.codeguru.com/forum/archive/index.php/t-321146.html

91voto

Peter N Lewis Points 12025

À partir de C++11, vous pouvez ajouter le mot clé final à votre classe, par exemple

class CBase final
{
...

La principale raison que je vois pour vouloir faire cela (et la raison pour laquelle j'ai cherché cette question) est de marquer une classe comme non sous-classable afin que vous puissiez utiliser en toute sécurité un destructeur non virtuel et éviter complètement une table virtuelle.

2 votes

Il existe une autre bonne raison, à savoir empêcher les classes dérivées de rompre le contrat des classes immuables.

0 votes

@Nemanja Boric cela s'appliquerait à toute sous-classe et à tout contrat, pas seulement à la mutabilité. Toute sous-classe peut potentiellement briser tout contrat implicite de la classe - ce n'est pas vraiment une bonne raison pour interdire toutes les sous-classes. Pour un objet immuable, que se passe-t-il si vous voulez ajouter une valeur dérivée, par exemple FullName() à partir des méthodes FirstName() et LastName(), ou peut-être une fonction de hachage spécifique.

14voto

Vous ne pouvez pas empêcher l'héritage (avant l'introduction de C++11). final ) - vous pouvez seulement empêcher l'instanciation des classes héritées. En d'autres termes, il n'y a aucun moyen d'empêcher :

class A { ... };

class B : public A { ... };

Le mieux que vous puissiez faire est d'empêcher les objets de type B d'être instanciés. Ceci étant, je vous suggère de suivre le conseil de kts et de documenter le fait que A (ou autre) n'est pas destiné à être utilisé pour l'héritage, de lui donner un destructeur non virtuel, et aucune autre fonction virtuelle, et d'en rester là.

8voto

KitsuneYMG Points 7604

Vous faites des contorsions pour éviter de nouveaux sous-classements. Pourquoi ? Documentez le fait que la classe n'est pas extensible et rendez le dtor non-virtuel. Dans l'esprit de c, si quelqu'un veut vraiment ignorer la façon dont vous vouliez que cela soit utilisé, pourquoi l'en empêcher ? (Je n'ai jamais vu l'intérêt de final classes/méthodes en java non plus).

//Note: this class is not designed to be extended. (Hence the non-virtual dtor)
struct DontExtened
{
  DontExtened();
  /*NOT VIRTUAL*/
  ~DontExtened();
  ...
};

12 votes

Je pense que le but de Java est que le compilateur JIT puisse optimiser les appels aux méthodes virtuelles si la classe est finale.

0 votes

@Manuel. Je comprends que le jvm puisse l'aimer, mais il devrait y avoir un moyen facile d'annuler cela sans changer la source. @ReallyOverride?

1 votes

On s'éloigne un peu du sujet. En Java, le final Ce mot-clé est logique car toutes les fonctions sont virtuelles par défaut, et il y a donc beaucoup à gagner en permettant au compilateur JIT d'effectuer ces optimisations. En C++, il n'y aurait rien à gagner d'un mécanisme similaire pour empêcher le sous-classement, c'est pourquoi le langage ne fournit pas de mécanisme pour le faire.

4voto

Whoever Points 18

1) est une question de goût. Si je vois bien, vos 2ème et 3ème solutions les plus fantaisistes déplacent l'erreur dans certaines circonstances du temps de liaison au temps de compilation, ce qui en général devrait être mieux.

2) L'héritage virtuel est nécessaire pour forcer la responsabilité d'initialiser la classe de base (virtuelle) à la classe la plus dérivée d'où le ctor de la classe de base n'est plus accessible.

4voto

Francis Boivin Points 169

Pour répondre à votre question, vous ne pouvez pas hériter de CBase car dans l'héritage virtuel, une classe dérivée devrait avoir un accès direct à la classe dont elle a hérité virtuellement. Dans ce cas, une classe qui dériverait de CBase devrait avoir un accès direct à CSealed, ce qu'elle ne peut pas faire puisque le constructeur est privé.

Bien que je ne vois pas l'utilité de tout cela (c'est-à-dire arrêter l'héritage), vous pouvez généraliser en utilisant des modèles (je ne pense pas que cela compile sur tous les compilateurs, mais c'est le cas avec MSVC).

template<class T>
class CSealed
{
    friend T;    // Don't do friend class T because it won't compile
    CSealed() {}
};

class CBase : private virtual CSealed<CBase>
{
};

1 votes

Il doit s'agir de la classe CBase : private virtual CSealed<CBase>. Sinon, CBase peut être dérivée.

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