Mixer du virtuel et non-virtuel dans une sous-classe

Mixer du virtuel et non-virtuel dans une sous-classe - C++ - Programmation

Marsh Posté le 20-12-2007 à 13:06:35    

Bonjour,
 
J'ai une question de debutant: je voudrais tirer avantage du polymorphisme mais aussi avoir des fonctions specifiques dans mes sous-classes derivees de classe abstraite. Comment faire ?
 
Voici un exemple:
 

Code :
  1. #include <iostream>
  2. using namespace std;
  3. //A is  
  4. class A {
  5. public:
  6. A() {}
  7. virtual void print() = 0;
  8. };
  9. class B : public A {
  10. protected:
  11. int b;
  12. public:
  13. B() : A() {}
  14. void print() { cout << "I am a B" << endl;}
  15. void setb(int n) {b = n;}
  16. };
  17. int main(int argc, char** argv) {
  18. A* objb = new B();
  19. objb->print();
  20. objb->setb(3); //error! 'class A' has no member named 'setb'
  21. }


 
Je pourrais mettre setb en virtuel dans la classe abstraite, mais ca n'aurait pas beaucoup de sens: setb est une fonction qui change b, specifique de la classe B, ca ne devrait pas faire partie de l'interface fournie par A. Ou alors je pourrais creer B comme un B* objb, mais je perdrais le polymorphisme sur print(). Comment fait-on pour ajouter a une interface des fonctions specifiques a mes sous-classes?


Message édité par libredr le 20-12-2007 à 13:07:00
Reply

Marsh Posté le 20-12-2007 à 13:06:35   

Reply

Marsh Posté le 20-12-2007 à 13:16:03    

Soit c'est pertinent d'inclure des méthodes dans A() qui doivent être implémentées dans les classes filles, soit ça ne l'est pas.
 
Dans ton cas, on peut imaginer que A prévoit qu'une propriété particulière doit exister, et B l'implémente.
Pour reprendre l'exemple bateau du polygone, on peut imaginer que A contient la propriété "nombre de côtés", avec les setter/getter qui vont bien, et toutes les classes filles l'implémentent.  
Mais si ta classe B définit, je ne sais pas, un triangle, en implémentant A, alors seule la classe B contiendra la propriété "médiane" par exemple.
 
Bref, c'est surtout une affaire de conception. Si setb() n'a rien à faire dans A, alors tu ne devrais pas rencontrer de cas où tu dois l'utiliser avec une classe A.
 
edit : par contre, si tu es sûr et certain que ton objet est de type B, tu peux faire :  

dynamic_cast<B*>(objb)->setb(3);

Message cité 1 fois
Message édité par Elmoricq le 20-12-2007 à 13:23:15
Reply

Marsh Posté le 20-12-2007 à 13:57:18    

Elmoricq a écrit :


Mais si ta classe B définit, je ne sais pas, un triangle, en implémentant A, alors seule la classe B contiendra la propriété "médiane" par exemple.
 
Bref, c'est surtout une affaire de conception. Si setb() n'a rien à faire dans A, alors tu ne devrais pas rencontrer de cas où tu dois l'utiliser avec une classe A.


 
La je ne te suis pas tres bien: le triangle et la mediane est exactement le probleme que j'ai souleve. Si je cree un objet triangle en utilisant un pointeur de la classe de base, pour beneficier du polymorphisme, je perds la capacite a utiliser une fonction ou attribut "mediane". Donc il est impossible de deriver des objets d'une classe de base et de leur rajouter des specificites si on veut utiliser le polymorphisme  :??:  
 

Citation :

edit : par contre, si tu es sûr et certain que ton objet est de type B, tu peux faire :  

dynamic_cast<B*>(objb)->setb(3);



 
 
Ah d'accord, c'est la que le dynamic cast joue son role. Est-ce que faire ce genre d'operations est habituel ? Est-ce la procedure "normale" pour rajouter des fonctions specifiques a une sous-classe et quand meme utiliser un pointeur de la base de classe ?

Message cité 1 fois
Message édité par libredr le 20-12-2007 à 14:20:25
Reply

Marsh Posté le 20-12-2007 à 14:17:48    

libredr a écrit :

La je ne te suis pas tres bien: le triangle et la mediane est exactement le probleme que j'ai souleve. Si je cree un objet triangle en utilisant un pointeur de la classe de base, pour beneficier du polymorphisme, je perds la capacite a utiliser une fonction ou attribut "mediane". Donc il est impossible de deriver des objets d'une classe de base et de leur rajouter des specificites si on veut utiliser le polymorphisme  :??:  


 
Ce n'est pas le but du polymorphisme.
Le polymorphisme, ça sert à gérer de manière standard n classes dérivées, à l'aide d'une classe de base et de ses propriétés à elle qui, par le biais de l'héritage, sont communes à toutes ses classes filles. Toute spécificité d'une classe dérivée n'est bien entendu pas accessible à partir de la classe de base.
 
