OVH Cloud OVH Cloud

dynamic_cast < > et héritage multiple

17 réponses
Avatar
Matthieu Moy
Bonjour,

J'ai une arborescence de classe qui ressemble à ça (Pour ceux qui
connaissent, c'est un signal SystemC) :

If
^
|
A<T> B
^ ^
| |
`--.--'
|
C<T>

(If est une interface)

J'ai un pointeur ptr sur un objet de type B, mais le type du pointeur
est If *. Je veux donc faire un cast.

Si C n'était pas un template, j'aurais bien casté successivement en C
puis en B, mais là, j'ai un template, dont je ne connais pas le type à
ce point du programme.

Si je fais simplement "(B *)ptr", alors "boom", il y a des décallages
d'adresses qui se passent mal, et je récupère un pointeur sur rien du
tout.

Si je fais "dynamic_cast<B *>(ptr)", ça a l'air de bien se passer (GCC
3.2).

Je dis « ça a l'air », mais justement, ce que je voudrais savoir,
c'est si il y a une bonne raison pour que ça se passe bien, et quels
risques je prends à faire ce genre de manips. (En général, ce qui
tourne autour de l'héritage multiple me fait un peu peur !)

Et accessoirement, y a-t-il une autre solution (Changer la hierarchie
de classe n'est pas une option, ce n'est pas du code à moi.) ?

--
Matthieu

10 réponses

1 2
Avatar
Matthieu Moy
Yannick Le goc writes:
Matthieu Moy wrote:
Bonjour,
J'ai une arborescence de classe qui ressemble à ça (Pour ceux
qui connaissent, c'est un signal SystemC) :
If
^
|
A<T> B
^ ^
| |
`--.--'
|
C<T>
(If est une interface)



[...]

Je ne comprends pas bien pourquoi tu as vraiment un probleme, car
comment se fait-il que tu ne puisse pas convertir successivement a
l'aide de static_cast vers A<T>, car ce parametre template, tu dois
bien l'avoir?


Non, je ne l'ai pas.

Les structures de données sont construites dans un morceau de code que
je ne maitrise pas. Là où j'interviens, j'ai juste un "If *", et c'est
tout. Le paramètre du template est inconnu, peut varier selon les
execution, et peut être différent à chaque fois en repassant plusieurs
fois sur la même ligne de code :-(.

Et non, mettre des dynamic_cast<> de partout ne m'enchante pas
particulièrement, c'est bien pour ça que je pose la question.

C'est triste, hein ! ;-)

--
Matthieu


Avatar
Yannick Le goc
Matthieu Moy wrote:
Bonjour,

J'ai une arborescence de classe qui ressemble à ça (Pour ceux qui
connaissent, c'est un signal SystemC) :

If
^
|
A<T> B
^ ^
| |
`--.--'
|
C<T>

(If est une interface)

J'ai un pointeur ptr sur un objet de type B, mais le type du pointeur
est If *. Je veux donc faire un cast.

Si C n'était pas un template, j'aurais bien casté successivement en C
puis en B, mais là, j'ai un template, dont je ne connais pas le type à
ce point du programme.

Si je fais simplement "(B *)ptr", alors "boom", il y a des décallages
d'adresses qui se passent mal, et je récupère un pointeur sur rien du
tout.

Si je fais "dynamic_cast<B *>(ptr)", ça a l'air de bien se passer (GCC
3.2).

Je dis « ça a l'air », mais justement, ce que je voudrais savoir,
c'est si il y a une bonne raison pour que ça se passe bien, et quels
risques je prends à faire ce genre de manips. (En général, ce qui
tourne autour de l'héritage multiple me fait un peu peur !)

Et accessoirement, y a-t-il une autre solution (Changer la hierarchie
de classe n'est pas une option, ce n'est pas du code à moi.) ?




Dynamic_cast est une fonction complexe qui se base sur les tables
virtuelles pour retrouver ses petits.
Ton probleme est la conversion statique de pointeurs, (conversion C (B
*)ptr a ne jamais faire en C++), en utilisant dynamic_cast tu resous a
l'execution cette conversion, qui est beaucoup plus couteuse en temps.

Je ne comprends pas bien pourquoi tu as vraiment un probleme, car
comment se fait-il que tu ne puisse pas convertir successivement a
l'aide de static_cast vers A<T>, car ce parametre template, tu dois bien
l'avoir?

Yannick

Avatar
Alexandre
"Matthieu Moy" a écrit dans le message
de news:
Bonjour,

J'ai une arborescence de classe qui ressemble à ça (Pour ceux qui
connaissent, c'est un signal SystemC) :

If
^
|
A<T> B
^ ^
| |
`--.--'
|
C<T>

(If est une interface)

J'ai un pointeur ptr sur un objet de type B, mais le type du pointeur
est If *. Je veux donc faire un cast.


Il y a un problème : B n'a aucun rapport avec If. Tu pourrais trantyper en A
ou en C, mais en B ça n'a aucun sens ! Rien n'impose les propriétés et
fonctions de B d'être identiques à celles de If... Donc problème de
conception au départ. Un If* NE PEUT pointer sur un B.



Si C n'était pas un template, j'aurais bien casté successivement en C
puis en B, mais là, j'ai un template, dont je ne connais pas le type à
ce point du programme.

Si je fais simplement "(B *)ptr", alors "boom", il y a des décallages
d'adresses qui se passent mal, et je récupère un pointeur sur rien du
tout.

Si je fais "dynamic_cast<B *>(ptr)", ça a l'air de bien se passer (GCC
3.2).

