0

As the title suggests, I am looking for a fast way of runtime typechecking. To illustrate my problem, imagine you have a class hierarchy like the following:

      Base
     /     \
    A       D
   / \     / \
  C   B   F   E
       \ /
        G

My program holds all instances of any class in a single list as Base_ptr because all these classes share common tasks. Now at some point some derived classes will need to know about the existence of an instance of another class. So far so good, I know about dynamic_cast and the typeid()-operator, but both have some mayor drawbacks:

  1. dynamic_cast consumes a lot of processing time if the types are incompatible (e.g. try to cast instances of E to C)
  2. typeid() does not work in "isTypeOrSubtype"-cases, e.g. you need all instances of D or derived from D (so Es, Fs and Gs as well)

The ideal solution would be some kind of "isTypeOrSubtype"-test and only casting, if this test returns successfully. I got an own approach with some macro definitions and precalculated classname hashes, but it is very ugly and hardly maintainable. So I am looking for a cleaner and faster way of dynamic type and subtype checking that can check far more than 20million times per second.

rootmenu
  • 33
  • 5
  • If you are really "looking for a cleaner and faster way of dynamic type and subtype checking that can check far more than 20million times per second", then you really should reconsider your design! A design that requires a high-performance type information check IS bad. – Frunsi Sep 11 '13 at 01:39
  • That may be a misunderstanding as I don't intend to check that often. I do need some kind of measurement to compare different approaches so if I compare two or more different designs by letting them run the same tests and measure the time taken for 10m cases at once. It needs to perform that fast so it won't slow down the other components in case some class uses type checking. – rootmenu Sep 11 '13 at 01:51
  • So, you "don't intend to check that often", but "It needs to perform that fast so it won't slow down other components..."? This is still the same message! And I still suggest, that you reconsider your design and try to avoid RTTI at all. – Frunsi Sep 11 '13 at 06:29
  • I allready considered as many designs as I could find, but I haven't found anything that is fully capable of dealing with all specifications. So assuming that RTTI is the least part of work I still don't want it to become the bottleneck. – rootmenu Sep 11 '13 at 22:59
  • http://stackoverflow.com/a/12344448/541686 – user541686 Sep 12 '13 at 04:26
  • 1
    If your program does business logic based on run-time type checks, you are on the wrong track. – n. m. could be an AI Sep 12 '13 at 06:13
  • @n.m.: I don't do buisness logic and I allready reflected on my design for weeks. My programm works with the Base interface and does not care at all what type a specific instance might or might not be. I simply cannot forsee all the relationships between different classes. – rootmenu Sep 12 '13 at 09:19
  • 1
    If it doesn't care, why does it need to check? – n. m. could be an AI Sep 12 '13 at 09:37
  • Think about a programm running tasks. For the program, a simple task interface is enough information to execute the task, but if you have dependent tasks, one task might need to check for some special other tasks. So my program doen't care, but some classes might do. – rootmenu Sep 12 '13 at 10:43
  • @rootmenu: I think I see your problem: you do not have a problem at all. You try to be prepared for use cases, that may come up some day. But they will not come up, because there will always be a better solution than what you can imagine now. You are afraid of imaginary bottlenecks. – Frunsi Sep 12 '13 at 12:00
  • @rootmenu: and BTW: when you end up with a class hierarchy like the depicted one, then you should throw away and re-think your code anyway! ;-) RTTI will be your least problem then. – Frunsi Sep 12 '13 at 12:05
  • @Frunsi: maybe you are right, maybe I should re-think my code, but I can't figure a better design. – rootmenu Sep 14 '13 at 14:33

4 Answers4

0

If your program knows about all the sub types that will be tested against, you can use a virtual interface that returns a pointer to the sub type. As noted by downvotes and comments, this is not the most flexible approach, since it requires the base class have knowledge of all the derived classes. However, it is very fast. So there is a trade off of flexibility to performance.

class Base {
    //...
    virtual A * is_A () { return 0; }
    virtual B * is_B () { return 0; }
    //...
    template <typename MAYBE_DERIVED>
    MAYBE_DERIVED * isTypeOrSubtype () {
        //...dispatch to template specialization to call is_X()
    }
};

