0%

RTTI in C++

RunTime Type Information or RunTime Type Identification, or just RTTI, is a useful feature in C++ language. As its name suggests, this facility gives you the ability to query type information at runtime.

  • dynamic_cast<>
  • typeid()

Those are the main tools to achieve RTTI. Some of you may frown upon this RTTI thing because it seems to have a bad reputation out there, and there is a good reason for that. Generally you want to stay away from this feature because static typing is much safer than dynamic typing thanks to the compiler, and it gives you runtime overheads as well. So why is it useful?

Walking around the Inheritance Graph

Generally we use virtual inheritance and polymorphism because we only care about the interface but not the exact implementations. However, some derived classes may have specific method that doesn’t fit into the interface in any way. Although it is possible to add another virtual function to the base class and all its derived classes and let them provide empty implementations. But doesn’t that sounds awkward when you tell every Person class to program() when a Programmer class has to? It might be more reasonable to recover the true type (not exactly right with dynamic_cast<>, I’ll talk about it later) of a polymorphic type and call the class’s unique function as needed.

dynamic_cast<> is your first choice to achieve this. Consider the following codes,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Base1
{
virtual void base1Fcn();
}

class Base2
{
virtual void base2Fcn();
}

class Derived : public Base1, public Base2
{
void base1Fcn() override;
void base2Fcn() override;
virtual void derivedFcn();
}

// upcast, which is just good old polymorphic pointers
Derived* pd = new Derived();
Base1* pb1 = pd;
Base2* pb2 = dynamic_cast<Base2*>(pd); // Same as above, no explicit cast needed

// downcast, which casts base pointer to derived class
Base1* pb1 = new Derived();
Derived* pd = dynamic_cast<Derived*>(pb1);

// crosscast, which casts from one base pointer to a sibling
Base1* pb1 = new Derived();
Base2* pb2 = dynamic_cast<Base2*>(pb1);

As is quite clear in the codes and comments, you can use dynamic_cast<> to walk around the inheritance graph get to the specific class you need, as long as virtual inheritance is used. The name upcast, downcast, and crosscast come from the tradition to draw base class on top of the derived in an inheritance graph.

Querying the Exact Type

Remember earlier I said that dynamic_cast<> isn’t really about recovering the true type of a polymorphic pointer. Let’s say we have another class down the inheritance graph,

1
2
3
4
5
6
7
8
class FurtherDerived : public Derived
{
void derivedFcn() override;
}

Base1* pb1 = new FurtherDerived();
Derived* pd = dynamic_cast<Derived*>(pb1); // See? Derived is not the real type pb1 points to
pd->derivedFc(); // But this will work

Bjarne Stroustrup has made this quite clear in TCPL,

From a design perspective, dynamic_cast can be seen as a mechanism for asking an object if it provides a given interface

As long as the class provides the requested interface, dynamic_cast<> will work. In other words it works for all derived classes down the road. So how do we find out the exact type of an object? It’s typeid() to the rescue.

1
2
typeid(*pb1) == typeid(FurtherDerived); // true
typeid(*pb1) == typeid(Derived); // false

The return type of typeid() is std::type_info which has some other useful functions like hashing and name string. Keep in mind that typid() and dynamic_cast<> are designed for different tasks so you shouldn’t misuse both.

Alternatives

You could almost always redesign the inheritance relationships and use virtual functions to do dynamic dispatching. But do know that you have the right tools at hands and don’t hesitate to use them when needed.