Je dis « ça a l'air », mais justement, ce que je voudrais savoir,
c'est si il y a une bonne raison pour que ça se passe bien, et quels
risques je prends à faire ce genre de manips. (En général, ce qui
tourne autour de l'héritage multiple me fait un peu peur !)

Et accessoirement, y a-t-il une autre solution (Changer la hierarchie
de classe n'est pas une option, ce n'est pas du code à moi.) ?

--
Matthieu


Avatar
christian.templier
"Matthieu Moy" a écrit dans le message
de news:

Bonjour,

J'ai une arborescence de classe qui ressemble à ça (Pour ceux qui
connaissent, c'est un signal SystemC) :

If
^
|
A<T> B
^ ^
| |
`--.--'
|
C<T>

(If est une interface)

J'ai un pointeur ptr sur un objet de type B, mais le type du pointeur
est If *. Je veux donc faire un cast.



Il y a un problème : B n'a aucun rapport avec If. Tu pourrais trantyper en A
ou en C, mais en B ça n'a aucun sens ! Rien n'impose les propriétés et
fonctions de B d'être identiques à celles de If... Donc problème de
conception au départ. Un If* NE PEUT pointer sur un B.


c est tout a fait vrai alexandre , matthieu construire un type x qui
herite de if et de B a t il un sens ?

si oui
If(virtual) ?
^
|
A<T> B(virtual) ?
^ ^
| |
`--.--X
|
C<T>



ou

If(virtual)
^
|
A<T> B(virtual)
^ ^
| |
`--.--'
|
C<T> X(virtual)
| |


| |
-----
|
K<T>



a+ christian





Si C n'était pas un template, j'aurais bien casté successivement en C
puis en B, mais là, j'ai un template, dont je ne connais pas le type à
ce point du programme.

Si je fais simplement "(B *)ptr", alors "boom", il y a des décallages
d'adresses qui se passent mal, et je récupère un pointeur sur rien du
tout.

Si je fais "dynamic_cast<B *>(ptr)", ça a l'air de bien se passer (GCC
3.2).

Je dis « ça a l'air », mais justement, ce que je voudrais savoir,
c'est si il y a une bonne raison pour que ça se passe bien, et quels
risques je prends à faire ce genre de manips. (En général, ce qui
tourne autour de l'héritage multiple me fait un peu peur !)

Et accessoirement, y a-t-il une autre solution (Changer la hierarchie
de classe n'est pas une option, ce n'est pas du code à moi.) ?

--
Matthieu







Avatar
Loïc Joly
Alexandre wrote:
"Matthieu Moy" a écrit dans le message
de news:

Bonjour,

J'ai une arborescence de classe qui ressemble à ça (Pour ceux qui
connaissent, c'est un signal SystemC) :

If
^
|
A<T> B
^ ^
| |
`--.--'
|
C<T>

(If est une interface)

J'ai un pointeur ptr sur un objet de type B, mais le type du pointeur
est If *. Je veux donc faire un cast.



Il y a un problème : B n'a aucun rapport avec If. Tu pourrais trantyper en A
ou en C, mais en B ça n'a aucun sens ! Rien n'impose les propriétés et
fonctions de B d'être identiques à celles de If... Donc problème de
conception au départ. Un If* NE PEUT pointer sur un B.


Sauf s'il pointe sur un C<T>. C'est un problème de vocabulaire utilisé
dans la damande. On a un pointeur sur If qui pointe en réalité sur un
objet du type C<T>, et on veut retrouver la partie B de cet objet.

Par contre, a moins d'être certain que le pointeur sur If pointe en fait
sur un C<T> (ce qui permettrait de faire un static_cast en C<T> puis un
autre en B (et qui impose donc de connaître T)), un dynamic_cast s'impose.

Voir par exemple www.research.att.com/~bs/performanceTR.pdf pour une
étude plus détaillée des coûts de dynamic_cast.


Et pour finir, un petit extrait de la norme sur dynamic_cast <T> (v),
dans le cas où une vérification au run-time est nécessaire :

The runtime check logically executes as follows:
— If, in the most derived object pointed (referred) to by v, v points
(refers) to a public base class subobject of a T object, and if only one
object of type T is derived from the subobject pointed (referred) to
by v, the result is a pointer (an lvalue referring) to that T object.
— Otherwise, if v points (refers) to a public base class subobject
of the most derived object, and the type of the most derived object has
an unambiguous public base class of type T, the result is a pointer
(an lvalue referring) to the T subobject of the most derived object.
— Otherwise, the runtime check fails.

--
Loïc


Avatar
Falk Tannhäuser
Matthieu Moy wrote:

Yannick Le goc writes:
Matthieu Moy wrote:
Bonjour,
J'ai une arborescence de classe qui ressemble à ça (Pour ceux
qui connaissent, c'est un signal SystemC) :
If
^
|
A<T> B
^ ^
| |
`--.--'
|
C<T>
(If est une interface)
[...]



Les structures de données sont construites dans un morceau de code que
je ne maitrise pas. Là où j'interviens, j'ai juste un "If *", et c'est
tout. Le paramètre du template est inconnu, peut varier selon les
execution, et peut être différent à chaque fois en repassant plusieurs
fois sur la même ligne de code :-(.

Et non, mettre des dynamic_cast<> de partout ne m'enchante pas
particulièrement, c'est bien pour ça que je pose la question.

C'est triste, hein ! ;-)
'dynamic_cast' est pourtant bien fait pour des cas comme ça et il

fonctionnera correctement à condition que les classes impliquées
comportent des fonctions virtuelles (sinon ça ne compilera pas).
On distingue des "down cast" (classe de base vers classe dérivée)
et des "cross cast" (classe X vers classe Y sans qu'il y ait de
relation d'héritage entre les 2, ceci est ton cas). L'ajustement
d'adresse doit bien se faire à l'exécution, étant donné que pendant la
compilation, les tailles (et par conséquent les offsets) des sous-objets
'A<T>' et 'B' à l'intérieur d'un objet 'C<T>' ne sont pas connues
statiquement.
Qu'est-ce que tu as contre le dynamic_cast ? C'est les perfs qui
t'embêtent ?

Falk



Avatar
kanze
Matthieu Moy wrote in message
news:...

J'ai une arborescence de classe qui ressemble à ça (Pour ceux qui
connaissent, c'est un signal SystemC) :

If
^
|
A<T> B
^ ^
| |
`--.--'
|
C<T>

(If est une interface)

J'ai un pointeur ptr sur un objet de type B, mais le type du pointeur
est If *. Je veux donc faire un cast.

Si C n'était pas un template, j'aurais bien casté successivement en C
puis en B, mais là, j'ai un template, dont je ne connais pas le type à
ce point du programme.


Le problème n'est pas le template. Le problème, c'est que tu ne connais
pas le type de la classe la plus dérivée. Template ou non n'y change
rien.

Si je fais simplement "(B *)ptr", alors "boom", il y a des décallages
d'adresses qui se passent mal, et je récupère un pointeur sur rien du
tout.


En effet. Dans ce cas-ci, une conversions à la C équivaut une
reinterpret_cast. C-à-d que tu dis au compilateur qu'il faut qu'il
« réinterprète » le If* que tu as comme s'il était un B*. Ce qu'il n'est
pas.

Si je fais "dynamic_cast<B *>(ptr)", ça a l'air de bien se passer (GCC
3.2).


