OVH Cloud OVH Cloud

Optimisation de "dynamic_cast"

8 réponses
Avatar
Vincent Richard
Bonjour,

Soit le système de classes :

class A { };
class B : virtual public A { public: void f(); };
class C : virtual public A { public: void g(); };
class D : public B, public C { };

Sachant que tous les objets contenus dans le vecteur sont de même type
dynamique (par exemple 'D'), est-il valide d'écrire :

std::vector <A*> v;

// ...

// Un "dynamic_cast" une fois pour toutes...
const int offset = static_cast <int>(dynamic_cast <C*>(v[0]) - v[0]);

for (std::vector <A*>::iterator it = v.begin() ; it != v.end() ; ++it)
{
static_cast <C*>(*it + offset)->f();
}

Ceci afin d'éviter la multitude de "dynamic_cast" (lent) avec des vecteurs
qui contiennent un grand nombre d'éléments (par exemple 1000).

Merci d'avance.

Vincent

--
vmime, une bibliothèque C++ sous licence GPL pour parser et générer
des messages au format MIME : http://www.sourceforge.net/projects/vmime/

8 réponses

Avatar
Frédéric Lachasse
"Vincent Richard" wrote in
message news:4024f0a5$0$28145$
Bonjour,

Soit le système de classes :

class A { };
class B : virtual public A { public: void f(); };
class C : virtual public A { public: void g(); };
class D : public B, public C { };

Sachant que tous les objets contenus dans le vecteur sont de même type
dynamique (par exemple 'D'), est-il valide d'écrire :

std::vector <A*> v;

// ...

// Un "dynamic_cast" une fois pour toutes...
const int offset = static_cast <int>(dynamic_cast <C*>(v[0]) - v[0]);

for (std::vector <A*>::iterator it = v.begin() ; it != v.end() ; ++it)
{
static_cast <C*>(*it + offset)->f();
}

Ceci afin d'éviter la multitude de "dynamic_cast" (lent) avec des vecteurs
qui contiennent un grand nombre d'éléments (par exemple 1000).


A mon avis, les compilateurs vont se plaindre.

Ce que tu veux faire, c'est:

const int offset = reinterpret_cast<char*>(dynamic_cast<C*>(v[0])) -
reinterpret_cast<char*>(v[0]);

for (std::vector<A*>::iterator it = v.begin(); it != v.end(); ++it)
{
reinterpret_cast<C*>(reinterpret_cast<char*>(*it) + offset)->f();
}

L'utilisation de reinterpret_cast<> montre que la méthode n'est pas
complètement portable, mais cela marchera probablement sur toutes les
platformes courantes.

Une méthode pour optimiser ponctuellement un dynamic_cast<>: ajouter une
fonction virtuelle dans A qui retourne un C*. L'implémentation pour A
retourne NULL et dans C retourne this.

--
Frédéric Lachasse - ECP86

Avatar
Vincent Richard

Une méthode pour optimiser ponctuellement un dynamic_cast<>: ajouter une
fonction virtuelle dans A qui retourne un C*. L'implémentation pour A
retourne NULL et dans C retourne this.


Le problème c'est que ma classe 'A' n'a pas (et ne doit pas avoir)
"connaissance" des classes dérivées (il peut y en avoir plusieurs)...

