2010-07-04 4 views
5

Cela a été demandé dans l'interview.Comment écrire own dynamic_cast

Comment écrire son propre dynamic_cast. Je pense, sur la base de la fonction de nom de typeid.

Maintenant, comment implémenter propre typid? Je n'ai aucune idée à ce sujet.

+0

@ saurabh01 Retour à travers les questions que vous avez posées et choisissez la réponse qui vous a été la plus utile (le cas échéant). Cliquez sur la case à cocher à gauche de cette réponse pour l'accepter. Merci! –

+13

Certainement un concurrent pour Crappiest question d'entrevue du mois. –

+2

@Neil Butterworth: Je suis d'accord. Il y a une raison pour que DC soit implémenté par le compilateur. – Puppy

Répondre

18

Il y a une raison pour laquelle vous n'avez pas la moindre idée, dynamic_cast et static_cast ne sont pas comme const_cast ou reinterpret_cast, ils effectuent réellement l'arithmétique des pointeurs et sont un peu typé.

Le pointeur arithmétique

Pour illustrer cela, pensez à la conception suivante:

struct Base1 { virtual ~Base1(); char a; }; 
struct Base2 { virtual ~Base2(); char b; }; 

struct Derived: Base1, Base2 {}; 

Une instance de Derived devrait ressembler à ceci (il est basé sur gcc, car il est en fait dépendant du compilateur ...):

|  Cell 1  | Cell 2 |  Cell 3  | Cell 4 | 
| vtable pointer | a | vtable pointer  | b | 
|   Base 1    |  Base 2    | 
|      Derived        | 

Maintenant, pensez au travail nécessaire pour c asting:

  • coulée Derived-Base1 ne nécessite aucun travail supplémentaire, ils sont à la même adresse physique
  • coulée Derived-Base2 nécessite de déplacer le pointeur de 2 octets

Par conséquent, , il est nécessaire de connaître la disposition en mémoire des objets pour pouvoir faire un cast entre un objet dérivé et un de sa base. Et ceci n'est connu que du compilateur, l'information n'est pas accessible via une API, elle n'est pas standardisée ou autre chose.

Dans le code, cela se traduirait comme:

Derived derived; 
Base2* b2 = reinterpret_cast<Base2>(((char*)&derived) + 2); 

Et qui est, bien sûr, pour une static_cast. Maintenant, si vous étiez en mesure d'utiliser static_cast dans l'implémentation de dynamic_cast, alors vous pourriez tirer parti du compilateur et le laisser gérer l'arithmétique du pointeur pour vous ... mais vous n'êtes pas encore sorti du bois.

Écrire dynamic_cast?

Pour commencer, nous devons clarifier les spécifications des dynamic_cast:

  • dynamic_cast<Derived*>(&base); renvoie null si base n'est pas une instance de Derived.
  • dynamic_cast<Derived&>(base); jette std::bad_cast dans ce cas.
  • dynamic_cast<void*>(base); renvoie l'adresse des plus classe dérivée
  • dynamic_cast respectent les spécifications d'accès (public, protected et private héritage)

Je ne sais pas pour vous, mais je pense que ça va être laid .L'utilisation typeid ne suffit pas ici:

struct Base { virtual ~Base(); }; 
struct Intermediate: Base {}; 
struct Derived: Base {}; 

void func() 
{ 
    Derived derived; 
    Base& base = derived; 
    Intermediate& inter = dynamic_cast<Intermediate&>(base); // arg 
} 

Le problème ici est que typeid(base) == typeid(Derived) != typeid(Intermediate), de sorte que vous ne pouvez pas compter sur cela non plus.

Une autre chose amusante:

struct Base { virtual ~Base(); }; 
struct Derived: virtual Base {}; 

void func(Base& base) 
{ 
    Derived& derived = static_cast<Derived&>(base); // Fails 
} 

static_cast ne fonctionne pas lorsque l'héritage virtuel est impliqué ... Nous avons donc aller un problème de fétuque calcul arithmétique de pointeur dans

