2009-06-16 12 views
6

J'ai une hiérarchie de types - GenericClass et un certain nombre de classes dérivées, InterestingDerivedClass inclus, GenericClass est polymorphe. Il y a une interfaceExiste-t-il un moyen plus rapide de détecter le type d'objet lors de l'exécution que d'utiliser dynamic_cast?

interface ICallback { 
    virtual void DoStuff(GenericClass*) = 0; 
}; 

que j'ai besoin de mettre en œuvre. Ensuite, je veux détecter le cas lorsque le pointeur GenericClass * passé dans ICallback :: DoStuff() est vraiment un pointeur vers InterestingDerivedClass:

class CallbackImpl : public ICallback { 
    void DoStuff(GenericClass* param) { 
     if(dynamic_cast<InterestingDerivedClass*>(param) != 0) { 
      return; //nothing to do here 
     } 
     //do generic stuff 
    } 
} 

Le GenericClass et les classes dérivées sont hors de mon contrôle, je contrôle seulement le CallbackImpl .

J'ai chronométré l'instruction dynamic_cast - il faut environ 1400 cycles ce qui est acceptable pour l'instant, mais ne semble pas très rapide. J'ai essayé de lire le désassemblage de ce qui est exécuté pendant dynamic_cast dans le débogueur et vu qu'il prend beaucoup d'instructions.

Puisque je n'ai pas vraiment besoin du pointeur vers la classe dérivée, y a-t-il une manière plus rapide de détecter le type d'objet à l'exécution en utilisant seulement RTTI? Peut-être une méthode spécifique à l'implémentation qui vérifie uniquement la relation "est un" mais ne récupère pas le pointeur?

+0

Similaire à http://stackoverflow.com/questions/500493/c-equivalent-of-instanceof –

Répondre

12

Je regarde toujours l'utilisation de dynamic_cast comme une odeur de code. Vous pouvez le remplacer en toutes circonstances avec un comportement polymorphe et améliorer la qualité de votre code. Dans votre exemple, je ferais quelque chose comme ceci:

class GenericClass 
{ 
    virtual void DoStuff() 
    { 
    // do interesting stuff here 
    } 
}; 

class InterestingDerivedClass : public GenericClass 
{ 
    void DoStuff() 
    { 
    // do nothing 
    } 
}; 

class CallbackImpl : public ICallback { 
    void DoStuff(GenericClass* param) { 
     param->DoStuff(); 
    } 
} 

Dans votre cas, vous ne pouvez pas modifier les classes cibles, vous programmez un contrat implicite par la déclaration du type GenericClass. Par conséquent, il est peu probable que tout ce que vous pouvez faire soit plus rapide que dynamic_cast, car toute autre chose nécessiterait de modifier le code client.

1

La comparaison de type_info serait-elle plus rapide? (appel typeid sur le paramètre param)

+2

Oui, c'est plus rapide mais il ne vérifie pas l'héritage, seulement les correspondances exactes. – Macke

+1

C'est peut-être plus rapide. Mais ce n'est pas une bonne idée (comme le code original ci-dessus). –

+0

