2010-12-04 22 views
10

tengo una jerarquía de clases como ésta:C++: emulando RTTI

class A  { }       // 
class AA : A { }       //   A 
class AAA : AA { }       //  / \ 
class AAB : AA { }       //  AA  AB 
class AB : A { }       // /\ /\ 
class ABA : AB { }       // AAA AAB ABA ABB 
class ABB : AB { }       // 

me gustaría emular RTTI (sin usarlo, por supuesto) para este ierarchy, de manera que, dado un puntero/referencia a A, puedo encontrar su tipo real (de manera similar a como lo hace typeid), como un entero que identifica la clase.

Además me gustaría que el conjunto de los enteros identificar mis tipos es contiguo y va de 0 a N-1 (de 0 a 6 en mi ejemplo):

class A  { virtual int t(){return 0;} } // 
class AA : A { virtual int t(){return 1;} } //   A(0) 
class AAA : AA { virtual int t(){return 2;} } //  / \ 
class AAB : AA { virtual int t(){return 3;} } //  AA(1)  AB(4) 
class AB : A { virtual int t(){return 4;} } // / \  / \ 
class ABA : AB { virtual int t(){return 5;} } // AAA(2) AAB(3) ABA(5) ABB(6) 
class ABB : AB { virtual int t(){return 6;} } // 