Une solution presque.

class Object 
{ 
public: 
    Object(): mMostDerived(0) {} 
    virtual ~Object() {} 

    void* GetMostDerived() const { return mMostDerived; } 

    template <class T> 
    T* dynamiccast() 
    { 
    Object const& me = *this; 
    return const_cast<T*>(me.dynamiccast()); 
    } 

    template <class T> 
    T const* dynamiccast() const 
    { 
    char const* name = typeid(T).name(); 
    derived_t::const_iterator it = mDeriveds.find(name); 
    if (it == mDeriveds.end()) { return 0; } 
    else { return reinterpret_cast<T const*>(it->second); } 
    } 

protected: 
    template <class T> 
    void add(T* t) 
    { 
    void* address = t; 
    mDerived[typeid(t).name()] = address; 
    if (mMostDerived == 0 || mMostDerived > address) { mMostDerived= address; } 
    } 

private: 
    typedef std::map < char const*, void* > derived_t; 
    void* mMostDerived; 
    derived_t mDeriveds; 
}; 

// Purposely no doing anything to help swapping... 

template <class T> 
T* dynamiccast(Object* o) { return o ? o->dynamiccast<T>() : 0; } 

template <class T> 
T const* dynamiccast(Object const* o) { return o ? o->dynamiccast<T>() : 0; } 

template <class T> 
T& dynamiccast(Object& o) 
{ 
    if (T* t = o.dynamiccast<T>()) { return t; } 
    else { throw std::bad_cast(); } 
} 

template <class T> 
T const& dynamiccast(Object const& o) 
{ 
    if (T const* t = o.dynamiccast<T>()) { return t; } 
    else { throw std::bad_cast(); } 
} 

Vous avez besoin de petites choses dans le constructeur:

class Base: public Object 
{ 
public: 
    Base() { this->add(this); } 
}; 

Alors, nous allons vérifier:

  • classique utilise: l'héritage correct
  • virtual? il devrait fonctionner ... mais pas testé
  • concernant spécificateurs d'accès ... ARG:/

Bonne chance à tous ceux qui essaient de mettre en œuvre ce en dehors du compilateur, vraiment: x

+0

Dans votre représentation graphique de la présentation de la classe en haut, s'il vous plaît changer 'Byte' à autre chose parce que le vtable-pointeur est [presque certainement] pas un octet. – iAdjunct

+1

@iAdjunct: En effet, j'ai utilisé 'Cell' à la place. –

-1

Facile. Dérivez tous les objets d'une interface typeid avec une fonction virtuelle WhoAmI(). Substituez-le dans toutes les classes dérivées.

+0

Qu'en est-il de la projection ascendante? –

1

ON est un moyen de déclarer un identifiant statique (un entier par exemple) qui définit l'ID de classe. Dans la classe, vous pouvez implémenter à la fois des routines statiques et de portée qui renvoient l'identifiant de classe (Remeber pour marquer les routines virtuelles).

