Using typeid for Runtime Type Identification in C++
This article provides an overview of how the typeid
operator enables runtime type identification (RTTI) in C++. It explains
the distinction between static and dynamic type resolution, how
compilers use virtual method tables (vtables) to retrieve runtime type
metadata, and the practical requirements for correctly identifying
derived classes through base class references or pointers.
In C++, typeid is an operator used to query the type of
an expression at runtime or compile time. It yields a reference to a
constant std::type_info object defined in the
<typeinfo> header, which holds implementation-defined
metadata about the type, including its name and equality comparison
capabilities.
Static vs. Dynamic Resolution
The behavior of typeid depends fundamentally on whether
the evaluated expression involves a polymorphic type:
- Non-Polymorphic Types: If an expression is a
fundamental type, a non-class type, or a class without any virtual
functions,
typeidresolves the type purely at compile time. The compiler inspects the static type of the operand, discarding any dynamic evaluation. - Polymorphic Types: If the operand is a dereferenced
pointer or a reference to a class type that contains at least one
virtualfunction,typeiddefers evaluation to runtime. This allows it to determine the actual derived type of the object rather than the static type of the reference or pointer.
How typeid Works
Under the Hood
Dynamic type identification relies on the compiler's implementation of Run-Time Type Information (RTTI), which is integrated into the class's virtual table infrastructure:
- The Virtual Table Pointer (
vptr): Every instance of a polymorphic class contains an implicit pointer (vptr) that points to that class's virtual table (vtable). - Type Information Metadata: Modern ABIs (such as the
Itanium C++ ABI used by GCC and Clang, as well as MSVC's ABI) allocate a
pointer to a
std::type_infostructure alongside the virtual function pointers inside each class'svtable—typically at a fixed negative offset relative to the function pointers. - Runtime Lookup: When
typeid(*base_ptr)is executed, the program follows the object'svptrto itsvtable, reads the associatedstd::type_infopointer, and returns a reference to that specific instance's runtime type metadata.
Practical Implementation
To inspect dynamic types correctly, the pointer must be dereferenced so that the operator inspects the object itself rather than the pointer variable:
#include <iostream>
#include <typeinfo>
class Base {
public:
virtual ~Base() = default; // Declares the class as polymorphic
};
class Derived : public Base {};
int main() {
Base* ptr = new Derived();
// Querying the pointer yields static type "Base*"
std::cout << typeid(ptr).name() << '\n';
// Querying the dereferenced object yields dynamic type "Derived"
std::cout << typeid(*ptr).name() << '\n';
// Comparing types directly
if (typeid(*ptr) == typeid(Derived)) {
std::cout << "Object is an instance of Derived\n";
}
delete ptr;
return 0;
}Critical Considerations
- Dereferencing Null Pointers: If
typeidis applied to a dereferenced null pointer of a polymorphic type (e.g.,typeid(*ptr)whereptrisnullptr), the C++ runtime throws astd::bad_typeidexception instead of causing undefined behavior. - Exact Matching vs. Inheritance:
typeidchecks for exact type identity. Unlikedynamic_cast, comparingtypeid(*ptr) == typeid(Base)evaluates tofalseif*ptris an instance ofDerived, as it does not account for class hierarchy relationships. - Compiler Settings: RTTI can introduce a minor
overhead in binary size and execution speed. Consequently, some
environments allow disabling RTTI via compiler flags (such as
-fno-rttiin GCC/Clang or/GR-in MSVC), which disables dynamictypeidevaluation.