[C++ extreme ...] héritage virtuelle multiple et cast

héritage virtuelle multiple et cast [C++ extreme ...] - Programmation

Marsh Posté le 01-03-2002 à 17:24:10    

bon j'ai une question pointu sur l'héritage en C++. Je sais que ce problème est résolu par le Jave ou le c#, mais en C++ je ne vous pas comment faire.
 
J'ai défini une classe interface A (c'est à dire : une classe qui n'a que des fonctions virtuelles pures) et deux classes interfaces B et C, qui hérite de toutes les deux de A.
 
on se trouve donc avec un code comme ceci :
class A
{};
class B : public A
{};
class C : public A
{};
 
Et quand je défini un classe D qui hérite de B et C, j'ai quelques problèmes.
 
class D : public B, public C
{};
 
En effet lors de l'appel du construteur de D, le constructeur de B est appeler une fois, celui de C une fois et celui de A deux fois.
Jusqu'ici tout est normal. A est instancier deux fois.
 
J'ai donc fais de l'héritage virtuelle :
class A
{};
class B : virtual public A
{};
class C : virtual public A
{};
 
et dans ce cas, ma classe A n'est instancié qu'une seule fois. C'est donc ce que je recherchais.
Par contre, j'ai une méthode A* CreateObjet( int id ) qui me retourne des A* mais qui en fait crée des classes de type D* (ou autre mais qui hérite de A*)...
 
Donc avant de faire de l'héritage multiple, je faisais :
D*   d=(D*)CreateObject( IdDelaClasseD );
 
Mais depuis que je suis passé en héritage virtual, le compilo ne veut plus convertir les Classes A* en D*.
Dans la MSDN et sur d'autre site, j'ai lu que c'était interdit (Compiler Error C2635 sous visual).
 
Donc, ma question c'est :
pourquoi c'est interdit ???? Normalement il ne devrait pas y avoir de problème. non ?
 
Es-ce que quelqu'un de savant pourrait m'expliquer ?
 
Leander.

Reply

Marsh Posté le 01-03-2002 à 17:24:10   

Reply

Marsh Posté le 01-03-2002 à 17:33:41    

Je comprends ton problème. Ca m'est arrivé une fois mais en fait je me suis aperçu qu'il n'y avait pas besoin de caster (d'où l'interet de l'héritage, n'est ce pas ?)
 
Je pense que le problème est dû au fait que le compilo ne sait pas s'il faut passer par B ou par C pour arriver à D.  
 
Essaie d'employer dynamic_cast<>, la programmation C++ puriste n'utilise plus les cast à l'ancienne.


---------------
"Dieu a exploité tous nos complexes d'infériorité, en commençant par notre incapacité de croire à notre propre divinité." - Emil Michel Cioran
Reply

Marsh Posté le 01-03-2002 à 17:41:04    

Ah non en fait, c'est un autre probleme qui ne dépend pas de B ou de C : voila la doc MSDN
 
cannot convert an 'identifier1*' to an 'identifier2*'; conversion from a virtual base class is implied
 
The conversion requires a cast from a virtual base class to a derived class, which is not allowed. The followings sample generates C2635:
 

Code :
  1. // C2635.cpp
  2. class B
  3. {
  4. };
  5. class D : virtual public B   // C2635 remove virtual to resolve the error
  6. {
  7. };
  8. int main()
  9. {
  10.     B b;
  11.     D d;
  12.     D * pD = &d;
  13.     pD = (D*)&b;   // C2635
  14. }


 
C'est marrant parce que j'utilise de temps en temps le multi heritage virtuel mais je n'ai pas rencontré ce problème. Mais faut dire que j'évite toujours le sous-casting car c'est mal.


---------------
"Dieu a exploité tous nos complexes d'infériorité, en commençant par notre incapacité de croire à notre propre divinité." - Emil Michel Cioran
Reply

Marsh Posté le 01-03-2002 à 17:44:12    

j'ai bien sur essayer le dynamic_cast. Et le compilo met une erreur qui dit d'utiliser un statique cast.
Et si je met un statique cast j'ai l'erreur que je t'ai indiqué.
 