L'identificateur statique doit être initialisé lors de l'initialisation de l'application. Une façon consiste à appeler une routine InitializeId pour chaque classe, mais cela signifie que les noms de classe doivent être connus et que le code d'initialisation doit être modifié chaque fois que la hiérarchie de classe est modifiée. Une autre méthode consiste à vérifier l'identificateur valide au moment de la construction, mais cela entraîne un surcoût puisque chaque fois qu'une classe est construite, la vérification est exécutée, mais seule la première fois est utile (en outre, si aucune classe n'est construite, la routine statique ne peut pas être utile puisque l'identifiant n'est jamais initialisé).

Une mise en œuvre équitable pourrait être une classe de modèle:

template <typename T> 
class ClassId<T> 
{ 
    public: 

    static int GetClassId() { return (sClassId); } 

    virtual int GetClassId() const { return (sClassId); } 

    template<typename U> static void StateDerivation() { 
     gClassMap[ClassId<T>::GetClassId()].push_back(ClassId<U>::GetClassId()); 
    } 

    template<typename U> const U DynamicCast() const { 
     std::map<int, std::list<int>>::const_iterator it = gClassMap.find(ClassId<T>); // Base class type, with relative derivations declared with StateDerivation() 
     int id = ClassId<U>::GetClassId(); 

     if (id == ClassId<T>::GetClassId()) return (static_cast<U>(this)); 

     while (it != gClassMap.end()) { 
      for (std::list<int>::const_iterator = pit->second.begin(), pite = it->second->end(); pit != pite; pit++) { 
       if ((*pit) == id) return (static_cast<U>(this)); 
       // ... For each derived element, iterate over the stated derivations. 
       // Easy to implement with a recursive function, better if using a std::stack to avoid recursion. 
      } 
     } 

     return (null); 
    } 

    private: 

    static int sClassId; 
} 

#define CLASS_IMP(klass) static int ClassId<klass>::sClassId = gClassId++; 

// Global scope variables  

static int gClassId = 0; 
static std::map<int, std::list<int>> gClassMap; 

CLASS_IMP est définie pour chaque classe dérivant de ClassId et gClassId et gClassMap est visible à portée mondiale.

Les identifiants de classe disponibles sont conservés par une seule variable entière statique accessible par toutes les classes (une classe de base ClassID ou une variable globale), qui est incrémentée chaque fois qu'un nouvel identificateur de classe est affecté.

Pour représenter la hiérarchie de classe est suffisante une carte entre l'identificateur de classe et ses classes dérivées. Pour savoir si une classe peut être castée dans une classe spécifique, parcourez la carte et vérifiez les dérivations de déclaration.

Il y a beaucoup de difficultés à faire face ... l'utilisation de références! dérivations virtuelles! mauvais casting! Bad initialisation de mappage de type de classe va conduire à des erreurs de casting ...


Les relations entre les classes sont définies manuellement, avec la routine Hardcoded d'initialisation. Cela permet de déterminer si une classe provient de, ou si deux classes sont une dérivation commune.

class Base : ClassId<Base> { } 
#define CLASS_IMP(Base); 
class Derived : public Base, public ClassId<Derived> { } 
#define CLASS_IMP(Derived); 
class DerivedDerived : public Derived, public ClassId<DerivedDerived> { } 
#define CLASS_IMP(DerivedDerived); 

static void DeclareDerivations() 
{ 
    ClassId<Base>::StateDerivation<Derived>(); 
    ClassId<Derived>::StateDerivation<DerivedDerived>(); 
} 

Personnellement, je suis d'accord avec "il y a une raison si compilateurs met en œuvre dynamic_cast"; probablement le compilateur fait les choses mieux (surtout en ce qui concerne le code d'exemple!).

+0

Supposons que vous ayez découvert que vous avez des références à des classes qui apparaissent dans la même hiérarchie, comment proposez-vous de faire le 'dynamic_cast'? –

+0

Mis à jour. Je pense que c'était évident le chemin de la solution. – Luca

+0

Vous semblez utiliser 'reinterpret_cast' d'une spécialisation' ClassId' comme résultat de 'DynamicCast'.Sûrement c'est la mauvaise classe à lancer; même si c'était la bonne classe, reinterpret_cast n'effectuera pas les ajustements appropriés pour les hiérarchies MI. Ai-je manqué quelque chose d'évident dans votre solution? –

0

Pour tenter une réponse de routine un peu moins, si un peu moins défini:

Ce que vous devez faire est de lancer le pointeur sur un int *, créez un nouveau type T sur la pile, jeter un pointeur à int *, et compare le premier int dans les deux types. Cela fera une comparaison d'adresse vtable. Si elles sont du même type, elles auront la même vtable. Sinon, ils ne le font pas.

Le plus sensé de nous coller une constante intégrale dans nos classes.

+0

Mais cela ne vous dit pas s'il est possible de 'dynamic_cast' entre deux types ou comment effectuer le cast. –