views:

5289

answers:

10

I am familiar with C++ RTTI, and find the concept interesting.

Still there exist a lot of more ways to abuse it than to use it correctly (the RTTI-switch dread comes to mind). As a developer, I found (and used) only two viable uses for it (more exactly, one and a half).

Could you share some of the ways RTTI is a viable solution to a problem, with example code/pseudo-code included?

Note: The aim is to have a repository of viable examples a junior developer can consult, criticize and learn from.

Edit: You'll find below code using C++ RTTI

// A has a virtual destructor (i.e. is polymorphic)
// B has a virtual destructor (i.e. is polymorphic)
// B does (or does not ... pick your poison) inherits from A

void doSomething(A * a)
{
   // typeid()::name() returns the "name" of the object (not portable)
   std::cout << "a is [" << typeid(*a).name() << "]"<< std::endl ;

   // the dynamic_cast of a pointer to another will return NULL is
   // the conversion is not possible
   if(B * b = dynamic_cast<B *>(a))
   {
      std::cout << "a is b" << std::endl ;
   }
   else
   {
      std::cout << "a is NOT b" << std::endl ;
   }
}
+6  A: 

Acyclic Visitor (pdf) is a great use of it.

fizzer
I agree. This again what I called above the "contract" programming. +1
paercebal
+3  A: 

I cant say I've ever found a use for in in real life but RTTI is mentioned in Effective C++ as a possible solution to multi-methods in C++. This is because method dispatch is done on the dynamic type of the this parameter but the static type of the arguments.

class base
{
  void foo(base *b) = 0; // dynamic on the parameter type as well
};

class B : public base {...}
class B1 : public B {...}
class B2 : public B {...}

class A : public base
{
  void foo(base *b)
  {
    if (B1 *b1=dynamic_cast<B1*>(b))
      doFoo(b1);
    else if (B2 *b2=dynamic_cast<B2*>(b))
      doFoo(b2);
  }
};
1800 INFORMATION
As a side-note, there's also a mention of multimethod impl in Alexandrescu's "Modern C++ Design"
GregC
+2  A: 

I worked on an aircraft simulation once, that had what they (somewhat confusingly) referred to as a "Simulation Database". You could register variables like floats or ints or strings in it, and people could search for them by name, and pull out a reference to them. You could also register a model (an object of a class descended from "SimModel"). The way I used RTTI, was to make it so you could search for models that implement a given interface:

SimModel* SimDatabase::FindModel<type*>(char* name="")
{
   foreach(SimModel* mo in ModelList)
   if(name == "" || mo->name eq name)
   {
       if(dynamic_cast<type*>mo != NULL)
       {
           return dynamic_cast<type*>mo;
       }
   }
   return NULL;
}

The SimModel base class:

class public SimModel
{
    public:
        void RunModel()=0;
};

An example interface might be "EngineModel":

class EngineModelInterface : public SimModel
{
    public:
        float RPM()=0;
        float FuelFlow()=0;
        void SetThrottle(float setting)=0; 
};

Now, to make a Lycoming and Continental engine:

class LycomingIO540 : public EngineModelInterface 
{
    public:
        float RPM()
        {
            return rpm;
        }
        float FuelFlow()
        {
            return throttleSetting * 10.0;
        }
        void SetThrottle(float setting) 
        {
            throttleSetting = setting
        }
        void RunModel() // from SimModel base class
        {
            if(throttleSetting > 0.5)
                rpm += 1;
            else
                rpm -= 1;
        }
    private:
        float rpm, throttleSetting;
};
class Continental350: public EngineModelInterface 
{
    public:
        float RPM()
        {
            return rand();
        }
        float FuelFlow()
        {
            return rand;
        }
        void SetThrottle(float setting) 
        {
        }
        void RunModel() // from SimModel base class
        {
        }
};

Now, here's some code where somebody wants an engine:

.
.
EngineModelInterface * eng = simDB.FindModel<EngineModelInterface *>();
.
.
fuel = fuel - deltaTime * eng->FuelFlow();    
.
.
.

Code is pretty pseudo, but I hope it gets the idea across. One developer can write code that depends on having an Engine, but as long as it has something that implements the engine interface, it doesn't care what it is. So the code that updates the amount of fuel in the tanks is completely decoupled from everything except the FindModel<>() function, and the pure virtual EngineModel interface that he's interested in using. Somebody a year later can make a new engine model, register it with the SimulationDatabase, and the guy above who updates fuel will start using it automatically. I actually made it so you could load new models as plugins (DLLs) at runtime, and once they are registered in the SimulationDatabase, they could be found with FindModel<>(), even though the code that was looking for them was compiled and built into a DLL months before the new DLL existed. You could also add new Interfaces that derive from SimModel, with something that implements them in one DLL, something that searches for them in another DLL, and once you load both DLLs, one can do a FindModel<>() to get the model in the other. Even though the Interface itself didn't even exist when the main app was built.

Parenthetically, RTTI doesn't always work across DLL boundaries. Since I was using Qt anyway, I used qobject_cast instead of dynamic_cast. Every class had to inherit from QObject (and get moc'd), but the qobject meta-data was always available. If you don't care about DLLs, or you are using a toolchain where RTTI does work across DLL boundaries (type comparisons based on string comparisons instead of hashes or whatever), then all of the above with dynamic_cast will work just fine.

