Comment écrire propre dynamic_cast

Cela a été demandé dans l’interview.

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

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

Il y a une raison pour laquelle vous n’avez pas d’indice: dynamic_cast et static_cast ne ressemblent pas à const_cast ou à const_cast , ils effectuent en réalité une arithmétique de pointeur et sont un peu protégés du type.

L’arithmétique de pointeur

Pour illustrer cela, pensez au design suivant:

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

Une instance de Derived devrait ressembler à ceci (elle est basée sur gcc car elle dépend du compilateur …):

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

Pensez maintenant au travail nécessaire au casting:

  • la Base1 de Derived en Base1 ne nécessite aucun travail supplémentaire, ils se trouvent à la même adresse physique
  • la Base2 de Derived en Base2 nécessite de décaler le pointeur de 2 octets

Par conséquent, il est nécessaire de connaître la disposition en mémoire des objects pour pouvoir effectuer la conversion entre un object dérivé et une de ses bases. Et ceci est uniquement connu du compilateur, les informations ne sont accessibles via aucune API, ni normalisées, ni rien d’autre.

En code, cela se traduirait comme:

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

Et cela, bien sûr, pour une static_cast .

Maintenant, si vous static_cast utiliser static_cast dans l’implémentation de dynamic_cast , vous pourriez alors exploiter le compilateur et le laisser gérer l’arithmétique du pointeur pour vous … mais vous n’êtes toujours pas à l’abri.

Écrire dynamic_cast?

Tout d’abord, nous devons clarifier les spécifications de dynamic_cast :

  • dynamic_cast(&base); renvoie la valeur null si base n’est pas une instance de Derived .
  • dynamic_cast(base); jette std::bad_cast dans ce cas.
  • dynamic_cast(base); retourne l’adresse de la classe la plus dérivée
  • dynamic_cast respecte les spécifications d’access (inheritance public , protected et private )

Je ne sais pas pour vous, mais je pense que ça va être moche. L’utilisation de typeid n’est pas suffisante ici:

 struct Base { virtual ~Base(); }; struct Intermediate: Base {}; struct Derived: Base {}; void func() { Derived derived; Base& base = derived; Intermediate& inter = dynamic_cast(base); // arg } 

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

Une autre chose amusante:

 struct Base { virtual ~Base(); }; struct Derived: virtual Base {}; void func(Base& base) { Derived& derived = static_cast(base); // Fails } 

static_cast ne fonctionne pas lorsque l’inheritance virtuel est impliqué … nous avons donc un problème de calcul arithmétique de pointeur qui se glisse.

Une quasi solution

 class Object { public: Object(): mMostDerived(0) {} virtual ~Object() {} void* GetMostDerived() const { return mMostDerived; } template  T* dynamiccast() { Object const& me = *this; return const_cast(me.dynamiccast()); } template  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(it->second); } } protected: template  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  T* dynamiccast(Object* o) { return o ? o->dynamiccast() : 0; } template  T const* dynamiccast(Object const* o) { return o ? o->dynamiccast() : 0; } template  T& dynamiccast(Object& o) { if (T* t = o.dynamiccast()) { return t; } else { throw std::bad_cast(); } } template  T const& dynamiccast(Object const& o) { if (T const* t = o.dynamiccast()) { 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, vérifions:

  • utilisations classiques: ok
  • inheritance virtual ? ça devrait marcher … mais pas testé
  • respecter les spécificateurs d’access … ARG: /

Bonne chance à tous ceux qui essaient d’implémenter cela en dehors du compilateur, vraiment: x

L’un des moyens consiste à déclarer un identifiant statique (un entier par exemple) définissant l’ID de classe. Dans la classe, vous pouvez implémenter des routines statiques et étendues qui renvoient l’identifiant de la classe ( rappelez-vous de marquer les routines virtuelles ).

L’identificateur statique doit être initialisé à l’initialisation de l’application. Une solution 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’identifiant valide au moment de la construction, mais cela introduit une surcharge puisque chaque fois qu’une classe est construite, la vérification est exécutée, mais seule la première fois est utile (de plus, si aucune classe n’est construite, la routine statique ne peut pas être utile. puisque l’identifiant n’est jamais initialisé).

Une implémentation équitable pourrait être une classe de modèle:

 template  class ClassId { public: static int GetClassId() { return (sClassId); } virtual int GetClassId() const { return (sClassId); } template static void StateDerivation() { gClassMap[ClassId::GetClassId()].push_back(ClassId::GetClassId()); } template const U DynamicCast() const { std::map>::const_iterator it = gClassMap.find(ClassId); // Base class type, with relative derivations declared with StateDerivation() int id = ClassId::GetClassId(); if (id == ClassId::GetClassId()) return (static_cast(this)); while (it != gClassMap.end()) { for (std::list::const_iterator = pit->second.begin(), pite = it->second->end(); pit != pite; pit++) { if ((*pit) == id) return (static_cast(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::sClassId = gClassId++; // Global scope variables static int gClassId = 0; static std::map> gClassMap; 

CLASS_IMP doit être défini dans pour chaque classe dérivée de ClassId, et gClassId et gClassMap doivent être visibles au niveau global.

Les identificateurs de classe disponibles sont conservés par une seule variable entière statique accessible à toutes les classes (un ID de classe de classe ou une variable globale), qui est incrémentée chaque fois qu’un nouvel identificateur de classe est atsortingbué.

Pour représenter la hiérarchie des classes, un mappage entre l’identifiant de classe et ses classes dérivées suffit. Pour savoir si une classe peut être convertie en une classe spécifique, parcourez la carte et vérifiez déclarer les dérivations.

Il y a beaucoup de difficultés à affronter … utilisation de références! dérivations virtuelles! mauvais casting! Une mauvaise initialisation du mappage de type de classe entraînera des erreurs de transtypage …


Les relations entre les classes doivent être définies manuellement, codées en dur avec la routine d’initialisation. Cela permet de déterminer si une classe dérive de, ou si deux classes sont une dérivation commune.

 class Base : ClassId { } #define CLASS_IMP(Base); class Derived : public Base, public ClassId { } #define CLASS_IMP(Derived); class DerivedDerived : public Derived, public ClassId { } #define CLASS_IMP(DerivedDerived); static void DeclareDerivations() { ClassId::StateDerivation(); ClassId::StateDerivation(); } 

Personnellement, je suis d’accord avec “il ya une raison pour que les compilateurs implémentent dynamic_cast”; probablement le compilateur fait mieux les choses (surtout en ce qui concerne le code de l’exemple!).

Pour tenter une réponse un peu moins routinière, si légèrement moins définie:

Ce que vous devez faire est de convertir le pointeur en un int *, de créer un nouveau type T sur la stack, de lui atsortingbuer un pointeur en int * et de comparer le premier int des deux types. Cela fera une comparaison d’adresse vtable. S’ils sont du même type, ils auront le même vtable. Sinon, ils ne le font pas.

Les plus sensibles d’entre nous ne font que coller une constante intégrale dans nos cours.

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