Méthode virtuelle privée en C ++

125

Quel est l'avantage de rendre une méthode privée virtuelle en C ++?

J'ai remarqué cela dans un projet C ++ open source:

class HTMLDocument : public Document, public CachedResourceClient {
private:
    virtual bool childAllowed(Node*);
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};
Silverburgh
la source
9
Je pense que la question est à l'envers. La raison de rendre quelque chose de virtuel est toujours la même: permettre aux classes dérivées de le remplacer. La question devrait donc être: quel est l'avantage de rendre une méthode virtuelle privée? À quoi la réponse est: rendre tout privé par défaut. :-)
ShreevatsaR
1
@ShreevatsaR Mais vous n'avez même pas répondu à votre propre question ......
Spencer
@ShreevatsaR Je pensais que vous vouliez dire à l'envers d'une manière différente: quel est l'avantage de rendre une méthode virtuelle non privée?
Peter - Réintègre Monica

Réponses:

116

Herb Sutter l'a très bien expliqué ici .

Ligne directrice n ° 2: préférez rendre les fonctions virtuelles privées.

Cela permet aux classes dérivées de remplacer la fonction pour personnaliser le comportement selon les besoins, sans exposer davantage les fonctions virtuelles directement en les rendant appelables par des classes dérivées (comme cela serait possible si les fonctions étaient simplement protégées). Le fait est que les fonctions virtuelles existent pour permettre la personnalisation; à moins qu'ils ne doivent également être appelés directement à partir du code des classes dérivées, il n'est pas nécessaire de les rendre autre chose que privés

Prasoon Saurav
la source
Comme vous pouvez le deviner d'après ma réponse, je pense que la ligne directrice n ° 3 de Sutter pousse plutôt la ligne directrice n ° 2 par la fenêtre.
Spencer
66

Si la méthode est virtuelle, elle peut être remplacée par des classes dérivées, même si elle est privée. Lorsque la méthode virtuelle est appelée, la version remplacée sera appelée.

(Opposé à Herb Sutter cité par Prasoon Saurav dans sa réponse, la FAQ C ++ Lite déconseille les virtuels privés , principalement parce que cela déroute souvent les gens.)

qc
la source
41
Il semble que la FAQ C ++ Lite a depuis changé sa recommandation: " La FAQ C ++ recommandait auparavant d'utiliser des virtuels protégés plutôt que des virtuels privés. Cependant, l'approche virtuelle privée est maintenant assez courante pour que la confusion des novices soit moins préoccupante. "
Zack The Human
19
La confusion des experts reste toutefois préoccupante. Aucun des quatre professionnels C ++ assis à côté de moi n'était au courant des virtuels privés.
Newtonx
12

Malgré tous les appels à déclarer un membre virtuel privé, l'argument ne tient tout simplement pas la route. Souvent, le remplacement d'une fonction virtuelle par une classe dérivée devra appeler la version de la classe de base. Il ne peut pas s'il est déclaré private:

class Base
{
 private:

 int m_data;

 virtual void cleanup() { /*do something*/ }

 protected:
 Base(int idata): m_data (idata) {}

 public:

 int data() const { return m_data; }
 void set_data (int ndata) { m_data = ndata; cleanup(); }
};

class Derived: public Base
{
 private:
 void cleanup() override
 {
  // do other stuff
  Base::cleanup(); // nope, can't do it
 }
 public:
 Derived (int idata): base(idata) {}
};

Vous devez déclarer la méthode de classe de base protected.

Ensuite, vous devez prendre le vilain expédient d'indiquer via un commentaire que la méthode doit être surchargée mais pas appelée.