C'est normal. Avec dynamic_cast, ou bien le résultat pointe réelement à
un objet de type B, ou bien le résultat est null. Et la norme dit que le
dynamic_cast va chercher le type cible en partant du type le plus dérivé
de l'objet.

Je dis « ça a l'air », mais justement, ce que je voudrais savoir,
c'est si il y a une bonne raison pour que ça se passe bien, et quels
risques je prends à faire ce genre de manips. (En général, ce qui
tourne autour de l'héritage multiple me fait un peu peur !)


La raison pourquoi ça se passe bien, c'est qu'une dynamic_cast, c'est
l'équivalent moral d'une conversion au type le plus dérivé, suivi d'une
conversion au type cible. Et que c'est vérifié -- si la converion du
type le plus dérivé au type cible n'est pas légal (quelque soit la
raison), ta dynamic_cast renvoie null. (Tout ça, évidemment, à condition
que ta classe du départ contient au moins une fonction virtuelle.)

Et accessoirement, y a-t-il une autre solution (Changer la hierarchie
de classe n'est pas une option, ce n'est pas du code à moi.) ?


Ne connaissant pas l'application, c'est difficile à dire. Idéalement, si
tu as besoin des B, et que celui qui crée les objets le sait, et ne crée
que les dérivées de B, on garderait des B* quelque part, et non des If*.
Quand on conçoit un système du début, se rétrouver dans des cas comme
ceci est en général un signe d'une mauvaise conception. (Mais il y a
d'autres cas où le dynamic_cast se justifie, même dès le début. Et en
passant, comment dit-on un « green fields project » en français ?) En
revanche, quand on part d'un existant, on est souvent amené à des
compromis. Si changer l'hièrarchie des classes n'est pas une option, et
que tu n'as aucun moyen d'obtenir des pointeurs autre qu'à des If, je ne
vois pas d'autre solution que dynamic_cast.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16

