133 votes

Quand dois-je utiliser l'héritage privé C++ ?

Contrairement à l'héritage protégé, l'héritage privé C++ a trouvé sa place dans le courant dominant du développement C++. Cependant, je n'ai toujours pas trouvé une bonne utilisation pour lui.

Quand l'utilisez-vous ?

161voto

fizzer Points 8193

Je l'utilise tout le temps. Voici quelques exemples qui me viennent à l'esprit :

  • Lorsque je veux exposer une partie mais pas la totalité de l'interface d'une classe de base. L'héritage public serait un mensonge, car Substituabilité de Liskov est cassé, alors que la composition impliquerait d'écrire un tas de fonctions de transfert.
  • Lorsque je veux dériver d'une classe concrète sans destructeur virtuel. L'héritage public inviterait les clients à supprimer par le biais d'un pointeur vers la base, invoquant un comportement non défini.

Un exemple typique est la dérivation privée d'un conteneur STL :

class MyVector : private vector<int>
{
public:
    // Using declarations expose the few functions my clients need 
    // without a load of forwarding functions. 
    using vector<int>::push_back;
    // etc...  
};
  • Lors de la mise en œuvre du modèle d'adaptation, le fait d'hériter en privé de la classe Adapted évite d'avoir à transférer vers une instance fermée.
  • Pour mettre en œuvre un interface privée . Ce problème se pose souvent avec le modèle de l'observateur. Typiquement, ma classe Observer, MyClass dit, s'abonne lui-même avec un certain Sujet. Ensuite, seule MyClass a besoin de faire la conversion MyClass -> Observer. Le reste du système n'a pas besoin de le savoir, donc l'héritage privé est indiqué.

5 votes

@Krsna : En fait, je ne le pense pas. Il n'y a qu'une seule raison ici : la paresse, à part la dernière, qui serait plus délicate à contourner.

14 votes

Pas vraiment de la paresse (à moins que vous ne le vouliez dans le bon sens). Cela permet de créer de nouvelles surcharges de fonctions qui ont été exposées sans travail supplémentaire. Si en C++1x on ajoute 3 nouvelles surcharges à push_back , MyVector les reçoit gratuitement.

0 votes

@DavidStone, ne pouvez-vous pas le faire avec une méthode modèle ?

63voto

Note après l'acceptation de la réponse : Il ne s'agit PAS d'une réponse complète. Lisez d'autres réponses comme aquí (sur le plan conceptuel) et aquí (à la fois théoriques et pratiques) si la question vous intéresse. Il s'agit simplement d'un tour de passe-passe qui peut être réalisé avec l'héritage privé. Bien que ce soit fantaisie ce n'est pas la réponse à la question.