class Base
{
 ...
 protected:
 // chained virtual function!
 // call in your derived version but nowhere else.
 // Use set_data instead
 virtual void cleanup() { /* do something */ }
 ...

Ainsi la ligne directrice n ° 3 de Herb Sutter ... Mais le cheval est de toute façon hors de la grange.

Lorsque vous déclarez quelque chose, protectedvous faites implicitement confiance à l'écrivain de toute classe dérivée pour comprendre et utiliser correctement les composants internes protégés, tout comme une frienddéclaration implique une confiance plus profonde pour les privatemembres.

Les utilisateurs qui ont un mauvais comportement en violant cette confiance (par exemple étiquetés «ignorants» en ne prenant pas la peine de lire votre documentation) n'ont qu'à eux-mêmes à blâmer.

Mise à jour : j'ai eu quelques retours qui prétendent que vous pouvez "enchaîner" les implémentations de fonctions virtuelles de cette façon en utilisant des fonctions virtuelles privées. Si tel est le cas, j'aimerais bien le voir.

Les compilateurs C ++ que j'utilise ne laisseront certainement pas une implémentation de classe dérivée appeler une implémentation de classe de base privée.

Si le comité C ++ relâchait "privé" pour permettre cet accès spécifique, je serais tout pour les fonctions virtuelles privées. Dans l'état actuel des choses, on nous conseille toujours de verrouiller la porte de la grange après le vol du cheval.

Spencer
la source
3
Je trouve votre argument invalide. En tant que développeur d'une API, vous devez rechercher une interface difficile à utiliser de manière incorrecte et ne pas configurer un autre développeur pour vos propres erreurs en le faisant. Ce que vous voulez faire dans votre exemple pourrait être implémenté avec uniquement des méthodes virtuelles privées.
sigy
1
Ce n'est pas ce que je disais. Mais vous pouvez restructurer votre code pour obtenir le même effet sans avoir besoin d'appeler une fonction de classe de base privée
sigy
3
Dans votre exemple, vous souhaitez étendre le comportement de set_data. Instructions m_data = ndata;et cleanup();pourrait donc être considéré comme un invariant qui doit être valable pour toutes les implémentations. Rendez donc cleanup()non virtuel et privé. Ajoutez un appel à une autre méthode privée qui est virtuelle et le point d'extension de votre classe. Désormais, vos classes dérivées n'ont plus besoin d'appeler les bases cleanup(), votre code reste propre et votre interface est difficile à utiliser de manière incorrecte.
sigy
2
@sigy Cela ne fait que déplacer les poteaux de but. Vous devez regarder au-delà de l'exemple minime. Lorsqu'il y a d'autres descendants qui doivent appeler tous les cleanup()s de la chaîne, l'argument s'effondre. Ou recommandez-vous une fonction virtuelle supplémentaire pour chaque descendant de la chaîne? Ick. Même Herb Sutter a autorisé les fonctions virtuelles protégées comme une faille dans sa directive n ° 3. Quoi qu'il en soit, sans un code réel, vous ne me convaincrez jamais.
Spencer
2
Alors
acceptons
9

Je suis tombé sur ce concept pour la première fois en lisant «Effective C ++» de Scott Meyers, Item 35: Considérez des alternatives aux fonctions virtuelles. Je voulais citer Scott Mayers pour d'autres qui pourraient être intéressés.

Cela fait partie du modèle de méthode de modèle via l'idiome de l'interface non virtuelle : les méthodes destinées au public ne sont pas virtuelles; ils encapsulent plutôt les appels de méthode virtuelle qui sont privés. La classe de base peut alors exécuter la logique avant et après l'appel de fonction virtuelle privée:

public:
  void NonVirtualCalc(...)
  {
    // Setup
    PrivateVirtualCalcCall(...);
    // Clean up
  }

Je pense que c'est un modèle de conception très intéressant et je suis sûr que vous pouvez voir à quel point le contrôle ajouté est utile.

  • Pourquoi faire de la fonction virtuelle private? La meilleure raison est que nous avons déjà fourni une publicméthode de mise en face.
  • Pourquoi ne pas simplement faire en protectedsorte que je puisse utiliser la méthode pour d'autres choses intéressantes? Je suppose que cela dépendra toujours de votre conception et de la façon dont vous pensez que la classe de base s'intègre. Je dirais que le créateur de classe dérivée devrait se concentrer sur l'implémentation de la logique requise; tout le reste est déjà pris en charge. Il y a aussi la question de l'encapsulation.

D'un point de vue C ++, il est tout à fait légitime de remplacer une méthode virtuelle privée même si vous ne pourrez pas l'appeler depuis votre classe. Cela prend en charge la conception décrite ci-dessus.

Pooven
la source
3

Je les utilise pour permettre aux classes dérivées de "remplir les blancs" pour une classe de base sans exposer un tel trou aux utilisateurs finaux. Par exemple, j'ai des objets hautement avec état dérivant d'une base commune, qui ne peuvent implémenter que 2/3 de la machine à états globale (les classes dérivées fournissent le 1/3 restant en fonction d'un argument de modèle, et la base ne peut pas être un modèle pour autres raisons).

J'AI BESOIN d'avoir la classe de base commune pour que de nombreuses API publiques fonctionnent correctement (j'utilise des modèles variadiques), mais je ne peux pas laisser cet objet dans la nature. Pire encore, si je laisse les cratères dans la machine à états - sous la forme de fonctions virtuelles pures - n'importe où mais en "Privé", je permets à un utilisateur intelligent ou désemparé dérivant d'une de ses classes enfants de remplacer les méthodes que les utilisateurs ne devraient jamais toucher. Donc, j'ai mis la machine à états «cerveaux» dans les fonctions virtuelles PRIVÉES. Ensuite, les enfants immédiats de la classe de base remplissent les espaces sur leurs remplacements NON virtuels, et les utilisateurs peuvent utiliser en toute sécurité les objets résultants ou créer leurs propres classes dérivées supplémentaires sans se soucier de gâcher la machine à états.

Quant à l'argument selon lequel vous ne devriez pas AVOIR de méthodes virtuelles publiques, je dis BS. Les utilisateurs peuvent mal remplacer les virtuels privés tout aussi facilement que les publics - ils définissent de nouvelles classes après tout. Si le public ne doit pas modifier une API donnée, ne la rendez pas virtuelle DU TOUT dans les objets accessibles au public.

Zack Yezek
la source