Quel est l'intérêt d'une fonction virtuelle pure privée?

139

Je suis tombé sur le code suivant dans un fichier d'en-tête:

class Engine
{
public:
    void SetState( int var, bool val );
    {   SetStateBool( int var, bool val ); }

    void SetState( int var, int val );
    {   SetStateInt( int var, int val ); }
private:
    virtual void SetStateBool(int var, bool val ) = 0;    
    virtual void SetStateInt(int var, int val ) = 0;    
};

Pour moi, cela implique que la Engineclasse ou une classe qui en dérive doit fournir l'implémentation de ces fonctions virtuelles pures. Mais je ne pensais pas que les classes dérivées pouvaient avoir accès à ces fonctions privées pour les réimplémenter - alors pourquoi les rendre virtuelles?

BeeBand
la source

Réponses:

209

La question du sujet suggère une confusion assez courante. La confusion est assez courante, que la FAQ C ++ a prôné pendant longtemps l'utilisation de virtuels privés, car la confusion semblait être une mauvaise chose.

Donc, pour se débarrasser de la confusion en premier: Oui, les fonctions virtuelles privées peuvent être remplacées dans les classes dérivées. Les méthodes des classes dérivées ne peuvent pas appeler des fonctions virtuelles à partir de la classe de base, mais elles peuvent leur fournir leur propre implémentation. Selon Herb Sutter, avoir une interface publique non virtuelle dans la classe de base et une implémentation privée qui peut être personnalisée dans les classes dérivées, permet une meilleure «séparation de la spécification de l'interface de la spécification du comportement personnalisable de l'implémentation». Vous pouvez en savoir plus à ce sujet dans son article "Virtuality" .

Il y a cependant une autre chose intéressante dans le code que vous avez présenté, qui mérite un peu plus d'attention, à mon avis. L'interface publique se compose d'un ensemble de fonctions non virtuelles surchargées et ces fonctions appellent des fonctions virtuelles non publiques et non surchargées. Comme d'habitude dans le monde C ++, c'est un idiome, il a un nom et bien sûr c'est utile. Le nom est (surprise, surprise!)

«Public surchargé non-virtuels virtuels protégés contre les appels non surchargés»

Cela aide à gérer correctement la règle de masquage . Vous pouvez en savoir plus ici , mais je vais essayer de l'expliquer sous peu.

Imaginez que les fonctions virtuelles de la Engineclasse sont aussi son interface et que c'est un ensemble de fonctions surchargées qui ne sont pas purement virtuelles. S'ils étaient purement virtuels, on pourrait toujours rencontrer le même problème, comme décrit ci-dessous, mais plus bas dans la hiérarchie des classes.

class Engine
{
public:
    virtual void SetState( int var, bool val ) {/*some implementation*/}
    virtual void SetState( int var, int val )  {/*some implementation*/}
};

Supposons maintenant que vous souhaitiez créer une classe dérivée et que vous deviez fournir une nouvelle implémentation uniquement pour la méthode, qui prend deux entiers comme arguments.

class MyTurbochargedV8 : public Engine
{
public:
    // To prevent SetState( int var, bool val ) from the base class,
    // from being hidden by the new implementation of the other overload (below),
    // you have to put using declaration in the derived class
    using Engine::SetState;

    void SetState( int var, int val )  {/*new implementation*/}
};

Si vous avez oublié de mettre la déclaration using dans la classe dérivée (ou de redéfinir la deuxième surcharge), vous pourriez avoir des problèmes dans le scénario ci-dessous.

MyTurbochargedV8* myV8 = new MyTurbochargedV8();
myV8->SetState(5, true);

Si vous n'avez pas empêché la dissimulation des Enginemembres, la déclaration:

myV8->SetState(5, true);

appellerait à void SetState( int var, int val )partir de la classe dérivée, convertissant trueen int.

Si l'interface n'est pas virtuelle et que l'implémentation virtuelle n'est pas publique, comme dans votre exemple, l'auteur de la classe dérivée a un problème de moins à penser et peut simplement écrire

