Hello,

I'm using a library which creates allows me to set void* userData for some of it's objects, so I can connect their objects with mine. When an event happens I get their object and I can retrieve my class from it using casting. My question is about the performance of my approach. So this is the setup.

// This is library class I get
struct Collision { void* userData;};
// My base class
struct Vehicle { };
// My derived class
struct Plane: Vehicle { void Fly(){} };

// My instances
Vehicle v;
Plane p;

// Thier instances
Collision a; a.userData = &v;
Collision b; b.userData = &p;
Collision c; c.userData = NULL;

// This is where I want to get to plane
void EventCallBack(Collision& col)
{
	// Make sure it's not c
	if(!col.userData) return;
	// I know for sure this will succeed
	Vehicle* v = static_cast<Vehicle*>(col.userData);
	// Try and see if it's acutally a plane type
	Plane* p = dynamic_cast<Plane*>(v);
	// If it's make it fly
	if(p) p->Fly();
}

I know that dynamic_cast is slow. Another way to do that is to introduce a virtual function into Vehicle, but that does not make sense, because Fly() is required only by Planes.
Yet another approach is to have a variable in Vehicle that says what type of the object it is. The question is: Is dynamic_cast that slow so it justifies using some other approach? Let's say if we have a 6000 calls to this function per second.

Thanks.

Can Fly() be called that many times per second ? What will happen if that function is called again before it finishes the first time?

It's called for different objects. If I have a hundred objects in the scene and the frame rate is 60 fps. We get 6000 calls.
I also could use a callback function in Vehicle class and Plane would define it as boost::bind to it's Fly function.
I'm not sure which way is more efficient. I thought before I sit down and actually test all these different ways I might ask :)
Thanks.

RTTI introduces both space and time overheads.

there is a per class space overhead to store the type information; most implementations use about 40 bytes or so per class.

finding the type_info (typeid on a reference to polymorphic type) involves getting to the vtable and locating the most derived class object that the object in question is belongs to and then extracting the type_info pointer from the vtable of the most derived class.

a dynamic cast conceptually involves getting to the vtable, finding the most derived class object that the object in question belongs to, extracting the type_info pointer from that object's vtable to determine whether the cast is valid and then adjusting the pointer if required. so this not a constant time operation. it depends on the positions within the class hierarchy and the complexity of the class hierarchy. with single non-virtual inheritance, costs are lower; virtual base classes and multiple inheritance make the costs higher. needless to say, the costs also depend on which compiler you use.

dynamic up-casts involve a straightforward adjustment. dynamic down-casts can take longer (there are more things to check). dynamic cross-casts (from one branch of a hierarchy to another) is roughly the cost of an up-cast plus a down-cast.

its probably not worth worrying about the overhead for most programs. embedded programmers tend to get (sometimes unnecessarily) hassled by RTTI overhead; but for 6000 casts per second, there should be nothing to be concerned about.

here are some sample performance measures which sugest that the overhead is not as great as people make it out to be.
g++ 4.2.3 / Thinkpad T61 T7100 @ 1.80GHz / freebsd 6.3-stable

#include <iostream>
#include <boost/timer.hpp>

struct A { virtual ~A() {} static int n ; };
int A::n = 0 ;
struct B : virtual A { virtual ~B() {} };
struct C : virtual A { void foo() { ++n ; } };

struct simple_single : A { void foo() { ++n ; } } ;
struct virtual_single : virtual A, virtual C { void foo() { ++n ; } } ;
struct virtual_multiple : virtual A, virtual B, virtual C {};

void simple_single_cast( A* pa )
{
  simple_single* p = dynamic_cast<simple_single*>(pa) ;
  if( p != 0 ) p->foo() ;
}
void virtual_single_cast( A* pa )
{
  virtual_single* p = dynamic_cast<virtual_single*>(pa) ;
  if( p != 0 ) p->foo() ;
}
void virtual_multiple_cast( A* pa )
{
  C* p = dynamic_cast<C*>(pa) ;
  if( p != 0 ) p->foo() ;
}

int main()
{
    enum { N = 1024*1024*64 } ;
    A a ;
    simple_single ss ;
    virtual_single vs ;
    virtual_multiple vm ;
    std::cout << std::fixed  ;

    boost::timer timer ;
    for( int i=0 ; i<N ; ++i )
    { simple_single_cast( &a ) ; simple_single_cast( &ss ) ; }
    std::cout << "simple_single: " 
              << N*2 / timer.elapsed() / (1024*1024)
              << " million casts per second\n" ;

    timer.restart() ;
    for( int i=0 ; i<N ; ++i )
    { virtual_single_cast( &a ) ; virtual_single_cast( &vs ) ; }
    std::cout << "virtual_single: " 
              << N*2 / timer.elapsed() / (1024*1024)
              << " million casts per second\n" ;

    timer.restart() ;
    for( int i=0 ; i<N ; ++i )
    { virtual_multiple_cast( &a ) ; virtual_multiple_cast( &vm ) ; }
    std::cout << "virtual_multiple: " 
              << N*2 / timer.elapsed() / (1024*1024)
              << " million casts per second\n" ;
}
>c++ -O3 rtti.cc && ./a.out
simple_single: 41.795918 million casts per second
virtual_single: 22.567493 million casts per second
virtual_multiple: 12.272659 million casts per second
This article has been dead for over six months. Start a new discussion instead.