Avatar
Alexandre
Il y a un problème : B n'a aucun rapport avec If. Tu pourrais trantyper
en A


ou en C, mais en B ça n'a aucun sens ! Rien n'impose les propriétés et
fonctions de B d'être identiques à celles de If... Donc problème de
conception au départ. Un If* NE PEUT pointer sur un B.


Sauf s'il pointe sur un C<T>. C'est un problème de vocabulaire utilisé
dans la damande. On a un pointeur sur If qui pointe en réalité sur un
objet du type C<T>, et on veut retrouver la partie B de cet objet.


Mais ce n'est pas du tout logique : le pointeur sur If devrait être utilisé
pour les fonctionnalités définies dans If, à savoir l'interface. S'il pointe
sur un C<T> il est effectivement indispensable de connaitre T pour retrouver
les membres de B.
Je pense qu'il y a un pb de conception au départ... Si on a un pointeur de
If, c'est qu'on veut utiliser la partie interface des classes dérivées, pas
des classes n'ayant pas de rapport avec If.
C'est souvent dû à une erreur de conception qu'on utilise un cast.


Par contre, a moins d'être certain que le pointeur sur If pointe en fait
sur un C<T> (ce qui permettrait de faire un static_cast en C<T> puis un
autre en B (et qui impose donc de connaître T)), un dynamic_cast s'impose.

Voir par exemple www.research.att.com/~bs/performanceTR.pdf pour une
étude plus détaillée des coûts de dynamic_cast.


Et pour finir, un petit extrait de la norme sur dynamic_cast <T> (v),
dans le cas où une vérification au run-time est nécessaire :

The runtime check logically executes as follows:
— If, in the most derived object pointed (referred) to by v, v points
(refers) to a public base class subobject of a T object, and if only one
object of type T is derived from the subobject pointed (referred) to
by v, the result is a pointer (an lvalue referring) to that T object.
— Otherwise, if v points (refers) to a public base class subobject
of the most derived object, and the type of the most derived object has
an unambiguous public base class of type T, the result is a pointer
(an lvalue referring) to the T subobject of the most derived object.
— Otherwise, the runtime check fails.