Bon, de toutes façons, je pense que je vais laisser les "dynamic_cast",
ça n'a pas l'air si long que ça de toutes façons (et du moment que ça
fonctionne, c'est le principal).

Vincent

--
vmime, une bibliothèque C++ sous licence GPL pour parser et générer
des messages au format MIME : http://www.sourceforge.net/projects/vmime/

Avatar
Benoit Dejean
Le Sat, 07 Feb 2004 15:06:37 +0100, Vincent Richard a écrit :

Bonjour,

Soit le système de classes :

class A { };
class B : virtual public A { public: void f(); };
class C : virtual public A { public: void g(); };
class D : public B, public C { };

Sachant que tous les objets contenus dans le vecteur sont de même type
dynamique (par exemple 'D'), est-il valide d'écrire :


qu'est-ce qui t'empêche d'exploiter cela, le fait que tous tes éléments
soit de même type ?


std::vector <A*> v;

// ...

// Un "dynamic_cast" une fois pour toutes...
const int offset = static_cast <int>(dynamic_cast <C*>(v[0]) - v[0]);

for (std::vector <A*>::iterator it = v.begin() ; it != v.end() ; ++it)
{
static_cast <C*>(*it + offset)->f();
}

Ceci afin d'éviter la multitude de "dynamic_cast" (lent) avec des vecteurs
qui contiennent un grand nombre d'éléments (par exemple 1000).


comment ça lent ?

Avatar
Vincent Richard


class A { };
class B : virtual public A { public: void f(); };
class C : virtual public A { public: void g(); };
class D : public B, public C { };

Sachant que tous les objets contenus dans le vecteur sont de même type
dynamique (par exemple 'D'), est-il valide d'écrire :


qu'est-ce qui t'empêche d'exploiter cela, le fait que tous tes éléments
soit de même type ?


Justement, c'est bien ce que je veux faire ! Le vecteur contient des
élements de type A* (non changeable), et je suis sûr que ce sont des
objets de type C.

La classe A est celle qui est exposée à l'utilisateur (classe abstraite)
pour qu'il puisse manipuler des objets concrets de manière transparente.

Ceci afin d'éviter la multitude de "dynamic_cast" (lent) avec des
vecteurs qui contiennent un grand nombre d'éléments (par exemple 1000).


comment ça lent ?


Dans le cas de l'héritage multiple, un "dynamic_cast" est logiquement
beaucoup plus coûteux qu'un simple "static_cast". Par contre, comme je
l'ai dis dans ma précédente réponse, j'ai l'impression que ça ne vaut
pas la peine de s'embêter pour si peu...

Vincent

--
vmime, une bibliothèque C++ sous licence GPL pour parser et générer
des messages au format MIME : http://www.sourceforge.net/projects/vmime/


Avatar
Fabien LE LEZ
On Sat, 07 Feb 2004 21:37:33 +0100, Vincent Richard
wrote:

logiquement beaucoup plus coûteux


Honnêtement, ce genre d'intuitions s'avère rarement. Pour savoir si
une méthode est lente ou rapide, il faut l'implémenter et la
chronométrer.

--
;-)

Avatar
David Geldreich
Bonjour a tous,

Vincent Richard wrote:
Justement, c'est bien ce que je veux faire ! Le vecteur contient des
élements de type A* (non changeable), et je suis sûr que ce sont des
objets de type C.


si tu es sûr que tout est du C*, un static_cast<C*> suffit. Par contre,
pour vérifier cette assertion, je rajouterais un

assert(dynamic_cast<C*>(*it) != NULL);

Ainsi, si un jour ce n'est plus vrai, tu pourras remettre en cause ton
optimisation.

David.

Avatar
Vincent Richard

Justement, c'est bien ce que je veux faire ! Le vecteur contient des
élements de type A* (non changeable), et je suis sûr que ce sont des
objets de type C.


si tu es sûr que tout est du C*, un static_cast<C*> suffit.


Même dans le cas d'héritage multiple ?
Il me semblait que ça n'était plus vrai...

Vincent

--
vmime, une bibliothèque C++ sous licence GPL pour parser et générer
des messages au format MIME : http://www.sourceforge.net/projects/vmime/


Avatar
alexis.nikichine
Vincent Richard wrote:


Justement, c'est bien ce que je veux faire ! Le vecteur contient des
élements de type A* (non changeable), et je suis sûr que ce sont des
objets de type C.


si tu es sûr que tout est du C*, un static_cast<C*> suffit.


Même dans le cas d'héritage multiple ?
Il me semblait que ça n'était plus vrai...


Il me semble qu'il y a problèmes avec l'héritage multiple. Si tu as
une base ambigue:

class A { int membre_de_a; };
class B : public A { int membre_de_b; };
class C : public A { int membre_de_c; };
class D : public B,C { int membre_de_d; }; // A est deux fois base
de D

