c++inheritancerttitypeid

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?


Solution

  • 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;
        }
    };