//...
class A : virtual public Base {
    //...
    A * is_A () { return this; }
};

On IDEONE, the suggested technique is 20 to 50 times faster than using dynamic cast.1 The implementation uses macros to allow a new class to be added to a single place, and the proper expansions to the base class occur in an automated way after that.

(1) - I originally clocked it closer to 100 times as fast, but this was without the isTypeOrSubtype() wrapper method that I added to simulate the desired interface.

If flexibility has a higher value than performance, then a slightly less performant solution is to use a map to associate types and corresponding pointer values (having the pointer values removes the need for a dynamic cast). The map instance is maintained in the base class, and the associations are made by the constructors of the subclasses. Whether a regular map or a unordered_map is used will depend on how many subclasses virtually inherit the base class. I would presume the numbers will be small, so a regular map should suffice.

class Base {
    std::map<const char *, void *> children_;
    //...
    template <typename MAYBE_DERIVED>
    MAYBE_DERIVED * isTypeOrSubtype () {
        auto x = children_.find(typeid(MAYBE_DERIVED).name());
        return ((x != children_.end())
                ? static_cast<MAYBE_DERIVED *>(x->second)
                : 0);
    }
};

//...
class A : virtual public Base {
    //...
    A () { children_[typeid(A).name()] = this; }
    //...
};

On IDEONE, this second suggestion is 10 to 30 times faster the using dynamic cast. I don't think IDEONE compiles with optimizations, so I would expect the times to be closer to the first suggestion on a production build. The mechanism as implemented uses typeid(...).name() as the key to the map.2

(2) - This assumes that typeid(...).name() returns something similar to a string literal, and always returns the same string pointer when operating on the same type. If your system does not behave that way, you can modify the map to take a std::string as the key instead, but performance will be degraded.