Une disposition raisonnable de ton objet en mémoire est

position |
d'un | contenu de la mémoire
pointeur |

A* ou D* |--------
| int membre_de_a; (parent de B)
|--------
|| int membre_de_b;
A* ou C* |--------
| int membre_de_a; (parent de C)
|--------
|| int membre_de_c;
|--------
||| int membre_de_d;
|----------

Tu ne peux pas faire :

D* d; A* a = static_cast<A*>(b);

Tu ne peux en fait pas non plus faire de dynamic_cast; le problème est
de savoir vers lequel des deux A bases de D tu veux avoir un pointeur.
Donc faire

A* a = static_cast<A*>( static_cast<B*>(b) );

ou

A* a = static_cast<A*>( static_cast<C*>(b) );

pour préciser le "chemin que tu prend" dans l'arbre des bases de D
(m'enfin, le static_cast<A*> est facultatif, on peut le laisser
implicite)

Mais la on parle d'upcast (c'est déjà malheureux qu'il pose un
problème, non ?). Pour downcaster, c'est encore pire.

Si tu as un A*, tu peux le convertir en B* ou C* par un static_cast
direct. Mais il faut être sur de ton coup (ie que tu tiens bien le A*
qui coincide avec le B*, ou vers le C* (ie scientificum: que ltu es
bien sur du type dynamique de ton objet)). Après, tu pourras
static_caster le pointeur obtenu en D*. Le seul moyen safe de t'en
sortir c'est le dynamic_cast, quand A est polymorphe (ie contient une
fonction membre virtuelle).

Si tu t'es dit "résolvons ce problème de class de base ambigue par un
héritage virtuel":

class A { int membre_de_a; };
class B : virtual public A { int membre_de_b; };
class C : virtual public A { int membre_de_c; };
class D : public B,C { int membre_de_d; }; // A est une fois base
de D

et le layout raisonnable devient (en omettant des pointeurs d'héritage
virtuel renvoyant à des v-tables un peu mystiques)

D* ou B* |--------
|| int membre_de_b;
C* |--------
|| int membre_de_c;
|--------
||| int membre_de_d;
A* |----------
| int membre_de_a;
|----------

alors la phrase précédente devient: "le seul moyen de t'en sortir est
le dynamic_cast". Oui monsieur, bien que A ne soit plus ambigue, tu ne
peux plus caster, ni dans un sens, ni dans l'autre par static_cast.

La raison... pfiouh, trop compliquée, vaut mieux admettre (meis quand
on comprend, on voit une sorte de lumière :-). Voir un article de
Stroustrup sur un exemple d'implémentation de l'héritage multiple
http://www.cs.colorado.edu/~diwan/class-papers/mi.pdf), mais
schématiquement, c'est que l'objet qui construit la base virtuelle A
n'est plus ni B, ni C, mais la classe la plus dérivée de la hiérarchie
(c'est bien ca ?). Ici D. Donc quand tu as un pointeur sur un A*, que
tu veux convertir en D*, et bien ca dépend de si D est l'objet le plus
dérivé de la hiérarchie. Si en fait ton D* ne pointait pas vers un
authentique D, mais vers un E, classe dérivée de D, et bien c'est E
qui doit construire A, et A n'est pas au même endroit dans l'objet, et
ca tu n'en a a priori aucune idée lors de la compilation d'un cast de
A vers D:
Il suffit en effet de connaitre les definitions de A et D; E peut-etre
encore meme pas compilé à ce moment la.

et le layout d'un E risquera d'être:

B* ou E* ou D* |--------
|| int membre_de_b;
C* ou E* ou D* |--------
|| int membre_de_c;
|--------
||| int membre_de_d;
|----------
||||int membre_de_e;
A* |----------
| int membre_de_a;
|----------


Bon, j'arrête, je suis pas clair la. Il faut y réfléchir trois heures
puis écrire un compilateur à titre d'exercice, et je pense que tout
devient vraiment limpide.


Alexis.


PS. Bonjour Prof :-) Impressionné ?