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.
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.
Hay una razón por la que no tiene ninguna pista, y dynamic_cast
static_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:
Derived
a Base1
no requiere ningún trabajo adicional, que están en la misma dirección físicaDerived
-Base2
hace necesaria para desplazar el puntero por 2 bytestanto , 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 derivadadynamic_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:
virtual
herencia? que debería funcionar ... pero no probadoBuena suerte a cualquiera que trate de poner en práctica esta fuera del compilador, de verdad: x
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
@iAdjunct: De hecho, usé 'Cell' en su lugar. –
Fácil. Derive todos los objetos de una interfaz tipográfica con una función virtual WhoAmI(). Anular en todas las clases derivadas.
¿Qué hay de la transmisión ascendente? –
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!).
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'? –
Actualizado. Creo que fue obvio el camino de solución. – Luca
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? –
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.
Pero esto no le dice si es posible 'dynamic_cast' entre dos tipos o cómo realizar el reparto. –
@ 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! –
Definitivamente un contendiente para la pregunta más entretenida de la entrevista del mes. –
@Neil Butterworth: Estoy de acuerdo. Hay una razón por la cual DC es implementado por el compilador. – Puppy