jxh
  • 69,070
  • 8
  • 110
  • 193
  • I did not down vote, but it always is a bad idea to have base classes that must know all of its derived classes. – Frunsi Sep 11 '13 at 01:41
  • Didn't down vote, but this isn't the most dynamic way of doing it - having to add a function to each subclass each time a new one is added. – Sinkingpoint Sep 11 '13 at 01:42
  • @Quirliom: I agree, I have updated the answer to reflect that. – jxh Sep 11 '13 at 02:35
  • I use a slightly different approach to this idea in another part of my program allready and I wouldn't go as far as to say this idea is always bad. But the fact is this idea is limited to situation where your allready know about all subclasses, so most likely a closed hierarchy. My approach simply uses the subclasses as function arguments but this still is no solution for my above problem. – rootmenu Sep 11 '13 at 22:15
  • @rootmenu: I agree, this method is not applicable for a situation where the base class is a public interface for an exported library component. But, if the base class is only being used by classes implemented in the same closed system to which the base class belongs, the hierarchy is closed. – jxh Sep 11 '13 at 22:22
  • @rootmenu: Perhaps I misunderstood what you meant by "macro definitions and predefined classname hashes", because this solution was meant to be an improvement over that. – jxh Sep 11 '13 at 22:25
  • @jxh: I used some macros to add functionality to every class in that hierarchy. That is, every class got a `static unsigned long Type()` function that returned a hashed classname. I had to precalculate the hashcode for every class as I miss a way to calculate a hashcode at compile-time. Furthermore every class got a function `virtual bool isType(unsigned long uiType)` that, starting at itself, recursively tested every base classes' hashcode. As I said these macro definitions are pretty ugly and I still don't trust the results. – rootmenu Sep 11 '13 at 23:09
  • @rootmenu: I guess I would say precalculating the hashcode for every subclass is not an improvement over making the base class aware of all the subclasses. My solution is an improvement in that instead of adding a manual lookup table to the base class, my solution leverages the existing vtable. – jxh Sep 11 '13 at 23:29
  • @jxh: I didn't add a manual lookup table. The Base class wasn't aware of any subclass at all. I used the same idea as [Frunsi](http://stackoverflow.com/a/18755270/2186380) in his answer, I just omitted the template and used some macros instead as I always run into trubble with templates and module boundaries. – rootmenu Sep 12 '13 at 09:28
0

I wrote an answer to my own question as this is a different approach to avoid RTTI but no real answer to a fast way of dynamic type/subtype check. This still isn't a clean solution, but the best I could think of until now.

If every class in this hierarchy has the following characteristics, I can skip most of the RTTI.

  1. every class should have a private member: static SecureVector<[class]*> s_Instances; where SecureVector<T> is a thread-safe vector.
  2. at the end of every constructor, s_Instances.push_back(this); should be called, to keep track of a newly created instance of that class
  3. at the beginning of the destructor, s_Instances.erase(this); should be called, to remove this instances reference
  4. every class should have a public function: static const SecureVector<[class]*>& Instances() { return s_Instances; } to get an unmodifiable vector containing all instances of this or any derived class

What this does is, every time a constructor is called, the instance adds itself to its own list of instances. When derived classes call their super constructor, the super class adds itself to its respective list of instances. E.g. if I randomly create 100 instances in the above hierarchy, there would allways be 100 entries in my Base class Instances() vector.

In code this would look like this:

class Base
{
    static SecureVector<Base*> s_Instances; // 1. condition

public:

    Base() 
    {
        s_Instances.push_back(this);    // 2. condition
    }

    ~Base()
    {
        s_Instances.erase(this);    // 3. condition
    }

    static const SecureVector<Base*>& Instances() { return s_Instances; } // 4. condition
};

This is still just as a workaround as the four conditions have to be added manually (or by macro or something like it).

rootmenu
  • 33
  • 5
  • I see how this helps you track all instances, but can you show how you use it to implement `isTypeOrSubtype()`? – jxh Sep 12 '13 at 01:33
  • Absolutely the wrong direction. You add a lot of runtime overhead for just a few cases in which you will really need type checks. Check my answer, it adds no runtime overhead at all. – Frunsi Sep 12 '13 at 04:48
  • @jxh: `isTypeOrSubtype()` becomes obsolete with this idea, as the `s_Instances` vector of a class T allready contains all instances that `are T or a subtype of it`. – rootmenu Sep 12 '13 at 08:27
  • @Frunsi: I don't see where this adds a lot of runtime overhead. I am well aware that this adds a little overhead to constructing instances, but instances won't be constructed as frequently as they will be processed. – rootmenu Sep 12 '13 at 08:30
  • @rootmenu: I don't see how this let's a pointer to the base type figure out whether or not it is one of the derived types without a dynamic cast. Can you show an example of how it is done? – jxh Sep 12 '13 at 08:39
  • @jxh: [http://ideone.com/sZ1avU](http://ideone.com/sZ1avU) In this example I use `isTypeOrSubtype()` of Base or of A. – rootmenu Sep 12 '13 at 09:08
  • Your code lets you know if a pointer is a Base or a subclass of Base, but it doesn't let you know if that same pointer is actually an A. Typically, I know if a pointer is Base or a subclass of Base at compile time, because otherwise the assignment to a Base pointer would fail, and this assignment is allowed without a cast. What a down cast is useful for is knowing if a Base pointer is actually an A (as opposed to, or in addition to a B). I don't see how your solution enables that any more efficiently than a down cast. – jxh Sep 12 '13 at 09:35
  • If i need to get all As (or subclasses), I simply get the list from A::Instance(). As I mentioned, my programm is fine with Base* to work fine, but I can't model relationships all across the hierarchy so if an instance should need all instances of a special class, this way it can simply get a list of As, or Bs or any class. It is kind of "the most derived class, thats a common base for a subset of classes" .. – rootmenu Sep 12 '13 at 10:37
  • @rootmenu: you forget all the hidden complexity of managing and even just accessing your instances lists. And, as jxh stated, you still do not have the functionality of a `isTypeOrSubtype()`! Managing lists of instances is a completely different requirement. – Frunsi Sep 12 '13 at 11:52
  • @rootmenu: I did not get from your question that you wanted to be able to retrieve a list of all instances of a type or subtype of your base object. My solution allows you to do this relatively quickly if you have a list of base objects (since it can make the determination an order of magnitude faster than a down cast), so you can avoid managing a complete set of lists, and there are fewer requirements imposed on the subclasses in my solution. My solution is geared more for determining (base on your picture) if a B instance is also a D instance. – jxh Sep 12 '13 at 17:30
0

Some time ago I used something like this:

// the actual type is irrelevant, const char*, int, ...
// but const char* is great for debugging, when it contains the actual class name
typedef const char* TypeId;

class Base {

    // actually the type id is not the value, but its memory location !
    // the value is irrelevant (but its a good idea to set it to the class name)
    static TypeId s_myTypeId;

public:

    static TypeId* getClassType()          { return &s_myTypeId; }
    virtual TypeId* getInstanceType()      { return &s_myTypeId; }

    static TypeId* getClassBaseType()      { return NULL; }
    virtual TypeId* getInstanceBaseType()  { return NULL; }

    virtual bool isType( TypeId* type )            { return type==getInstanceType(); }
    virtual bool isTypeOrSubType( TypeId* type )   { return isType(type); }

};


template< class MyBase >
class TBase : public MyBase {

    // actually the type id is not the value, but its memory location !
    // the value is irrelevant (but its a good idea to set it to the class name)
    static TypeId s_myTypeId;

public:

    static TypeId* getClassType()          { return &s_myTypeId; }
    virtual TypeId* getInstanceType()      { return &s_myTypeId; }

    static TypeId* getClassBaseType()      { return MyBase::getClassType(); }
    virtual TypeId* getInstanceBaseType()  { return MyBase::getInstanceType(); }

    virtual bool isType( TypeId* type )            { return type==getInstanceType(); }
    virtual bool isTypeOrSubType( TypeId* type )   { return isType(type) || MyBase::isTypeOrSubType(type); }

};

// by deriving from TBase<Base>, a new instantiation of s_myTypeId was created,
// so the class now has its very own unique type id,
// and it inherited all the type resolution magic
class A : public TBase<Base> {
};

// NOTE: class B must not derive directly from A, but from TBase<A>
// imagine a hidden class between B and A,
// actually B inherits from the TBase<A> instantiation, which in turn inherits from A
class B : public TBase<A> {
};

// you will also need to instantiate the static members
// hereby the memory location will be reserved,
// and on execution that memory location becomes the unique type id
#define IMPLEMENT_RTTI(CL) TypeId CL::s_myTypeId = STRFY(CL)

// one per class per source file:
IMPLEMENT_RTTI(Base);
IMPLEMENT_RTTI(A);
IMPLEMENT_RTTI(B);

// example usage:
A a;
B b;

b.getInstanceType()==B::getClassType();     // TRUE
b.getInstanceBaseType()==A::getClassType(); // TRUE
B::getClassBaseType()==A::getClassType();   // TRUE

b.isType( B::getClassType() );              // TRUE
b.isType( A::getClassType() );              // FALSE

b.isTypeOrSubType( B::getClassType() );     // TRUE
b.isTypeOrSubType( A::getClassType() );     // TRUE
b.isTypeOrSubType( Base::getClassType() );  // TRUE

It is safe, fast and easy to use. You just have to obey two rules:

  • do not inherit directly from a class X, but inherit from TBase<X>,
  • and add an IMPLEMENT_RTTI(Me) to source file.

There is one drawback: it does not yet support multiple inheritance. But it would be possible with a few changes.

And probably the TypeId type should be composed like typedef const char* TypeLoc and typedef TypeLoc* TypeId. Maybe just a question of taste.

Frunsi
  • 7,099
  • 5
  • 36
  • 42
-1

dynamic_cast would work wonderfully for this!

Base *instance = //get the pointer from your collection;
A* ap = dynamic_cast<A*>(instance);
D* dp = dynamic_cast<D*>(instance);

if(ap) {
    //instance is an A or a subclass of A
}
if(dp) {
    //instance is a D or a subclass of D
}

This will work for more specific checks as well. So you could check for any type you want.

Lance
  • 8,872
  • 2
  • 36
  • 47