--
Loïc




Avatar
Gourgouilloult

Sauf s'il pointe sur un C<T>. C'est un problème de vocabulaire utilisé
dans la damande. On a un pointeur sur If qui pointe en réalité sur un
objet du type C<T>, et on veut retrouver la partie B de cet objet.


Mais ce n'est pas du tout logique : le pointeur sur If devrait être utilisé
pour les fonctionnalités définies dans If, à savoir l'interface. S'il pointe
sur un C<T> il est effectivement indispensable de connaitre T pour retrouver
les membres de B.


Non, c'est pas nécessaire de connaître T à la compil, ni même qu'il
s'agit d'un C<quelque_chose>. Le fait est qu'on balade un objet dans le
code, en le tenant par sa «poignée If*». Mais à l'exécution, l'objet
pointé sait de quel type effectif il est. Avec un dynamic_cast<B*>,
c'est l'objet qui va vérifier lui même s'il a une «poignée B*». Et s'il
en trouve une, ben il la refile gentiment.

(C'est simpliste comme explication, et je suis désolé si je donne
l'impression de m'adresser à un neuneu ; mon intention n'est pas de
vexer, mais d'éviter de m'emmêler les pinceaux ;)

Je pense qu'il y a un pb de conception au départ... Si on a un pointeur de
If, c'est qu'on veut utiliser la partie interface des classes dérivées, pas
des classes n'ayant pas de rapport avec If.


Hmm.. là, ce que tu remets en question, c'est l'utilisation de
l'héritage multiple. C'est vrai que c'est rarement d'une nécessité
absolue, mais le PO a précisé, si je me souviens bien, qu'il n'a pas la
maîtrise de la hiérarchie... oui, c'est ça.

Une utilité des héritages multiples, mise en avant par Stroustrup, c'est
de pouvoir fusionner deux hiérarchies : d'un côté les classes
d'interfaces, de l'autre celles d'implémentation, par exemple.

Le programme «client» ne manipule que des pointeurs vers des objets de
types appertenants à la hiérarchie des interfaces (par exemple pour être
capable de wrapper plusieurs systèmes de façon transparente). Mais dès
lors qu'on va échanger des objets entre le système et le programme, il
faudra pouvoir convertir d'un côté vers l'autre. D'où l'intérêt des
cross-casts.

Je vais pas reproduire tout l'exemple ici, c'est dans TC++PL, §15.4. Si
t'as la possibilité d'aller voir ça, je pense que tu seras convaincu. (A
part ça, j'ai le sentiment d'avoir déjà raconté ça y a pas longtemps...
Je radote, ou je perds la boule ?-)

C'est souvent dû à une erreur de conception qu'on utilise un cast.


Là, à voir la façon dont tu généralises, j'ai le sentiment que tu parles
de C-style cast. En C++, elles sont au nombre de 4, les «directives de
conversion» (traduc au pif, hein). La forme à la C -la cinquième- étant
la forme à éviter absolument. En l'occurence, dynamic_cast<> est ce
qu'il semble falloir au PO.

Le fait est qu'il est effectivement souvent douteux, dans un langage à
typage fort, de jouer avec les types des objets. Les avantages des casts
à la C++, c'est qu'ils sont nettement plus visibles, et que quand on en
voit un, il est plus facile de comprendre pourquoi le programmeur a
voulu un cast là ou pas. Après, savoir si c'est bien judicieux en termes
de conception... ça peut toujours se discuter pendant des heures ;)

Gourgou
J'ai la désagréable impression que plus je me débats, et plus j'épaissis
la soupe... rassurez-moi, j'explique pas aussi mal que ça, quand même ?
(Si tant est que je dise pas trop de bêtises, déjà.)

...
Et si j'vous embête avec tous mes diclaimers, vous le dites ;)


Avatar
Loïc Joly
Gourgouilloult wrote:
Et si j'vous embête avec tous mes diclaimers, vous le dites ;)


Un disclaimer auto référentiel... C'est original !

--
Loïc

1 2