Pour reprendre l'exemple standard de la classe de base A qui définit polygone, imagine des classes B, C et D qui définissent, respectivement, un triangle, un quadrilatère et un dodécagone.
La classe A contient une méthode abstraite "aire()" par exemple, qui retourne l'aire du polygone. Ici le polymorphisme s'applique : B, C et D auront chacune leur méthode propre pour déterminer l'aire de l'objet qu'elles définissent.
Tu peux ainsi définir une liste de classes A, contenant en vrai des classes B, C ou D, et appeler à chaque fois la méthode aire() qui retournera tout le temps la bonne aire de l'objet, sans que tu n'aies jamais à te soucier de la façon dont elle est calculée derrière.
 
En revanche, comme B définit un triangle, si tu veux avoir des informations sur ses médianes, alors c'est quelque chose que tu devras faire en utilisant un objet B spécifiquement, puisque c'est une propriété qui n'a pas de sens pour les classes A, C et D.
C'est là que le polymorphisme ne s'applique plus, et c'est normal. Reste donc la méthode de cast qui te permet, à partir d'un objet A, de retrouver ton objet B (SI c'est bien un objet B, et ça le dynamic_cast te le dira à coup d'exception si ce n'est pas le cas).
 
Comme tu le vois, c'est affaire de conception.

Reply

Marsh Posté le 20-12-2007 à 14:25:02    

Elmoricq a écrit :


En revanche, comme B définit un triangle, si tu veux avoir des informations sur ses médianes, alors c'est quelque chose que tu devras faire en utilisant un objet B spécifiquement, puisque c'est une propriété qui n'a pas de sens pour les classes A, C et D.
C'est là que le polymorphisme ne s'applique plus, et c'est normal.


 
D'accord. Donc le seul moyen d'appeler une "specificite" d'une sous-classe est d'utiliser le dynamic_cast ? Est-ce qu'il y a d'autres moyens, par exemple:
 

Code :
  1. B* bptr = new B;


 
la pas de polymorphisme, j'appelle bptr->print() a la compilation.
Est-ce que je peux faire:
 

Code :
  1. A* bptr2 = bptr


et faire bptr2->print() qui sera resolu a run-time ?

Reply

Marsh Posté le 20-12-2007 à 14:27:38    

Bien sûr. [:romf]
 
B est aussi un objet de type A.

Reply

Marsh Posté le 20-12-2007 à 14:34:19    

OK, merci beaucoup pour ces infos.  :jap:

Reply

Marsh Posté le 20-12-2007 à 15:11:50    

Je me reponds a moi-meme: peut-etre que je prends le probleme a l'envers. Peut-etre faut-il utiliser le run-time binding et le polymorphisme que lorsque c'est necessaire, et utiliser un pointeur de la sous-classe en "temps normal", quand je veux utiliser les specificites de cette classe. C'est ca?

Reply

Marsh Posté le 20-12-2007 à 15:20:14    

Là comme ça, sans plus d'information sur ce que tu dois faire, je ne sais pas te répondre, ça dépend des cas que tu dois gérer.
 
En gros, tu as des outils à ta disposition. À toi de comprendre à quoi ils servent, et de les utiliser au mieux. [:spamafote]

Reply

Marsh Posté le 20-12-2007 à 15:30:00    

Elmoricq a écrit :

Là comme ça, sans plus d'information sur ce que tu dois faire, je ne sais pas te répondre, ça dépend des cas que tu dois gérer.

 

En gros, tu as des outils à ta disposition. À toi de comprendre à quoi ils servent, et de les utiliser au mieux. [:spamafote]

 

Je comprends, mais ma question ne se veut pas tellement specifique, mais plus "design" en general. En general: comment gere des sous-classes qui etended la classe de base (classique) mais tout en profitant du polymorphisme. D'apres ce que je comprends a tous ces conseils, c'est que le polymorphisme n'a pas besoin d'etre utilise. :)


Message édité par libredr le 20-12-2007 à 15:30:14
Reply

Marsh Posté le 20-12-2007 à 15:30:00   

Reply

Marsh Posté le 21-12-2007 à 10:27:24    

Sans avoir à utiliser des dynamic cast, qui dans un code aussi simple sont un peu en trop, il faut se rappeller une chose:

Citation :

Si je déclare un objet de type A, c'est pour l'utiliser en tant qu'objet de type A.


 
Dans ton exemple, tu déclares ton objet de type A pour l'utiliser en tant qu'objet de type B. A ce point là le compilateur t'envoie bouler avec raison: tu es en train de faire du bidouillage. Ce qu'il faut, c'est que tu déclares ton objet comme étant de type B. Pour cela, plusieurs manières.
 
Si tu es dans le cas ou tu dois utiliser une fonction des objets de type B uniquement, tu peux tenter de fournir à la classe A la fonction de traitement appropriée, tout en la définissant comme renvoyant une valeur par défaut qui soit correcte pour les autres cas. Par exemple, dans le cas d'un véhicule, sachant que les sous-marins possèdent une profondeur maximale de plongée, on peut implémenter la fonction profondeurMax() dans la classe véhicule, celle ci renverra 0 par défaut (les véhicules qui ne la précisent pas n'iront pas sous l'eau) et sera redéfinie plus précisément dans SousMarin.
 
