RTTI Fundamentals
Run-Time Type Information in C++
C++ offers some minimal dynamic type inspection under the umbrella term “RTTI”. Most programs do without, and several compilers either turn it off by default, or have a compile time option to en/disable it. There are not many facilities, and they are briefly presented here.
PREREQUISITES — You should already…
- have non-trivial C++ language & programming background.
- be experienced in creating and compiling C++ programs.
- understand the terms runtime, compile-time, dynamic, static & polymorphic in a C++ context.
Introduction
RTTI is a dynamic mechanism, which means it operates during runtime, and cannot generally report errors at compile-time. Dynamic type conversion will therefore return nullptr
or throw an exception during runtime. This is the only reflection support you will find in C++.
NOTE — Compile-Time Conversions
Type conversions, including dynamic conversions, are all evaluated for validity at compile-time, within reason. If the compiler cannot detect an obvious problem, the cast may be allowed, but the result could be invalid; using it in such a case will generally result in a crash, or invalid memory access.
RTTI is not a general reflection mechanism — it only works up and down an inheritance hierarchy. And even then, it only works if the classes involved are polymorphic (implement or inherit at least one virtual function).
RTTI Operators
There are two operators that use RTTI: typeid()
, and dynamic_cast<>(
‹expr›)
. They are both briefly discussed below.
Type Information
The typeid(
‹expr›‖‹type›)
operator provides information about a type. This operator returns a std::type_info
object, declared in the <typeinfo>
header, which must be included before using typeid()
, otherwise the program is ill-formed.
For polymorphic types, the information can be retrieved at runtime, and for other types, the information is determined at compile-time, if possible. Type qualifiers, like const
or volatile
, are ignored by typeid()
.
A simply way to use typeid
, is to retrieve the name of the type via the name()
member function:
The output from GCC will be mangled, but can be filtered (piped) through the GCC command line utility c++filt
, with the -t
switch, to obtain more readable names. Alternatively, several utility functions using the GCC C++ ABI can be found to demangle names in the program itself.
It can be used to check if ‹expr1›
and ‹expr2›
(for example) have the same type:
Designs should not be built around this ability — it is mostly a mechanism used as a last resort.
Dynamic Cast
The dynamic_cast
operator works only with polymorphic types (classes with one or more virtual functions, either defined in the respective classes, or inherited from base classes). Used on pointer conversions, it will return nullptr
if the conversion fails. Using dynamic_cast
for casting to a reference type, may cause it to throw a std::bad_cast
exception.
Although dynamic_cast
can be used for upcasts, it is not necessary, since conversion to a base class pointer is always implicit.
rttidemo.cpp
— Minimal RTTI Example
/*!@file rttidemo.cpp
* @brief Minimalistic RTTI Demonstration
*/
#include <iostream>
using std::cout; using std::endl;
class PolyBase { // class with (at least) one virtual function
public: virtual ~PolyBase() { }
};
class PolyDeriv : public PolyBase { // inherits a virtual function
//...
};
int main () {
PolyBase base_obj{};
PolyDeriv deriv_obj{};
PolyBase* base_ptr{nullptr};
// use `typeid()` to determine if `base_ptr` points to a `PolyDeriv`
// object. we could also have compared `type_info` objects directly.
//
base_ptr = &deriv_obj;
if (typeid(*base_ptr).hash_code() == typeid(PolyDeriv).hash_code())
cout << "base_ptr points to a PolyDeriv object" << endl;
else
cout << "base_ptr does not point to a PolyDeriv object" << endl;
// use `dynamic_cast` for downcast from `PolyBase*` to `PolyDeriv*`.
//
if (dynamic_cast<PolyDeriv*>(base_ptr))
cout << "base_ptr points to a PolyDeriv object" << endl;
else
cout << "base_ptr does not point to a PolyDeriv object" << endl;
// same code, but with `base_ptr` pointing to a `PolyBase` object:
//
base_ptr = &base_obj;
if (dynamic_cast<PolyDeriv*>(base_ptr))
cout << "base_ptr points to a PolyDeriv object" << endl;
else
cout << "base_ptr does not point to a PolyDeriv object" << endl;
return EXIT_SUCCESS;
}
Of course, static_cast
could have been used, but it would be a compile-time cast, and will not report a failure — the program may simply crash if you used a PolyBase*
as a PolyDeriv*
if it does not actually point to a PolyDeriv
object. At least with dynamic_cast
, a nullptr
is returned on failure.
WARNING — Careful with Casts
Calling a derived class function on an incorrectly converted base class pointer may in fact work, but the derived data will appear corrupt, since it does not exist — just like indexing past the end of a C-style array, which may corrupt data or crash, or not.
It is really as simple as that. And no more, as far as C++ reflection goes, but do not forget about static type checking via facilities in the <type_traits>
header.
2017-11-18: Update to new admonitions. [brx]
2017-09-20: Created. Edited. [brx;jjc]