Je pense qu'il a été suffisamment parlé dans les autres réponses. Il a demandé une façon plus rapide de faire ce qu'il fait. (Il est juste de dire que la comparaison de type_infos n'atteindra que * exactement * InterestingDerivedClass, et non quelque chose qui en dérive). –

1

Tout d'abord, ne pas optimiser prématurément. Deuxièmement, si vous interrogez un objet pour une implémentation concrète à l'intérieur, il est probable qu'il y ait quelque chose qui ne va pas dans votre conception (pensez à une double expédition). Comme pour la question d'origine, l'introduction d'une fonction GetRuntimeType() à un ICallback fera très bien: voir MFC pour comment cela peut être fait.

5

Comme d'autres l'ont dit, l'utilisation d'une fonction virtuelle est une bonne pratique. Il y a une autre raison de l'utiliser, qui ne s'applique pas dans votre cas, car vous ne pouvez pas ajouter la VF, mais je pense que cela vaut la peine d'être mentionné - cela peut être beaucoup plus rapide qu'avec la distribution dynamique. Dans certains tests (pas très rigoureux) que j'ai fait avec g ++, la fonction virtuelle a dépassé le dynamic_cast par un facteur de 4.

Parce qu'il existe une telle disparité, il peut être utile de créer votre propre hiérarchie d'héritage. enveloppe le code que vous ne contrôlez pas. Vous créez des instances de l'encapsuleur en utilisant dynamic_cast (une fois) pour décider du type dérivé à créer, puis utiliser les fonctions virtuelles à partir de là.

+1

+1 Pour l'emballage – ralphtheninja

1

Dans votre cas d'utilisation concret, la réponse est d'utiliser des fonctions virtuelles.

Il existe cependant des situations dans lesquelles vous devez effectuer un downcast dynamique. Il existe quelques techniques pour rendre cette opération plus rapide (ou beaucoup plus rapide selon la manière dont votre compilateur met en œuvre dynamic_cast), en particulier, si vous vous limitez à l'héritage unique.L'idée principale est que si vous savez en quelque sorte le type exact, un static_cast est beaucoup plus rapide:

f(A* pA) 
{ 
    if (isInstanceOfB(pA)) 
    { 
    B* pB = static_cast<B*>(pA); 
    // do B stuff... 
    } 
} 

Bien sûr, le problème donne maintenant une mise en œuvre rapide de isInstanceOfB().

Voir par exemple boost::type_traits.

2

On dirait un joli design hackish pour moi. (Comme d'autres l'ont mentionné, avoir à utiliser dynamic_cast est généralement un signe que vous avez un problème de conception.). Mais si la majeure partie du code est hors de votre contrôle, je suppose que vous ne pouvez pas faire grand-chose à ce sujet.

Mais non, la seule solution générale que je connaisse est dynamic_Cast. typeid correspond uniquement au type le plus dérivé, qui peut fonctionner dans votre cas. Sur une note de côté, la raison pour laquelle dynamic_cast est si cher peut être qu'il doit traverser toute la hiérarchie de classe. Si vous avez une hiérarchie profonde, cela devient cher (en d'autres termes, ne pas avoir une hiérarchie profonde, en général, mais surtout en C++).

(Bien sûr, la première fois que vous effectuez la distribution, la plupart des recherches de descripteurs de classe sera probablement misses cache, ce qui a pu biaiser votre analyse comparative et fait paraître plus cher que c'est)

1

dynamic_cast standard est très flexible, mais généralement très lent, car il gère de nombreux coins des cas, vous n'êtes probablement pas intéressés au sujet. Si vous utilisez des héritages simples, vous pouvez le remplacer par une implémentation simple basée sur des fonctions virtuelles.

mise en œuvre Exemple:

// fast dynamic cast 
//! Fast dynamic cast declaration 
/*! 
Place USE_CASTING to class that should be recnognized by dynamic casting. 
Do not forget do use DEFINE_CASTING near class definition. 
*\note Function dyn_cast is fast and robust when used correctly. 
Each class that should be used as target for dyn_cast 
must use USE_CASTING and DEFINE_CASTING macros.\n 
Forgetting to do so may lead to incorrect program execution, 
because class may be sharing _classId with its parent and IsClassId 
will return true for both parent and derived class, making impossible' 
to distinguish between them. 
*/ 
#define USE_CASTING(baseType) \ 
    public: \ 
    static int _classId; \ 
    virtual size_t dyn_sizeof() const {return sizeof(*this);} \ 
    bool IsClassId(const int *t) const \ 
    { \ 
    if(&_classId==t) return true; \ 
    return baseType::IsClassId(t); \ 
    } 

//! Fast dynamic cast root declaration 
/*! 
Place USE_CASTING_ROOT to class that should act as 
root of dynamic casting hierarchy 
*/ 

#define USE_CASTING_ROOT \ 
    public: \ 
    static int _classId; \ 
    virtual size_t dyn_sizeof() const {return sizeof(*this);} \ 
    virtual bool IsClassId(const int *t) const { return (&_classId==t); } 

//! Fast dynamic cast definition 
#define DEFINE_CASTING(Type) \ 
    int Type::_classId; 

template <class To,class From> 
To *dyn_cast(From *from) 
{ 
    if(!from) return NULL; 
    if(from->IsClassId(&To::_classId)) 
    { 
    assert(dynamic_cast<To *>(from)); 
    return static_cast<To *>(from); 
    } 
    return NULL; 
} 

Cela dit, je suis d'accord avec d'autres complète dynamic_cast est suspect et vous le plus souvent être en mesure d'atteindre le même objectif d'une manière beaucoup plus propre. Cela dit, semblable à goto, il peut y avoir des cas où il peut être vraiment utile et plus lisible.

Une autre remarque: si vous dites que les classes en question sont hors de votre contrôle, cette solution ne vous aidera pas, car elle nécessite de modifier les classes (pas beaucoup, il suffit d'ajouter quelques lignes, mais vous devez les modifier). Si c'est vraiment le cas, vous devez utiliser ce que la langue offre, c'est-à-dire dynamic_cast et typeinfo.