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:
typeid(type):
Results in a reference to atype_info
object representing the given type.typeid(expression)
- If evaluating expression results in a polymorphic type, then expression is evaluated and the result of the
typeid
operator is a reference to atype_info
object representing the dynamic type of the evaluated expression. - Otherwise, expression is not evaluated, and the result is a reference to a
type_info
object representing the static type
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
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.