Outre l'utilisation de base de l'héritage privé présenté dans la FAQ C++ (dont le lien figure dans les commentaires d'autres personnes), vous pouvez utiliser une combinaison d'héritage privé et virtuel pour joint une classe (dans la terminologie .NET) ou pour faire d'une classe final (dans la terminologie Java). Ce n'est pas une utilisation courante, mais j'ai quand même trouvé cela intéressant :

class ClassSealer {
private:
   friend class Sealed;
   ClassSealer() {}
};
class Sealed : private virtual ClassSealer
{ 
   // ...
};
class FailsToDerive : public Sealed
{
   // Cannot be instantiated
};

Scellé peut être instancié. Il dérive de ClassSealer et peut appeler le constructeur privé directement puisqu'il s'agit d'un ami.

FailsToDerive ne compilera pas car il doit appeler la fonction ClassSealer directement (exigence de l'héritage virtuel), mais il ne peut pas le faire car il est privé dans la classe Scellé et dans ce cas FailsToDerive n'est pas un ami de ClassSealer .


EDIT

Il a été mentionné dans les commentaires que cela ne pouvait pas être rendu générique à ce moment-là en utilisant le CRTP. La norme C++11 supprime cette limitation en fournissant une syntaxe différente pour les arguments de template :

template <typename T>
class Seal {
   friend T;          // not: friend class T!!!
   Seal() {}
};
class Sealed : private virtual Seal<Sealed> // ...

Bien sûr, tout cela est discutable, puisque C++11 fournit une fonction final mot-clé contextuel dans ce but précis :

class Sealed final // ...

0 votes

C'est une excellente technique. Je vais écrire un article de blog à ce sujet.

1 votes

Question : si nous n'utilisions pas l'héritage virtuel, alors FailsToDerive compilerait. Est-ce exact ?

4 votes

+1. @Sasha : Correct, l'héritage virtuel est nécessaire car la classe la plus dérivée appelle toujours directement les constructeurs de toutes les classes virtuellement héritées, ce qui n'est pas le cas avec l'héritage ordinaire.

34voto

Harper Shelby Points 13395

L'utilisation canonique de l'héritage privé est la relation "implémenté en termes de" (merci à Scott Meyers's 'Effective C++' pour cette formulation). En d'autres termes, l'interface externe de la classe héritière n'a aucune relation (visible) avec la classe héritée, mais elle l'utilise en interne pour implémenter sa fonctionnalité.

9 votes

Il peut être utile de mentionner l'une des raisons pour lesquelles elle est utilisée dans ce cas : Cela permet d'effectuer l'optimisation de la classe de base vide, ce qui ne se produira pas si la classe avait été un membre au lieu d'une classe de base.

2 votes

Son utilisation principale est de réduire la consommation d'espace là où cela est vraiment important, par exemple dans les classes de chaînes contrôlées par des politiques ou dans les paires compressées. en fait, boost::compressed_pair utilisait l'héritage protégé.

0 votes

Jalf : Hé, je n'avais pas réalisé ça. Je pensais que l'héritage non public était principalement utilisé comme un hack lorsque vous avez besoin d'accéder aux membres protégés d'une classe. Je me demande pourquoi un objet vide prendrait de la place quand on utilise la composition. Probablement pour l'adressabilité universelle...

25voto

Daemin Points 5651

Une utilisation utile de l'héritage privé est lorsque vous avez une classe qui implémente une interface, qui est ensuite enregistrée avec un autre objet. Vous rendez cette interface privée afin que la classe elle-même doive s'enregistrer et que seul l'objet spécifique avec lequel elle est enregistrée puisse utiliser ces fonctions.

Par exemple :

class FooInterface
{
public:
    virtual void DoSomething() = 0;
};

class FooUser
{
public:
    bool RegisterFooInterface(FooInterface* aInterface);
};

class FooImplementer : private FooInterface
{
public:
    explicit FooImplementer(FooUser& aUser)
    {
        aUser.RegisterFooInterface(this);
    }
private:
    virtual void DoSomething() { ... }
};

Par conséquent, la classe FooUser peut appeler les méthodes privées de FooImplementer par le biais de l'interface FooInterface, alors que les autres classes externes ne le peuvent pas. Il s'agit d'un excellent modèle pour gérer les rappels spécifiques qui sont définis comme des interfaces.

1 votes

En effet, l'héritage privé est un IS-A privé.

0 votes

@Daemin, pourquoi personne ne marque FooInterface comme abstraite ? Les interfaces sont généralement abstraites n'est-ce pas.

0 votes

@SouravKannanthaB Je ne sais pas ce que vous voulez dire par là. Il n'y a pas de mot-clé abstrait en C++.

19voto

Bill the Lizard Points 147311

Je pense que la section critique de la FAQ C++ Lite es:

Une utilisation légitime et à long terme de l'héritage privé est lorsque vous voulez construire une classe Fred qui utilise du code dans une classe Wilma, et que le code de la classe Wilma doit invoquer des fonctions membres de votre nouvelle classe Fred. Dans ce cas, Fred appelle des fonctions non virtuelles dans Wilma, et Wilma appelle (généralement des fonctions virtuelles pures) dans elle-même, qui sont surchargées par Fred. Cela serait beaucoup plus difficile à faire avec la composition.

En cas de doute, il faut préférer la composition à l'héritage privé.

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