(la orden no hace realmente importa: A::t podría volver AAB::t 3 y 0, por ejemplo


¿es posible dejar que el compilador de asignar los índices a mis clases

I Thi.? nk que CRTP podría ayudarme; algo así como:

class X : A, AssignFirstAvailableIndex<X> { } 

pero no soy lo suficientemente bueno con las plantillas. ¿Cómo podría implementar esa clase de plantilla AssignFirstAvailableIndex?

(por supuesto, el compilador puede ver todas las clases en tiempo de compilación)

+0

¿con qué propósito desea esto? –

+2

Algunos compiladores proporcionan macros de preprocesador que se expanden a un nuevo entero cada vez que se utilizan dentro de la misma TU. Aparte de eso, creo que no tienes suerte con este. Sin embargo, simplemente plantea la pregunta: _Si lo que quieres es RTTI, ¿por qué no usas RTTI? _ – sbi

+0

Es sobre todo para mejorar mis habilidades con las plantillas y comprender nuevas formas de usarlas. Además, eso haría que mi código sea más fácil de mantener: en este momento tengo esas funciones 'A :: t' codificadas manualmente. Necesito esos valores integrales que identifican los tipos para construir un contenedor que asocie un tipo a un puntero de función; si esos enteros son continuos, puedo usar una matriz; de lo contrario, necesitaría usar una tabla de mapa/hash, y preferiría evitarla. – peoro

Respuesta

3

Hay un método estándar para implementar lo que necesita. Las facetas de locale estándar lo usan para identificarse. Considere la posibilidad de examinar la configuración regional del encabezado estándar.

class Base { 
    public: 
    // Being constructed contains a new unique identifier 
    class Id { 
    // An id factory returns a sequence of nonnegative ints 
    static int allocate() { 
     static int total = 0; 
     return total++; 
    } 
    int _local; 
    public: 
    Id(): _local(allocate()) {} 
    int get() const {return _local;} 
    }; 
    //Child classes should make this function return an id generated by Base::Id constructed as static member. 
    virtual int id() const = 0; 
}; 

class Child1{ 
    public: 
    static const Base::Id _id; 
    virtual int id() { return _id.get(); } 
}; 

class Child2 { 
    public: 
    static const Base::Id _id; 
    virtual int id() { return _id.get(); } 
}; 

class Child3 { 
    public: 
    static const Base::Id _id; 
    virtual int id() { return _id.get(); } 
}; 

Los miembros estáticos podrían ser inicializadas en los archivos de implementación o ser basados ​​en plantillas para permitir la creación de instancias directamente de encabezados o de ser reprogramado para la inicialización perezosa.

+0

Esperaba hacer eso en tiempo de compilación, pero si no es posible, esta es la mejor manera, supongo ... – peoro

0

Tal vez algo como esto? (Sintaxis es probablemente equivocado por todo el lugar, pero se entiende la idea)

class A { 
    virtual int t() { return 0; } 
} 

template <bool AB> 
class AX<AB>: A { 
    virtual int t() { return AB ? 1 : 2; } 
}; 

template <bool AB2> 
template <bool AB> 
class AXX<AB2>: AX<AB> { 
    virtual int t() { return AX<AB>::t() * 2 + (AB2 ? 1 : 2); } 
} 

... Pero en serio, por favor sólo tiene que utilizar RTTI.

0

No creo que haya ninguna forma de generar los índices en tiempo de compilación, excepto mediante el uso de un enum (que consideraría un enfoque perfectamente razonable). No estoy seguro de que una plantilla sea útil, porque las plantillas son puramente funcionales y no hay ningún lugar para almacenar ningún estado global (es decir, el índice actual) excepto en el nombre del tipo de plantilla en sí (que es exactamente lo que está intentando para evitar).

Si realmente desea ID enteros, probablemente sea más fácil configurarlos en tiempo de ejecución en lugar de esforzarse demasiado.

En primer lugar, tenga un objeto que represente un tipo y utilice el enfoque RTTI hecho a mano típico: haga que cada clase tenga una instancia estática de ese objeto y que su función get-type-info virtual devuelva un puntero a ese objeto Así que cada clase tendría un poco de código en él, así:

static TypeInfo ms_type_info; 
virtual const TypeInfo *GetTypeInfo() const { 
    return &ms_type_info; 
} 

Y será definir la información de tipo, poniendo en la sección <<whatever you info you want>> cualquier información los TypeInfo tiendas fuera para que sea mejor que el del compilador RTTI;

TypeInfo WhateverClass::ms_type_info(<<whatever info you want>>); 

(Cada aplicación que he visto de este utiliza una macro para automatizar la creación de estos dos fragmentos de texto, un poco feo, pero sus opciones son limitadas, y es mejor que escribir a cabo.)

La estructura TypeInfo sí se vería un poco como esto:

struct TypeInfo { 
    int type_index; 
    TypeInfo *next; 
    TypeInfo(<<whatever>>) {<<see below>>} 
}; 

Si el lector preferiría obtener y establecer las funciones, podría tener esos. El objeto TypeInfo debe ser estático para la clase, no la función, porque la intención es crear una lista de todos TypeInfos. Tienes dos opciones aquí. El automático es tener cada TypeInfo, en el constructor que se dejó en blanco arriba, agregarse a algún tipo de lista vinculada global; el otro es tener una función grande que agrega los que quiere a la lista global manualmente.

Luego, al iniciar, ejecute los objetos TypeInfo y asigne un índice a cada uno. Algo como esto haría, en el supuesto de que hay una TypeInfo *g_first_type_info que apunta a la primera información de tipo en la lista:

int next_type_index=0; 
for(TypeInfo *ti=g_first_type_info;ti;ti=ti->next) 
    ti->type_index=next_type_index++; 

Ahora, cuando usted quiere que su ID número entero, se puede recuperar fácilmente:

object->GetTypeInfo()->type_index; 

Se podría aplicar t ahora con bastante facilidad:

virtual int t() const { 
    return ms_type_info.type_index; 
} 

la revelación completa de los problemas que se me ocurren:

  • Los índices de tipo no se configuran en tiempo de compilación, por lo que si crea matrices, deberá compilarlas en tiempo de ejecución. Esto puede ser un desperdicio de código si sus matrices de lo contrario serían constantes de tiempo de compilación; sin embargo, para los compiladores que no son compatibles con la sintaxis de inicialización del array al estilo C99, a veces esto puede hacer que el código sea más legible.

  • Si te gusta hacer todo en constructores globales antes de main se inicia, es posible que se despegan, como los TypeInfo objetos son objetos globales comunes y corrientes y (en esta situación de todos modos) no están debidamente preparados para el uso de todos modos hasta que el tipo índices han sido asignados.

  • Los vinculadores tienen la tendencia a eliminar objetos globales que parecen no utilizarse, por lo que los objetos de información en bibliotecas estáticas pueden no agregarse nunca a la lista. Por lo tanto, debe tener en cuenta que, si bien el enfoque de registro automático funciona bien, cuando no funciona, no funciona, por lo que puede terminar teniendo que registrar las cosas manualmente de todos modos.

+0

Considero que todos los problemas considerados se resuelven en el enfoque utilizado por STL. Ver mi respuesta para un ejemplo en forma reducida. – Basilevs

0

He estado utilizando con éxito el método descrito aquí: http://www.flipcode.com/archives/Run_Time_Type_Information.shtml. Básicamente, cada clase que requiere RTTI tiene una función estática que devuelve su tipo rtti. la ventaja de utilizar una estructura para el tipo es que puede agregar funciones a su estructura rtti.

Modifiqué este enfoque para permitir cosas como component->IsOfType(CollisionComponent::GetClass()).

También extendí la estructura RTTI para proporcionar un nombre de clase y ahora puedo llamar al component->IsOfType("CollisionComponent") Sin incluir CollisionComponent.h.

Este enfoque es muy útil cuando se combina con la creación de clase dinámica this. Soy capaz de crear e identificar clases de C++ en scripts y crear instancias de una gran cantidad de componentes diferentes que solo se cargan cuando el sistema operativo/hardware admite los tipos necesarios. También puedo volver a configurar los tipos de componentes cargados según la versión del servidor. Por ejemplo, un servidor puede permitir el uso de "CollisionComponent_V2_0" mientras que otro forzará el uso de "CollisionComponent_Proxy_V2"

Cuestiones relacionadas