2009-01-27 12 views
37

Y a-t-il une raison de différencier les autorisations sur une fonction virtuelle C++ remplacée de la classe de base? Y a-t-il un danger à le faire?Substitution de fonctions virtuelles publiques avec des fonctions privées en C++

Par exemple:

class base { 
    public: 
     virtual int foo(double) = 0; 
} 

class child : public base { 
    private: 
     virtual int foo(double); 
} 

Le C++ faq dit qu'il est une mauvaise idée, mais ne dit pas pourquoi.

J'ai vu cet idiome dans un code et je crois que l'auteur essayait de rendre la classe finale, basée sur l'hypothèse qu'il n'est pas possible de surcharger une fonction membre privée. Cependant, This article montre un exemple de substitution de fonctions privées. Bien sûr, another part of the C++ faq recommande de ne pas le faire.

Mes questions concrètes:

  1. Est-il un problème technique avec l'aide d'une autre autorisation pour des méthodes virtuelles dans les classes dérivées vs classe de base?

  2. Y a-t-il une raison légitime de le faire?

+3

réinventant "protégé" sommes-nous? –

Répondre

27

Le problème est que les méthodes de classe de base sont sa façon de déclarer son interface. C'est, en substance, dire: "Ce sont les choses que vous pouvez faire pour les objets de cette classe."

Lorsque vous créez un objet que la base a déclaré public-privé, vous enlevez quelque chose maintenant, même si un objet dérivé" est-un "objet de base, quelque chose que vous devriez pouvoir faire à un objet de classe de base que vous ne pouvez pas faire à un objet de classe dérivé, briser le Liskov Substitution Prinicple

Cela causera-t-il un problème "technique" dans votre programme? Peut-être que non, mais cela signifiera probablement que l'objet de vos classes ne se comportera pas dans

Si vous vous trouvez dans la situation où c'est ce que vous voulez (sauf dans le cas d'une méthode obsolète mentionnée dans une autre réponse), le hasard s avez-vous un modèle d'héritage où l'héritage ne modélise pas vraiment "is-a" (par ex. L'exemple de Scott Myers Square héritant de Rectangle, mais vous ne pouvez pas modifier la largeur d'un carré indépendamment de sa hauteur comme vous pouvez le faire pour un rectangle) et vous devrez peut-être reconsidérer vos relations de classe.

6

Il n'y a pas de problème technique, mais vous vous retrouverez dans une situation où les fonctions disponibles publiquement dépendront de si vous avez un pointeur de base ou dérivé.

Ceci à mon avis serait bizarre et déroutant.

35

Vous obtenez le résultat surprenant que si vous avez un enfant, vous ne pouvez pas appeler foo, mais vous pouvez le convertir en base et ensuite appeler foo.

child *c = new child(); 
c->foo; // compile error (can't access private member) 
static_cast<base *>(c)->foo(); // this is fine, but still calls the implementation in child 

Je suppose que vous pourriez être en mesure d'inventer un exemple où vous ne voulez pas une fonction exposée, sauf lorsque vous traitez comme une instance de la classe de base. Mais le fait même que cette situation apparaisse suggère une mauvaise conception OO quelque part le long de la ligne qui devrait probablement être refactorisée.

+0

Qt fait cela et m'a amené ici :) Merci. – mlvljr

+3

Aussi: en tant qu'EDI, vous êtes nul! – mlvljr

+21

@mlvljr: Mais en tant qu'événement astronomique, je suis plutôt génial! – Eclipse

3

Cela peut être fait, et très occasionnellement apportera des avantages. Par exemple, dans notre base de code, nous utilisons une bibliothèque qui contient une classe avec une fonction publique que nous avions l'habitude d'utiliser, mais décourage maintenant l'utilisation en raison d'autres problèmes potentiels (il existe des méthodes plus sûres à appeler). Nous avons aussi une classe dérivée de cette classe que beaucoup de notre code utilise directement. Donc, nous avons rendu la fonction donnée privée dans la classe dérivée afin d'aider tout le monde à se souvenir de ne pas l'utiliser si elle peut l'aider. Il n'élimine pas la possibilité de l'utiliser, mais il va attraper certaines utilisations lorsque le code essaie de compiler, plutôt que plus tard dans les révisions de code.

1
  1. Aucun problème technique, si vous voulez dire par technique car il y a un coût d'exécution caché.
  2. Si vous héritez publiquement de la base, vous ne devriez pas le faire. Si vous héritez via protected ou private, cela peut vous aider à éviter d'utiliser des méthodes qui n'ont pas de sens à moins d'avoir un pointeur de base.
4

Cela peut être très utile si vous utilisez un héritage privé, c'est-à-dire que vous souhaitez réutiliser une fonctionnalité (personnalisée) d'une classe de base, mais pas l'interface.

1

Une bonne utilisation de l'héritage privé est une interface d'événement Listener/Observer.

Exemple de code pour l'objet privé:

class AnimatableListener { 
    public: 
    virtual void Animate(float delta_time); 
}; 

class BouncyBall : public AnimatableListener { 
    public: 
    void TossUp() {} 
    private: 
    void Animate(float delta_time) override { } 
}; 

Certains utilisateurs de l'objet veulent la fonctionnalité parent et certains veulent la fonctionnalité de l'enfant:

class AnimationList { 
    public: 
    void AnimateAll() { 
     for (auto & animatable : animatables) { 
     // Uses the parent functionality. 
     animatable->Animate(); 
     } 
    } 
    private: 
    vector<AnimatableListener*> animatables; 
}; 

class Seal { 
    public: 
    void Dance() { 
     // Uses unique functionality. 
     ball->TossUp(); 
    } 
    private: 
    BouncyBall* ball; 
}; 

De cette façon, le AnimationList peut contenir une référence à les parents et utilise la fonctionnalité parent. Alors que le Seal contient une référence à l'enfant et utilise la fonctionnalité enfant unique et ignorant les parents. Dans cet exemple, le Seal ne doit pas appeler Animate. Maintenant, comme mentionné ci-dessus, Animate peut être appelée en castant à l'objet de base, mais c'est plus difficile et ne devrait généralement pas être fait.

+0

Interface écouteur/observateur, réception de rappels. – dashesy

Questions connexes