class MyTurbochargedV8 : public Engine
{
private:
    void SetStateInt(int var, int val )  {/*new implementation*/}
};
Maciej Hehl
la source
pourquoi la fonction virtuelle doit-elle être privée? Cela peut-il être public?
Riche du
Je me demande si les directives données par Herb Sutter dans son article "Virtuality" tiennent toujours aujourd'hui?
nurabha
@Rich Vous pouvez, mais en les rendant non publics, vous pouvez exprimer leur intention plus clairement. Premièrement, cela montre une séparation des préoccupations si vous vous en tenez à rendre l'interface publique et l'implémentation non publique. Deuxièmement, si vous voulez que les classes héritées puissent appeler les implémentations de base, vous pouvez les déclarer protégées; si vous voulez seulement qu'ils fournissent leurs propres implémentations sans appeler celles de base, vous les rendez privées.
Dan
43

La fonction virtuelle pure privée est la base de l' idiome d' interface non virtuelle (OK, ce n'est pas absolument toujours pur virtuel, mais toujours virtuel là-bas). Bien sûr, cela est également utilisé pour d'autres choses, mais je trouve cela pour le plus utile (: En deux mots: dans une fonction publique, vous pouvez mettre des choses courantes (telles que la journalisation, les statistiques, etc.) au début et à la fin de la fonction et ensuite, "au milieu" pour appeler cette fonction virtuelle privée, ce sera différent pour la classe dérivée spécifique. Quelque chose comme:

class Base
{
    // ..
public:
    void f();
private:
    virtual void DerivedClassSpecific() = 0;
   // ..
};
void Base::f()
{
    //.. Do some common stuff
    DerivedClassSpecific();
    //.. Some other common stuff
}
// ..

class Derived: public Base
{
    // ..
private:
    virtual void DerivedClassSpecific();
    //..
};
void Derived::DerivedClassSpecific()
{
    // ..
}

Pure virtual - oblige simplement les classes dérivées à l'implémenter.

EDIT : Plus à ce sujet: Wikipedia :: NVI-idiom

Kiril Kirov
la source
17

Eh bien, d'une part, cela permettrait à une classe dérivée d'implémenter une fonction que la classe de base (contenant la déclaration de fonction virtuelle pure) peut appeler.

Michael Goldshteyn
la source
5
que seule la classe de base peut appeler!
underscore_d
4

EDIT: des déclarations clarifiées sur la capacité de passer outre et la capacité d'accéder / invoquer.

Il pourra remplacer ces fonctions privées. Par exemple, l'exemple artificiel suivant fonctionne ( EDIT: rend la méthode de classe dérivée privée et supprime l'invocation de la méthode de classe dérivée main()pour mieux démontrer l'intention du modèle de conception utilisé. ):

#include <iostream>

class Engine
{
public:
  void SetState( int var, bool val )
  {
    SetStateBool( var, val );
  }

  void SetState( int var, int val )
  {
    SetStateInt( var, val );
  }

private:

    virtual void SetStateBool(int var, bool val ) = 0;
    virtual void SetStateInt(int var, int val ) = 0;

};

class DerivedEngine : public Engine
{
private:
  virtual void SetStateBool(int var, bool val )
  {
    std::cout << "DerivedEngine::SetStateBool() called" << std::endl;
  }

  virtual void SetStateInt(int var, int val )
  {
    std::cout << "DerivedEngine::SetStateInt() called" << std::endl;
  }
};


int main()
{
  DerivedEngine e;
  Engine * be = &e;

  be->SetState(4, true);
  be->SetState(2, 1000);
}

Private virtualLes méthodes d'une classe de base comme celles de votre code sont généralement utilisées pour implémenter le modèle de conception de méthode modèle . Ce modèle de conception permet de changer le comportement d'un algorithme dans la classe de base sans changer le code de la classe de base. Le code ci-dessus où les méthodes de classe de base sont appelées via un pointeur de classe de base est un exemple simple du modèle de méthode modèle.

Néant
la source
Je vois, mais si les classes dérivées ont quand même une sorte d'accès, pourquoi se donner la peine de les rendre privées?
BeeBand
@BeeBand: L'utilisateur aurait accès aux substitutions de méthode virtuelle de classe dérivée publique, mais n'aurait pas accès à celles de la classe de base. Dans ce cas, l'auteur de la classe dérivée peut également garder les substitutions de méthode virtuelle privées. En fait, je vais apporter la modification dans l'exemple de code ci-dessus pour souligner cela. Dans tous les cas, ils pourraient toujours hériter publiquement et remplacer les méthodes virtuelles de la classe de base privée, mais ils n'auraient toujours accès qu'à leurs propres méthodes virtuelles de classe dérivée. Notez que je fais une distinction entre le remplacement et l'accès / l'invocation.
Void
parce que tu as tort. la visibilité de l'héritage entre les classes Engineet DerivedEnginen'a rien à voir avec ce qui DerivedEnginepeut ou ne peut pas remplacer (ou accéder, d'ailleurs).
wilhelmtell
@wilhelmtell: soupir Vous avez bien sûr raison. Je mettrai à jour ma réponse en conséquence.
Annulé
3

La méthode virtuelle privée est utilisée pour limiter le nombre de classes dérivées qui peuvent remplacer la fonction donnée. Les classes dérivées qui doivent remplacer la méthode virtuelle privée devront être un ami de la classe de base.

Une brève explication peut être trouvée sur DevX.com .


EDIT Une méthode virtuelle privée est effectivement utilisée dans Template Method Pattern . Les classes dérivées peuvent remplacer la méthode virtuelle privée mais les classes dérivées ne peuvent pas appeler sa méthode virtuelle privée de classe de base (dans votre exemple, SetStateBoolet SetStateInt). Seule la classe de base peut effectivement appeler sa méthode virtuelle privée ( uniquement si les classes dérivées doivent appeler l'implémentation de base d'une fonction virtuelle, protégez la fonction virtuelle ).

Un article intéressant peut être trouvé sur la virtualité .

Buhake Sindi
la source
2
@ Gentleman ... hmmm faites défiler jusqu'au commentaire de Colin D Bennett. Il semble penser qu '"Une fonction virtuelle privée peut être remplacée par des classes dérivées, mais ne peut être appelée qu'à partir de la classe de base.". @Michael Goldshteyn pense aussi comme ça.
BeeBand
Je suppose que vous avez oublié le principe selon lequel une classe privée ne peut pas être vue par sa classe dérivée. Ce sont les règles de la POO et elles s'appliquent à toutes les langues qui sont OOP. Pour qu'une classe dérivée implémente sa méthode virtuelle privée de classe de base, elle doit être une friendde la classe de base. Qt a adopté la même approche lors de la mise en œuvre de son modèle de document XML DOM.
Buhake Sindi
@ Gentleman: Non, je n'ai pas oublié. J'ai fait une faute de frappe dans mon commentaire. Au lieu de «accéder aux méthodes de la classe de base», j'aurais dû écrire «peut remplacer les méthodes de la classe de base». La classe dérivée peut certainement remplacer la méthode de classe de base virtuelle privée même si elle ne peut pas accéder à cette méthode de classe de base. L'article DevX.com que vous avez signalé était incorrect (héritage public). Essayez le code dans ma réponse. Malgré la méthode de classe de base virtuelle privée, la classe dérivée est capable de la remplacer. Ne confondons pas la possibilité de remplacer une méthode de classe de base virtuelle privée avec la possibilité de l'invoquer.
Annulé
@Gentleman: @wilhelmtell a signalé l'erreur dans ma réponse / commentaire. Ma réclamation concernant l'héritage affectant l'accessibilité des classes dérivées de la méthode de classe de base était désactivée. J'ai supprimé le commentaire offensant de votre réponse.
Annulé
@Void, je vois qu'une classe dérivée peut remplacer sa méthode virtuelle privée de classe de base mais elle ne peut pas l'utiliser. Il s'agit donc essentiellement d'un modèle de méthode de modèle.
Buhake Sindi
0

TL; DR réponse:

Vous pouvez le traiter comme un autre niveau d'encapsulation - quelque part entre protégé et privé : vous ne pouvez pas l'appeler à partir d'une classe enfant, mais vous pouvez le remplacer.

Il est utile lors de l'implémentation du modèle de conception de méthode modèle. Vous pouvez utiliser protégé , mais privé avec virtuel peut être considéré comme un meilleur choix, en raison d'une meilleure encapsulation.

jaskmar
la source