En fait pour mieux comprendre mon problème je vais situer le contexte.
On fait un API ou l'on a des classes des doivent s'enregistrer sur le disque.
Image que l'on fasse une classe Oiseau et une classe Moineau toute deux dérivant d'un objet sauvegardable sur le disque. Lorsque l'on enregistre ces classes sur le disque, on enregistre simplement un ID qui lors du chargement permet de retrouver des methodes utilisées pour créer l'objet.  
Et au chargement on fait  
A*  p=CreateObjet( ID_chargerSurleDisque );
 
Et à un moment moment donnée on va utiliser cet objet en sachant que c'est pas un A* mais un Oiseau*. Donc on est obligé de faire un cast.
 
A cela tu rajoutes de l'héritage multiple décrit ci-dessus et tu obtient mon problème.
 
Leander

Reply

Marsh Posté le 01-03-2002 à 17:47:46    

Tetragrammaton IHVH a écrit a écrit :

Ah non en fait, c'est un autre probleme qui ne dépend pas de B ou de C : voila la doc MSDN
 
cannot convert an 'identifier1*' to an 'identifier2*'; conversion from a virtual base class is implied
 
The conversion requires a cast from a virtual base class to a derived class, which is not allowed. The followings sample generates C2635:
 

Code :
  1. // C2635.cpp
  2. class B
  3. {
  4. };
  5. class D : virtual public B   // C2635 remove virtual to resolve the error
  6. {
  7. };
  8. int main()
  9. {
  10.     B b;
  11.     D d;
  12.     D * pD = &d;
  13.     pD = (D*)&b;   // C2635
  14. }


 
C'est marrant parce que j'utilise de temps en temps le multi heritage virtuel mais je n'ai pas rencontré ce problème. Mais faut dire que j'évite toujours le sous-casting car c'est mal.  




 
 
voila, c'est exatement ça !!! C'est pas un problème d'héritage multiple (quoique c'est à cause de ça que je met le mot virtual qui me pose les problèmes.)
Le problème vient du Cast.
Et je ne vois pas comment je peux supprimer le sous-casting comme tu dis.
 
A partir du moment ou je veux charger une classe du disque, et que je sais juste que cette classe est de type A*, y a de grande chance que plus tard je sois obligé de faire du sous-casting pour retrouver mon objet original.

Reply

Marsh Posté le 01-03-2002 à 18:40:01    

C'est bien ce qui me semblait, il n'y a pas de problème avec les RTTI :
 
Je viens de tester ça :
 

Code :
  1. class
  2. {
  3. public:
  4. A() {};
  5. virtual ~A() {};
  6. virtual void coucou() = 0;
  7. };
  8. class B : public virtual
  9. {
  10. public:
  11. B() {};
  12. virtual ~B() {};
  13. virtual void coucou() { printf("Ahhhh !\n" ); };
  14. };
  15. int main(int argc, char* argv[])
  16. {
  17. printf("Hello World!\n" );
  18. A* virtuA = new B();
  19. virtuA->coucou();
  20. // maintenant on sous-caste
  21. B* b = dynamic_cast<B*>(virtuA);  // RTTI
  22. // B* b = (B*) virtuA;   // provoque une erreur C2635
  23. b->coucou();
  24. return 0;
  25. }


 
Le sous-casting à la moyenageuse (B*) provoque une erreur de compil C2635 alors que le dynamic_cast<B*> tourne nickel.
 
La méthode coucou() est dans les 2 cas, correctement appelée (que ce soit avec le ptr de type A* ou B*).
 
Conclusion : BANNIR LES CAST "A LA MOYENAGEUSE"

 

[jfdsdjhfuetppo]--Message édité par Tetragrammaton IHVH--[/jfdsdjhfuetppo]


---------------
"Dieu a exploité tous nos complexes d'infériorité, en commençant par notre incapacité de croire à notre propre divinité." - Emil Michel Cioran
Reply

Marsh Posté le 01-03-2002 à 19:29:57    

oui mais ca impose de compiler avec les RTTI.
 
void* p = new D();
D* d = (D*)p;
 
ca ne marche pas ca?
 
A+
LEGREG

Reply

Marsh Posté le 01-03-2002 à 19:33:20    

legreg a écrit a écrit :

oui mais ca impose de compiler avec les RTTI.
 
void* p = new D();
D* d = (D*)p;
 
ca ne marche pas ca?
 




 
VADE RETRO !!!!!!!!!!!!!!!
 
C'est clair que si c'est pour faire des réponses comme ça legreg, tu peux économiser un post    :sarcastic:

 

