Run-Time Type Information (RTTI) in C++

Relative to other object-oriented languages, C++ is very compile-time oriented. Overriding member functions works because of a level of indirection between a member function and its implementation, not because the object has built-in knowledge of its own class.

There are, however, features in C++ that provide a run-time view of an object. These features are commonly grouped together under a feature set called run-time type information (RTTI). RTTI provides a number of useful features for working with information about an object's class membership. One such feature is dynamic_cast(), which allows you to safely convert between types within an object-oriented hierarchy. Using dynamic_cast() on a class without a vtable, that is, without any virtual member functions, causes a compilation error.

A second RTTI feature is the typeid operator, which lets you query for types at run time. The result of applying the operator is a reference to an std::type_info object, defined in <typeinfo>. The type_info class has a member function called name() returning the compiler-dependent name of the type. The typeid operator behaves as follows:

The following code uses typeid to print a message based on the type of the object:

class Animal { public: virtual ˜Animal() = default; };
class Dog : public Animal {};
class Bird : public Animal {};

void speak(const Animal& animal)
{
  if (typeid(animal) == typeid(Dog)) {
    println("Woof!");
  } else if (typeid(animal) == typeid(Bird)) {
    println("Chirp!");
  }
};

Whenever you see code like this, you should immediately consider reimplementing the functionality as a virtual member function. In this case, a better implementation would be to declare a virtual member function called speak() in the Animal base class. Dog would override the member function to print Woof!, and Bird would override it to print Chirp!. This approach better fits object-oriented programming, where functionality related to objects is given to those objects.

One possible use case of the typeid operator is for logging and debugging purposes. The following code makes use of typeid for logging. The logObject() function takes a loggable object as a parameter. The design is such that any object that can be logged inherits from the Loggable class and supports a member function called getLogMessage()

class Loggable
{
public:
  virtual ˜Loggable() = default;
  virtual string getLogMessage() const = 0;
};

class Foo : public Loggable
{
public:
 string getLogMessage() const override { return "Hello logger."; }
};

void logObject(const Loggable& loggableObject)
{
  print("{}: ", typeid(loggableObject).name());
  println("{}", loggableObject.getLogMessage());
}

logObject() first prints the name of the object's class to the console, followed by its log message. This way, when you read the log later, you can see which object was responsible for every written line.