C++ Programming/RTTI
Run-Time Type Information (RTTI)
editRTTI refers to the ability of the system to report on the dynamic type of an object and to provide information about that type at runtime (as opposed to at compile time), and when utilized consistently can be a powerful tool to ease the work of the programmer in managing resources.
Consider what you have already learned about the dynamic_cast
keyword, and let's say that we have the following class hierarchy:
class Interface
{
public:
virtual void GenericOp() = 0;// pure virtual function
};
class SpecificClass : public Interface
{
public:
virtual void GenericOp();
virtual void SpecificOp();
};
Let's say that we also have a pointer of type Interface*
, like so:
Interface* ptr_interface;
Supposing that a situation emerges that we are forced to presume but have no guarantee that the pointer points to an object of type SpecificClass
and we would like to call the member SpecificOp()
of that class. To dynamically convert to a derived type we can use dynamic_cast
, like so:
SpecificClass* ptr_specific = dynamic_cast<SpecificClass*>(ptr_interface);
if( ptr_specific ){
// our suspicions are confirmed -- it really was a SpecificClass
ptr_specific->SpecificOp();
}else{
// our suspicions were incorrect -- it is definitely not a SpecificClass.
// The ptr_interface points to an instance of some other child class of the base InterfaceClass.
ptr_interface->GenericOp();
};
With dynamic_cast
, the program converts the base class pointer to a derived class pointer and allows the derived class members to be called. Be very careful, however: if the pointer that you are trying to cast is not of the correct type, then dynamic_cast
will return a null pointer.
We can also use dynamic_cast
with references.
SpecificClass& ref_specific = dynamic_cast<SpecificClass&>(ref_interface);
This works almost in the same way as pointers. However, if the real type of the object being cast is not correct then dynamic_cast
will not return null (there's no such thing as a null reference). Instead, it will throw a std::bad_cast
exception.
- Syntax
typeid( object );
The typeid
operator is used to determine the class of an object at runtime. It returns a reference to a std::type_info
object, which exists until the end of the program, that describes the "object". If the "object" is a dereferenced null pointer, then the operation will throw a std::bad_typeid
exception.
Objects of class std::bad_typeid
are derived from std::exception
, and thrown by typeid
and others.
The use of typeid
is often preferred over dynamic_cast
<class_type>
in situations where just the class information is needed, because typeid
, applied on a type or non de-referenced value is a constant-time procedure, whereas dynamic_cast
must traverse the class derivation lattice of its argument at runtime. However, you should never rely on the exact content, like for example returned by std::type_info::name()
, as this is implementation specific with respect to the compile.
It is generally only useful to use typeid
on the dereference of a pointer or reference (i.e. typeid(*ptr)
or typeid(ref)
) to an object of polymorphic class type (a class with at least one virtual member function). This is because these are the only expressions that are associated with run-time type information. The type of any other expression is statically known at compile time.
- Example
#include <iostream>
#include <typeinfo> //for 'typeid' to work
class Person {
public:
// ... Person members ...
virtual ~Person() {}
};
class Employee : public Person {
// ... Employee members ...
};
int main () {
Person person;
Employee employee;
Person *ptr = &employee;
// The string returned by typeid::name is implementation-defined
std::cout << typeid(person).name() << std::endl; // Person (statically known at compile-time)
std::cout << typeid(employee).name() << std::endl; // Employee (statically known at compile-time)
std::cout << typeid(ptr).name() << std::endl; // Person * (statically known at compile-time)
std::cout << typeid(*ptr).name() << std::endl; // Employee (looked up dynamically at run-time
// because it is the dereference of a
// pointer to a polymorphic class)
}
Output (exact output varies by system):
Person
Employee
Person*
Employee
In RTTI it is used in this setup:
const std::type_info& info = typeid(object_expression);
Sometimes we need to know the exact type of an object. The typeid
operator returns a reference to a standard class std::type_info
that contains information about the type. This class provides some useful members including the ==
and !=
operators. The most interesting method is probably:
const char* std::type_info::name() const;
This member function returns a pointer to a C-style string with the name of the object type. For example, using the classes from our earlier example:
const std::type_info &info = typeid(*ptr_interface);
std::cout << info.name() << std::endl;
This program would print something like[1]
SpecificClass
because that is the dynamic type of the pointer ptr_interface
.
typeid
is actually an operator rather than a function, as it can also act on types:
const std::type_info& info = typeid(type);
for example (and somewhat circularly)
const std::type_info& info = typeid(std::type_info);
will give a type_info
object which describes type_info
objects. This latter use is not RTTI, but rather CTTI (compile-time type identification).
Limitations
editThere are some limitations to RTTI. First, RTTI can only be used with polymorphic types. That means that your classes must have at least one virtual function, either directly or through inheritance. Second, because of the additional information required to store types some compilers require a special switch to enable RTTI.
Note that references to pointers will not work under RTTI:
void example( int*& refptrTest )
{
std::cout << "What type is *&refptrTest : " << typeid( refptrTest ).name() << std::endl;
}
Will report int*
, as typeid()
does not support reference types.
Misuses of RTTI
editRTTI should only be used sparingly in C++ programs. There are several reasons for this. Most importantly, other language mechanisms such as polymorphism and templates are almost always superior to RTTI. As with everything, there are exceptions, but the usual rule concerning RTTI is more or less the same as with goto
statements. Do not use it as a shortcut around proper, more robust design. Only use RTTI if you have a very good reason to do so and only use it if you know what you are doing.
- ↑ (The exact string returned by std::type_info::name() is compiler-dependent).