[jfdsdjhfuetppo]--Message édité par Tetragrammaton IHVH--[/jfdsdjhfuetppo]


---------------
"Dieu a exploité tous nos complexes d'infériorité, en commençant par notre incapacité de croire à notre propre divinité." - Emil Michel Cioran
Reply

Marsh Posté le 01-03-2002 à 19:36:15    

SATANAS !!!!!!!!!

Reply

Marsh Posté le 02-03-2002 à 12:42:39    

d'accord merci. La réponse à ma question est donc de compiler en RTTI.
Es-ce que vous savez si augmente la place mémoire des instances des classes ou si c'est juste des infos rajouter dans le code ?
 
Parce que j'ai vraiment besoin de surveiller la taille mémoire de mes programmes qui ont des restrictions assez importantes.
 
je vais faire quelques tests. Merci encore Tetragrammaton IHVH
 
Leander

Reply

Marsh Posté le 02-03-2002 à 12:42:39   

Reply

Marsh Posté le 02-03-2002 à 12:48:48    

Oui ça rajoute des infos.
Mais si t'es vraiment contraint en taille, vaut mieux pas utiliser le c++, ou alors ne pas utiliser toutes les fonctionnalités du langage (c'est souvent ce qui est fait dans l'embarqué).

Reply

Marsh Posté le 02-03-2002 à 12:52:50    

ah ouai, je viens de faire des tests, et apparement le new B, alloue 12 octets !!!!
 
Contre 4 octets (pour la vftable) si on enlève l'héritage virtuel.
 
ça fait donc 8 octets de plus pour les RTTI... Va falloir que je pèse le pour et le contre.

Reply

Marsh Posté le 02-03-2002 à 14:00:04    

Tetragrammaton IHVH a écrit a écrit :

 
C'est clair que si c'est pour faire des réponses comme ça legreg, tu peux économiser un post    :sarcastic:  




1- on n'est pas en java
2- si tu as demande explicitement la creation
d'un objet de type D c'est tout a fait safe
de faire le cast (D*).
(En C, malloc renvoie un pointeur (void*))
3- personne n'oblige personne a faire
ce qu'il ne veut pas faire, c'est le principe du C++.
Je n'ai pas a payer pour une verification de type
que je sais par avance inutile.
 