KeyserSoze
Effectively, I tried, too, the decoupling of interfaces and implementations, and code using defined interfaces worked quite well with implementation types coded time after, without needing recompilation... +1
paercebal
After reading the first lines of Your post, I wondered why I could not remember posting some details about my aircraft simulation. :-DThis should proove, that there are some examples of useful RTTI.
Black
A: 

I used RTTI when doing some canvas-based work with Qt several years ago. It was darn convenient when doing hit-tests on objects to employ RTTI to determine what I was going to do with the shape I'd 'hit'. But I haven't used it otherwise in production code.

itsmatt
+6  A: 

How about the boost::any object!

This basically uses the RTTI info to store any object and the retrieve that object use boost::any_cast<>.

Martin York
While boost:any does not use dynamic cast, it uses the typeid operator which works even for non-polymorphic types, to be sure the cast is correct. +1
paercebal
+3  A: 

You can use RTTI with dynamic_cast to get a pointer to a derived class in order to use it to call a fast, type specialized algorithm. And instead of using the virtual methods through the base class, it will make direct and inlined calls.

This sped things up for me a lot using GCC. Visual Studio didn't seem to do as well, it may have a slower dynamic_cast lookup.

Example:

D* obj = dynamic_cast<D*>(base);
if (obj) {
    for(unsigned i=0; i<1000; ++i)
        f(obj->D::key(i));
    }
} else {
    for(unsigned i=0; i<1000; ++i)
        f(base->key(i);
    }
}
Zan Lynx
Probably D* d = dynamic_cast<D*>(base); for (i=0;i!=1000;++i) { d->Foo(); }.Wrong idea, since this doesn't call MoreDerived::Foo
MSalters
Use the d->D::Foo() syntax. I put it in the example I added.
Zan Lynx
Interesting micro optimization, I'll have to remember this.
Mark Ransom
I would be interested by "why this goes faster on gcc". The work avoided in `obj->D::key` is being done by the dynamic_cast. And will this scale well for, say, multiple types D, E, F, etc., meaning multiple if(D)/else if(E)/else if(F)/else ?
paercebal
@paercebal: It worked because the compiler was able to inline the direct call to D::key(i) and over 1000 iterations the savings of not doing indirect pointer calls adds up.
Zan Lynx
@Zan Lynx: Oops... I was so puzzled by the thing I didn't see the loop. Of course, you're right!
paercebal
@Zan Lynx: Now, as MSalters did put it, what if the class is DD, which is derived from D, and overrides `key`? This means that this piece of code needs to know the exact class hierarchy to work. Still, it is an interesting piece of code. +1.
paercebal
+2  A: 

I use it in a class tree which serializes to a XML file. On the de-serialization, the parser class returns a pointer to the base class which has a enumeration for the type of the subclass (because you don't know which type it is until you parse it). If the code using the object needs to reference subclass specific elements, it switches on the enum value and dynamic_cast's to the subclass (which was created by the parser). This way the code can check to ensure that the parser didn't have an error and a mismatch between the enum value and the class instance type returned. Virtual functions are also not sufficient because you might have subclass specific data you need to get to.

This is just one example of where RTTI could be useful; it's perhaps not the most elegant way to solve the problem, but using RTTI makes the application more robust when using this pattern.

Nick
You're right: This is an awful switch/RTTI combination, but then, you are using XML, and this is a *good* solution to strong-type again what was a string XML <element /> into a full fledged object. +1.
paercebal
A: 

I'm using it with Dynamic Double Dispatch and Templates. Basically, it gives the ability to observe/listen to only the interesting parts of an object.

Johann Gerell
+2  A: 
Motti
you don't need the "diamond shaped hierarchy" to have that...
João Portela
A: 

Use cases I have in my projects (if you know any better solution for specific cases, please comment):

  1. The same thing as 1800 INFORMATION has already mentioned:

    You'll need a dynamic_cast for the operator== or operator< implementation for derived classes. Or at least I don't know any other way.

  2. If you want to implement something like boost::any or some other variant container.

  3. In one game in a Client class which had a std::set<Player*> (possible instances are NetPlayer and LocalPlayer) (which could have at most one LocalPlayer), I needed a function LocalPlayer* Client::localPlayer(). This function is very rarely used so I wanted to avoid to clutter Client with an additional local member variable and all the additional code to handle this.

  4. I have some Variable abstract class with several implementations. All registered Variables are in some std::set<Variable*> vars. And there are several builtin vars of the type BuiltinVar which are saved in a std::vector<BuiltinVar> builtins. In some cases, I have a Variable* and need to check if it is a BuiltinVar* and inside builtins. I could either do this via some memory-range check or via dynamic_cast (I can be sure in any case that all instances of BuiltinVar are in this vector).

  5. I have a gridded collection of GameObjects and I need to check if there is a Player object (a specialized GameObject) inside one grid. I could have a function bool GameObject::isPlayer() which always returns false except for Player or I could use RTTI. There are many more examples like this where people often are implementing functions like Object::isOfTypeXY() and the base class gets very cluttered because of this.

    This is also sometimes the case for other very special functions like Object::checkScore_doThisActionOnlyIfIAmAPlayer(). There is some common sense needed to decide when it actually make sense to have such a function in the base class and when not.

  6. Sometimes I use it for assertions or runtime security checks.

  7. Sometimes I need to store a pointer of some data in some data field of some C library (for example SDL or what not) and I get it somewhere else later on or as a callback. I do a dynamic_cast here just to be sure I get what I expect.

  8. I have some TaskManager class which executes some queue of Tasks. For some Tasks, when I am adding them to the list, I want to delete other Tasks of the same type from the queue.

Albert