2010-07-04 20 views
5

Esto se ha preguntado en la entrevista.Cómo escribir propio dynamic_cast

Cómo escribir propio dynamic_cast. Creo que, sobre la base de la función del nombre del tipeo.

Ahora, ¿cómo implementar el propio typid? No tengo ni idea sobre eso.

+0

@ saurabh01 Volver a través de las preguntas que ha hecho y elija la que fue más útil (si corresponde). Haga clic en la casilla a la izquierda de esa respuesta para aceptarlo. ¡Gracias! –

+13

Definitivamente un contendiente para la pregunta más entretenida de la entrevista del mes. –

+2

@Neil Butterworth: Estoy de acuerdo. Hay una razón por la cual DC es implementado por el compilador. – Puppy

Respuesta

18

Hay una razón por la que no tiene ninguna pista, y dynamic_caststatic_cast no son como const_cast o reinterpret_cast, que en realidad realiza la aritmética de punteros y son un poco typesafe.

La aritmética de punteros

Para ilustrar esto, pensar en el siguiente diseño:

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

struct Derived: Base1, Base2 {}; 

Una instancia de Derived debería ser algo como esto (que está basado en gcc ya que es en realidad compilador dependiente ...):

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

Ahora piense en el trabajo necesario para c asting:

  • enviar contenido desde Derived a Base1 no requiere ningún trabajo adicional, que están en la misma dirección física
  • fundición Derived-Base2 hace necesaria para desplazar el puntero por 2 bytes

tanto , es necesario conocer el diseño de memoria de los objetos para poder transmitir entre un objeto derivado y uno de su base. Y esto solo lo sabe el compilador, no se puede acceder a la información a través de ninguna API, no está estandarizada o cualquier otra cosa.

En el código, esto se traduciría como:

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

Y eso es, por supuesto, para un static_cast.

Ahora, si fueron capaces de utilizar static_cast en la implementación de dynamic_cast, entonces se podría aprovechar el compilador y se deja manejar la aritmética de punteros para usted ... pero todavía no está fuera de la madera.

Escribiendo dynamic_cast?

Lo primero es lo primero, tenemos que aclarar las especificaciones de dynamic_cast:

  • dynamic_cast<Derived*>(&base); vuelve nulo si base no es una instancia de Derived.
  • dynamic_cast<Derived&>(base); arroja std::bad_cast en este caso.
  • dynamic_cast<void*>(base); devuelve la dirección de la clase más derivada
  • dynamic_cast respetar las especificaciones de acceso (public, protected y private herencia)

Yo no sé ustedes, pero yo creo que va a ser feo .Usando typeid no es suficiente aquí:

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

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

La cuestión aquí es que typeid(base) == typeid(Derived) != typeid(Intermediate), por lo que no se puede confiar en eso.

Otra cosa divertida:

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

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

static_cast no funciona cuando se trata de la herencia virtual ... así que hemos recorrer un problema de cálculo aritmético puntero se arrastra en

Una solución casi.

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(); } 
} 

usted necesita algunas pequeñas cosas en el constructor:

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

Por lo tanto, vamos a ver:

  • clásico utiliza: bien
  • virtual herencia? que debería funcionar ... pero no probado
  • respetando especificadores de acceso ... ARG:/

Buena suerte a cualquiera que trate de poner en práctica esta fuera del compilador, de verdad: x

+0

En su representación gráfica del diseño de clase en la parte superior, cambie 'Byte' a otra cosa porque el puntero vtable es [casi] no un byte. – iAdjunct

+1

@iAdjunct: De hecho, usé 'Cell' en su lugar. –

-1

Fácil. Derive todos los objetos de una interfaz tipográfica con una función virtual WhoAmI(). Anular en todas las clases derivadas.

+0

¿Qué hay de la transmisión ascendente? –

1

ONe manera es declarar un identificador estático (un entero por ejemplo) que define el ID de clase. En la clase puede implementar rutinas estáticas y de ámbito que devuelve el identificador de clase (Remeber para marcar rutinas virtuales).

El identificador estático se inicializará en la inicialización de la aplicación. Una forma es llamar a una rutina InitializeId para cada clase, pero esto significa que los nombres de clase deben conocerse, y el código de inicialización se modificará cada vez que se modifique la jerarquía de clase. Otra forma es verificar el identificador válido en el momento de la construcción, pero esto introduce una sobrecarga ya que cada vez que se construye una clase se ejecuta la verificación, pero solo la primera vez es útil (adicionalmente si no se construye ninguna clase, la rutina estática no puede ser útil ya que el identificador nunca se inicializa).

Una aplicación justa podría ser una clase de plantilla:

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 se definirá en cada clase derivada de ClassId y gClassId y gClassMap será visible en el ámbito global.

Los identificadores de clase disponibles se mantienen mediante una única variable entera estática accesible para todas las clases (una clase base ClassID o una variable global), que se incrementa cada vez que se asigna un nuevo identificador de clase.

Para representa la jerarquía de clases es suficiente un mapa entre el identificador de clase y sus clases derivadas. Para saber si una clase puede convertirse en una clase específica, itere sobre el mapa y verifique las derivaciones.

Hay muchas dificultades para enfrentar ... uso de referencias! derivaciones virtuales! mal lanzamiento! Mal tipo de clase de inicialización mapeo dará lugar a errores de casting ...


Las relaciones entre las clases se definirá de forma manual, codificada de manera no con la rutina de inicialización. Esto permite determinar si una clase deriva de, o si dos clases como una derivación común.

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>(); 
} 

Personalmente estoy de acuerdo con "hay una razón si los compiladores implementa dynamic_cast"; probablemente el compilador haga las cosas mejor (¡especialmente con respecto al código de ejemplo!).

+0

Supongamos que ha descubierto que tiene referencias a clases que aparecen en la misma jerarquía, ¿cómo está proponiendo realmente hacer el 'dynamic_cast'? –

+0

Actualizado. Creo que fue obvio el camino de solución. – Luca

+0

Parece que está utilizando un 'reinterpret_cast' de una especialización' ClassId' como resultado de 'DynamicCast'.Seguramente esta es la clase equivocada para ser lanzado; incluso si fuera la clase correcta, reinterpret_cast no hará los ajustes apropiados para las jerarquías MI. ¿Me he perdido algo obvio en tu solución? –

0

Para intentar una respuesta un poco menos de rutina, aunque un poco menos definida:

Lo que hay que hacer es emitir el puntero a un int *, cree un nuevo tipo T en la pila, convertir un puntero a él para int *, y compara la primera int en ambos tipos. Esto hará una comparación de direcciones vtable. Si son del mismo tipo, tendrán el mismo vtable. De lo contrario, no lo hacen.

Los más sensatos de nosotros solo mantenemos una constante integral en nuestras clases.

+0

Pero esto no le dice si es posible 'dynamic_cast' entre dos tipos o cómo realizar el reparto. –

Cuestiones relacionadas