Le principe sous-jacent c'est de savoir si tu veux
faire le controle de type a la compilation
(impossible avec de l'heritage virtuel je suis bien  
d'accord) ou a l'execution ce qui peut poser d'autres
problemes, pas seulement de performance.
 
A+
LEGREG

Reply

Marsh Posté le 02-03-2002 à 14:44:38    

legreg a écrit a écrit :

 
1- on n'est pas en java
2- si tu as demande explicitement la creation
d'un objet de type D c'est tout a fait safe
de faire le cast (D*).
(En C, malloc renvoie un pointeur (void*))
3- personne n'oblige personne a faire
ce qu'il ne veut pas faire, c'est le principe du C++.
Je n'ai pas a payer pour une verification de type
que je sais par avance inutile.
 
Le principe sous-jacent c'est de savoir si tu veux
faire le controle de type a la compilation
(impossible avec de l'heritage virtuel je suis bien  
d'accord) ou a l'execution ce qui peut poser d'autres
problemes, pas seulement de performance.
 
A+
LEGREG  




 
ça pose quelques problèmes de faire des tests avec l'heritage virtuel au Run-Time ?
 
Parce que je n'ai jamais lu de truc la dessus. Vous connaissez de bonne source d'infos (livre ou site web) sur l'héritage virtuel ?

Reply

Marsh Posté le 02-03-2002 à 14:58:13    

le dynamic_cast ne resout pas toutes les
ambiguites (en cas d'heritage multiple).
 
et ca ne dispense pas a l'avance
de prevoir les cas ou il peut
planter pour cause de cast invalide.
(en cas de plantage il renvoie un pointeur NULL
ou il leve une exception bad_cast si  
c'est un cast de reference)
 
A+
LEGREG

Reply

Marsh Posté le 02-03-2002 à 14:59:57    

ouai ok...
tu aurais un exemples où il n'arrive pas à résoudre les problèmes ?
que je vous ce qui peut poser problème ?

Reply

Marsh Posté le 02-03-2002 à 16:28:25    

legreg> Sauf que ta méthode plante lamentablement.
 
Si tu as une classe B qui dérive d'une classe A, tu ne peux pas passer par void* pour acceder à une méthode virtuelle :
 
B* b = new B;
void* ptr = (void*) b;
A* a = (A*) ptr;
a->MethodeVirtuelleB(); // va provoquer un superbe plantage :lol:
 
Donc, tu tacheras de nous épargner ton humour sur les void*  :sarcastic:  
 
Et concernant tes points 2 & 3 : personne n'est à l'abri d'un bug. Si on a inventé le typage, c'est bien pour la sécurité sinon, autant retourner à l'ASM, là c'est rapide  :sarcastic:


---------------
"Dieu a exploité tous nos complexes d'infériorité, en commençant par notre incapacité de croire à notre propre divinité." - Emil Michel Cioran
Reply

Marsh Posté le 02-03-2002 à 21:43:08    

Tetragrammaton IHVH a écrit a écrit :

 
Si tu as une classe B qui dérive d'une classe A, tu ne peux pas passer par void* pour acceder à une méthode virtuelle :
B* b = new B;
void* ptr = (void*) b;
A* a = (A*) ptr;
a->MethodeVirtuelleB(); // va provoquer un superbe plantage :lol:




 
je ne suis pas stupide, je sais bien que TON exemple plante..
bof de toute facon je sens bien que cette discussion
ne nous amenera a rien alors a quoi bon..
 
A+
LEGREG

Reply

Marsh Posté le 02-03-2002 à 23:39:08    

leander a écrit a écrit :

ah ouai, je viens de faire des tests, et apparement le new B, alloue 12 octets !!!!
Contre 4 octets (pour la vftable) si on enlève l'héritage virtuel.




 
12 octets me parait beaucoup
je croyais que le RTTI rajoutait
simplement une entree "information de type"
dans la vtable, et que donc le surcout
par instance etait nul par rapport a une  
vtable "classique". (mais je n'ai jamais ecrit
de compilateur)  
 
Bon voila un exemple de probleme qui m'est
venu a l'esprit:
Imaginons que tu veuilles utiliser ton interface
pour faire du stockage de données heterogenes
exemple: un tableau de pointeurs de A
qui va recueillir a la fois des pointeurs d'objets B, C, D ou E.
la hierarchie est la suivante:

Code :
  1. A
  2.   / \
  3. B   B
  4. |   |
  5. C   D
  6.   \ /
  7.    E


 
Seul probleme, lorsque tu voudras utiliser
une methode de B, tu vas essayer de faire un dynamic_cast
de ton pointeur a vers B:
B* b = dynamic_cast<B*> a;  
or tu peux t'attendre a ce que si a pointe vers un objet
de type E, ce cast renvoie un pointeur nul parce que le cast est ambigu.
 
Donc pour y arriver il faudrait que tu passes par un intermediaire:
E* e = dynamic_cast<E*> a;
// suivant ce que tu veux faire, tu pourras passer par D  
C* c = dynamic_cast<C*> e;  
B* b = dynamic_cast<B*> c;
et la ca marche.
 
Tu vas me dire: "oui mais c'est une erreur
de design". Peut-etre. Mais la personne qui
va ecrire du code pour ta librairie n'est  
pas sense savoir les details de ton implantation,
de meme que toi tu n'es pas sense savoir qu'il
y aura un objet E quand tu vas ecrire du code
pour ton conteneur de A*.
 
Perso je ne sais pas trop s'il y a des solutions elegantes
a de tels problemes; je me restreins a des designs
simples mais c'est vrai que quand on a envie de faire
du Java en C++, c'est la que les problemes arrivent.
(mais en java on esquive le pb puisqu'il n'y a pas
d'heritage multiple)
 
A+
LEGREG

 

[jfdsdjhfuetppo]--Message édité par legreg--[/jfdsdjhfuetppo]

Reply

Marsh Posté le 03-03-2002 à 01:13:00    

C'est vrai que la solution c'est de ne pas faire d'héritage multiple. C'est la conclusion a laquelle on était arrivé vendredi soir aussi.
 
D'ailleurs dans Design Pattern ils déconseillent fortement l'héritage multiple et préconise plutot la composition.
Enfin ça dépend des cas d'utilisations bien sur....

Reply

Sujets relatifs:

Leave a Replay

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