C++: emulating RTTI
I've got a class hierarchy as this one:
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 { } //
I'd like to emulate RTTI (without using it, of course) for this ierarchy, in a way that, given a pointer/reference to A
, I can find out its actual type (similarly to what typeid
does), as an integer identifying the class.
Moreover I'd like that the set of integers identifying my types is conti开发者_运维问答guous and goes from 0 to N-1 (from 0 to 6 in my example):
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;} } //
(the order doesn't really matter: A::t
could return 3 and AAB::t
0, for example.
Is it possible to let the compiler assign the indexes to my classes?
I think that CRTP could help me; something like:
class X : A, AssignFirstAvailableIndex< X > { }
but I'm not good enough with templates. How could I implement that AssignFirstAvailableIndex
template class?
(of course the compiler can see all of the classes at compile time)
There is a standard method to implement what you need. Standard locale facets use it to identify themselves. Consider examining standard header "locale".
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(); }
};
Static members might be initialized in implementation files or be templated to allow instantiation directly from headers or be refactored for lazy initialization.
Maybe something like this? (syntax is probably wrong all over the place, but you get the 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); }
}
... But seriously, please just use RTTI.
I don't think there's any way to generate the indexes at compile time, except by using an enum
(which I'd consider a perfectly reasonable approach). I'm not sure that a template would help, because templates are purely functional and there's nowhere to store any global state (i.e., the current index) the except in the name of the templated type itself (which is exactly what you're trying to avoid).
If you really want integer IDs, it's probably easiest to set them up at runtime rather than trying too hard.
Firstly, have an object that represents a type, and use the typical hand-made-RTTI approach: have each class have a static instance of that object, and have its virtual get-type-info function return a pointer to that object. So each class would have a little bit of code in it, like this:
static TypeInfo ms_type_info;
virtual const TypeInfo *GetTypeInfo() const {
return &ms_type_info;
}
And you'd define the type info, putting into the <<whatever you info you want>>
section whatever info the TypeInfo
stores off to make it better than the compiler's RTTI;
TypeInfo WhateverClass::ms_type_info(<<whatever info you want>>);
(Every implementation I've seen of this uses a macro to automate the creation of these two bits of text; a bit ugly, but your options are limited, and it's better than typing it out.)
The TypeInfo
struct itself would look a bit like this:
struct TypeInfo {
int type_index;
TypeInfo *next;
TypeInfo(<<whatever>>) {<<see below>>}
};
If the reader would prefer get and set functions, it could have those.
The TypeInfo
object should be static to the class, not the function, because the intent is to create a list of all TypeInfos
. You have two options here. The automatic one is to have each TypeInfo
, in the constructor that was left blank above, add itself to some kind of global linked list; the other one is to have a big function that adds the ones it wants to the global list manually.
Then, on startup, run through the TypeInfo
objects and assign each an index. Something like this would do, assuming that there's a TypeInfo *g_first_type_info
that points to the first type info in the list:
int next_type_index=0;
for(TypeInfo *ti=g_first_type_info;ti;ti=ti->next)
ti->type_index=next_type_index++;
Now, when you want your integer ID, you can retrieve it easily:
object->GetTypeInfo()->type_index;
You could implement t
easily enough now:
virtual int t() const {
return ms_type_info.type_index;
}
Full disclosure of the problems I can think of:
The type indexes aren't set at compile time, so if you build up arrays you'll need to build them at runtime. This can be a waste of code if your arrays would otherwise be compile time constants - however for compilers that don't support the C99-style array initialization syntax, this can sometimes make the code more readable.
If you like to do everything in global constructors before
main
starts, you may come unstuck, as theTypeInfo
objects are just ordinary global objects and (in this situation anyway) aren't properly ready for use anyway until the type indexes have been assigned.Linkers have a tendency to strip out global objects that don't appear to get used, so type info objects in static libraries may never add themselves to the list. So you should bear in mind that whilst the automatic registration approach works well, when it doesn't work, it doesn't, so you may end up having to register things manually anyway.
I have been successfully using the method described here: http://www.flipcode.com/archives/Run_Time_Type_Information.shtml. Basically each class which requires RTTI has a static function which returns its rtti type. the advantage of using a structure for the type is that you can add functions to your rtti structure.
I modified this approach to allow things like component->IsOfType(CollisionComponent::GetClass())
.
I also extended the RTTI structure to provide a class name and I'm able now to call component->IsOfType("CollisionComponent")
Without including CollisionComponent.h.
This approach is very handy when combined with this dynamic class creation. I'm able to create and identify C++ classes in scripts and instantiate a large number of different components which are only loaded when the OS/hardware supports the types needed. I'm also able to reconfigure the types of components loaded depending on the server version. For example a server can allow the use of "CollisionComponent_V2_0"
while another one will force the use of "CollisionComponent_Proxy_V2"
精彩评论