Using RTTI to determine inheritance graph in C++?
What, if any, c++ constructs are there for listing the ancestors of a class at runtime?
Basically, I have a class which stores a pointer to any object, including possibly a primitive type (somewhat like boost::any
, which I don't want to use because I need to retain ownership of my objects). Internally, this pointer is a void*
, but the goal of this class is to wrap the void*
with runtime type-safety. The assignment operator is templated, so at assignment time I take the typeid()
of the incoming pointer and store it. Then when I cast back later, I can check the typeid()
of the cast type against the stored type_info
. If it mismatches, the cast will throw an exception.
But there's a problem: It seems I lose polymorphism. Let's say B
is a base of D
. If I store a pointer to D
in my class, then the stored type_info
will also be of D
. Then later on, I might want to retrieve a B
pointer. If I use my class's method to cast to B*
, then typeid(B) == typeid(D)
fails, and the cast raises an exception, even though D->B
conversion is safe. Dynamic_cast<>()
doesn't apply here, since I'm operating on a void*
and not an ancestor of B
or D
.
What I would like to be able to do is check is_ancestor(typeid(B), typeid(D))
. Is this possible? (And isn't this what dynamic_cast<>
is doing behind the scenes?)
If not, then I am thinking of taking a second approach anyway: implement a a class TypeInfo
, whose derived classes are templated singletons. I can then store whatever information I like in these classes, and then keep pointers to them in my AnyPointer
class. This would allow me to generate/store the ancestor information at compile time in a more accessible way. So failing option #1 (a built-in way of listing ancestors given only information available at runtime), is there a construct/procedure I can use which will allow the ancestor information to be generated and stored automatically at compile-time, preferably without having to开发者_高级运维 explicitly input that "class A
derives from B
and C
; C
derives from D
" etc.? Once I have this, is there a safe way to actually perform that cast?
I had a similar problem which I solved through exceptions! I wrote an article about that:
Part 1, Part 2 and code
Ok. Following Peter's advise the outline of the idea follows. It relies on the fact that if D
derives from B
and a pointer to D
is thrown, then a catch clause expecting a pointer to B
will be activated.
One can then write a class (in my article I've called it any_ptr
) whose template constructor accepts a T*
and stores a copy of it as a void*
. The class implements a mechanism that statically cast the void*
to its original type T*
and throws the result. A catch clause expecting U*
where U
= T
or U
is a base of T
will be activated and this strategy is the key to implementing a test as in the original question.
EDIT: (by Matthieu M. for answers are best self-contained, please refer to Dr Dobbs for the full answer)
class any_ptr {
void* ptr_;
void (*thr_)(void*);
template <typename T>
static void thrower(void* ptr) { throw static_cast<T*>(ptr); }
public:
template <typename T>
any_ptr(T* ptr) : ptr_(ptr), thr_(&thrower<T>) {}
template <typename U>
U* cast() const {
try { thr_(ptr_); }
catch (U* ptr) { return ptr; }
catch (...) {}
return 0;
}
};
The information is (often) there within the implementation. There's no standard C++ way to access it though, it's not exposed. If you're willing to tie yourself to specific implementations or sets of implementations you can play a dirty game to find the information still.
An example for gcc, using the Itanium ABI is:
#include <cassert>
#include <typeinfo>
#include <cxxabi.h>
#include <iostream>
bool is_ancestor(const std::type_info& a, const std::type_info& b);
namespace {
bool walk_tree(const __cxxabiv1::__si_class_type_info *si, const std::type_info& a) {
return si->__base_type == &a ? true : is_ancestor(a, *si->__base_type);
}
bool walk_tree(const __cxxabiv1::__vmi_class_type_info *mi, const std::type_info& a) {
for (unsigned int i = 0; i < mi->__base_count; ++i) {
if (is_ancestor(a, *mi->__base_info[i].__base_type))
return true;
}
return false;
}
}
bool is_ancestor(const std::type_info& a, const std::type_info& b) {
if (a==b)
return true;
const __cxxabiv1::__si_class_type_info *si = dynamic_cast<const __cxxabiv1::__si_class_type_info*>(&b);
if (si)
return walk_tree(si, a);
const __cxxabiv1::__vmi_class_type_info *mi = dynamic_cast<const __cxxabiv1::__vmi_class_type_info*>(&b);
if (mi)
return walk_tree(mi, a);
return false;
}
struct foo {};
struct bar : foo {};
struct baz {};
struct crazy : virtual foo, virtual bar, virtual baz {};
int main() {
std::cout << is_ancestor(typeid(foo), typeid(bar)) << "\n";
std::cout << is_ancestor(typeid(foo), typeid(baz)) << "\n";
std::cout << is_ancestor(typeid(foo), typeid(int)) << "\n";
std::cout << is_ancestor(typeid(foo), typeid(crazy)) << "\n";
}
Where I cast the type_info
to the real type that's used internally and then recursively used that to walk the inheritance tree.
I wouldn't recommend doing this in real code, but as an exercise in implementation details it's not impossible.
First, what you are asking for cannot be implemented just on top of type_info
.
In C++, for a cast to occur from one object to another, you need more than blindly assuming a type can be used as another, you also need to adjust the pointer, because of multi-inheritance (compile-time offset) and virtual inheritance (runtime offset).
The only way to safely cast a value from a type into another, is to use static_cast
(works for single or multi-inheritance) and dynamic_cast
(also works for virtual inheritance and actually checks the runtime values).
Unfortunately, this is actually incompatible with type erasure (the old template-virtual
incompatibility).
If you limit yourself to non-virtual inheritance, I think it should be possible to achieve this by storing the offsets of conversions to various bases in some Configuration
data (the singletons you are talking about).
For virtual inheritance, I can only think of a map of pairs of type_info
to a void* (*caster)(void*)
.
And all this requires enumerating the possible casts manually :(
It is not possible using std::type_info
since it does not provide a way to query inheritance information or to convert a std::type_info
object to its corresponding type so that you could do the cast.
If you do have a list of all possible types you need to store in your any
objects use boost::variant
and its visitor.
While I can't think of any way to implement option #1, option #2 should be feasible if you can generate a compile-time list of the classes you would like to use. Filter this type list with boost::MPL and the is_base_of
metafunction to get a list of valid-cast typeids, which can be compared to the saved typeid.
精彩评论