Si c'est ton traitement de l'objet qui doit varier suivant sa classe, à savoir que tu envoie les véhicules au garage et les sous-marins en cale seche s'il sont abimés, alors tu devras utiliser une bete surcharge de fonction.
Soit au final deux fonctions:
 
Fonction reparer(Vehicule v)
 emploiDuTemps.add(v.reparer(), garage.obtenirDateRendezVous())
Fin fonction
 
Fonction reparer(SousMarin sm)
 caleSeche.ouvrir()
 caleSeche.add(sm)
 commencerReparationDansCaleSeche()
Fin fonction  
 
Au cas ou tu penses ne pas avoir le choix dans le type de la variable que tu reçois à traiter (c'est un A parce que c'est défini comme tel), tu peux forcer la main en créant une nouvelle fonction:
 
Fonction privée traitement(B aTraiter)
 aTraiter.traitementSpecialDeB()
Fin fonction
 
Fonction privée traitement(A aTraiter)
Fin fonction
 
Fonction publique traitementComplet(A aTraiter)
 traitement(aTraiter)
Fin fonction
 
Si ces solutions ne conviennent pas, c'est probablement une erreur dans ton analyse.  

Message cité 1 fois
Message édité par bapho13 le 21-12-2007 à 10:29:08
Reply

Marsh Posté le 21-12-2007 à 10:36:34    

bapho13 a écrit :

Si tu es dans le cas ou tu dois utiliser une fonction des objets de type B uniquement, tu peux tenter de fournir à la classe A la fonction de traitement appropriée, tout en la définissant comme renvoyant une valeur par défaut qui soit correcte pour les autres cas. Par exemple, dans le cas d'un véhicule, sachant que les sous-marins possèdent une profondeur maximale de plongée, on peut implémenter la fonction profondeurMax() dans la classe véhicule, celle ci renverra 0 par défaut (les véhicules qui ne la précisent pas n'iront pas sous l'eau) et sera redéfinie plus précisément dans SousMarin.


 
Hmm. Je ne suis pas sûr d'adhérer à ce genre de conception, je trouve que ça ruine trop facilement la cohérence d'un diagramme de classes. Si on n'y prend pas garde, on se retrouve vite avec une kyrielle de propriétés spécifiques dans la classe de base. :/
Si tu veux définir une classe de véhicules capables d'aller en profondeur (sous terre ou dans la flotte), alors tu définis une classe intermédiaire de véhicules sous-marin/terrain, non ?
En tout cas, je ne vois absolument aucune raison pour un avion d'avoir une profondeur max, même si celle-ci retourne 0.
 
Je reste convaincu qu'avec une bonne conception, on utilise sans aucun problème la classe de base. Si on a besoin de plus, c'est qu'on rencontre un cas spécifique, ce qui signifie qu'on doit utiliser la ou les classes spécifiques ad hoc.

Message cité 1 fois
Message édité par Elmoricq le 21-12-2007 à 10:40:07
Reply

Marsh Posté le 21-12-2007 à 10:41:49    

Elmoricq a écrit :


 
Hmm. Je ne suis pas sûr d'adhérer à ce genre de conception, je trouve que ça ruine trop facilement la cohérence d'un diagramme de classes. Si on n'y prend pas garde, on se retrouve vite avec une kyrielle de propriétés spécifiques dans la classe de base. :/
Si tu veux définir une classe de véhicules capables d'aller en profondeur (sous terre ou dans la flotte), alors tu définis une classe intermédiaire de véhicules sous-marin/terrain, non ?
En tout cas, je ne vois absolument aucune raison pour un avion d'avoir une profondeur max, même si celle-ci retourne 0.
 
Je reste convaincu qu'avec une bonne conception, on utilise sans aucun problème la classe de base. Si on a besoin de plus, c'est qu'on rencontre un cas spécifique, ce qui signifie qu'on doit utiliser la ou les classes spécifiques ad hoc.


 
Oui je suis tout à fait d'accord, c'est juste que dans certains cas ça peut simplifier la vie (nottament si certaines fonctions de la classe abstraite peuvent y faire appel).

Reply

Marsh Posté le 21-12-2007 à 11:45:56    

Salut,
 
Selon le besoin, il peut aussi être intéressant de considérer une aggrégation de comportement. Chaque comportement étant instancié selon le type de véhicule à créer (l'arbre d'héritage est ainsi déplacé là où il est uniquement nécessaire).
Un bon article sur le sujet => http://cowboyprogramming.com/2007/ [...] -heirachy/

Reply

Marsh Posté le 21-12-2007 à 19:23:13    

Super toutes ces idées.
Je viens de relire le châpitre sur le polymorphisme dans "Thinking in C++" de Bruce Eckel et ça m'a pas mal ouvert les yeux. En effet, le polymorphisme ne devrait être utilisé que pour les fonctions fournies par l'interface de la classe de base.
 
Et il y a un autre moyen pour étendre l'interface d'une sous-classe: la multi-inheritance de plusieurs classes abstraites, fournissant des interfaces différentes et complémentaires.

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed