
运行时类型识别(Runtime Type Identification, RTTI)是 C++ 标准提供的一组机制,允许程序在运行时获取对象的类型信息。RTTI 主要用于处理多态场景下的类型判断,是面向对象编程中解决类型转换、动态分发等问题的重要工具。

在 C++ 中,静态类型系统(编译时类型检查)是核心安全保障,但某些场景需要运行时动态判断对象类型:
RTTI 通过dynamic_cast和typeid两个操作符,配合type_info类,提供了运行时类型查询能力。
RTTI 功能需要编译器支持(现代 C++ 编译器默认开启),但部分嵌入式或高性能场景可能通过编译选项关闭(如 GCC 的-fno-rtti)。关闭 RTTI 后:
dynamic_cast仅能用于指针类型转换(无法用于引用,否则编译错误)typeid对多态类型的行为未定义dynamic_cast:动态类型转换dynamic_cast是 RTTI 中最常用的操作符,用于安全地将基类指针 / 引用转换为派生类指针 / 引用。其核心特性是:
nullptr,引用类型抛出std::bad_cast异常基本语法
// 指针转换(失败返回nullptr)
Derived* d_ptr = dynamic_cast<Derived*>(base_ptr);
// 引用转换(失败抛出std::bad_cast)
Derived& d_ref = dynamic_cast<Derived&>(base_ref);场景 1:向上转换(Upcast)
向上转换(基类指针→基类指针)是安全的,编译器会直接优化为静态转换(等价于static_cast),无需运行时检查。
#include <iostream>
using namespace std;
class Base {
public:
virtual void func() { cout << "Base::func()" << endl; } // 虚函数使类多态
};
class Derived : public Base {
public:
void func() override { cout << "Derived::func()" << endl; }
};
int main() {
Derived d;
Base* base_ptr = &d; // 隐式向上转换(安全)
// dynamic_cast向上转换(等价于static_cast)
Base* upcast_ptr = dynamic_cast<Base*>(base_ptr);
upcast_ptr->func(); // 输出:Derived::func()(多态调用)
return 0;
}
场景 2:向下转换(Downcast)
向下转换(基类指针→派生类指针)是 RTTI 的核心应用场景,用于从基类指针获取派生类的具体类型。转换前需确保基类指针实际指向目标派生类对象,否则返回nullptr。
#include <iostream>
#include <typeinfo>
using namespace std;
class Animal {
public:
virtual ~Animal() = default; // 虚析构函数保证多态
virtual void sound() const { cout << "Animal makes sound" << endl; }
};
class Dog : public Animal {
public:
void sound() const override { cout << "Dog barks" << endl; }
void wagTail() const { cout << "Dog wags tail" << endl; }
};
class Cat : public Animal {
public:
void sound() const override { cout << "Cat meows" << endl; }
void scratch() const { cout << "Cat scratches" << endl; }
};
void interactWithAnimal(Animal* animal) {
// 尝试转换为Dog指针
Dog* dog = dynamic_cast<Dog*>(animal);
if (dog) {
dog->sound();
dog->wagTail();
return;
}
// 尝试转换为Cat指针
Cat* cat = dynamic_cast<Cat*>(animal);
if (cat) {
cat->sound();
cat->scratch();
return;
}
cout << "Unknown animal type" << endl;
}
int main() {
Animal* animals[] = {new Dog(), new Cat(), new Animal()};
for (auto animal : animals) {
interactWithAnimal(animal);
delete animal; // 释放内存
}
return 0;
}
Animal对象无法转换为Dog或Cat,因此输出Unknown animal typedelete基类指针时调用正确的派生类析构函数)场景 3:交叉转换(Crosscast)
交叉转换用于将同一基类的两个派生类指针互相转换,前提是两个派生类存在共同的基类。
class A { public: virtual ~A() = default; };
class B : public A {};
class C : public A {};
void crosscastDemo() {
B* b = new B();
A* a = b; // 向上转换
// 尝试将A*转换为C*(交叉转换)
C* c = dynamic_cast<C*>(a); // 返回nullptr(因为a实际指向B对象)
if (!c) {
cout << "Crosscast from B to C failed" << endl;
}
delete b;
}dynamic_cast用于引用类型时,若转换失败会抛出std::bad_cast异常(需包含头文件<typeinfo>)。
#include <iostream>
#include <typeinfo>
using namespace std;
void processAnimal(Animal& animal) {
try {
Dog& dog = dynamic_cast<Dog&>(animal);
dog.wagTail();
} catch (const bad_cast& e) {
cout << "Not a Dog: " << e.what() << endl;
}
}
int main() {
Cat cat;
processAnimal(cat); // 尝试将Cat&转换为Dog&,触发异常
return 0;
}
dynamic_cast的运行时开销主要来自:
type_info对象)在性能敏感场景(如游戏引擎、高频交易系统)中,频繁使用dynamic_cast可能成为瓶颈。此时建议:
type枚举字段) typeid:类型信息查询
typeid操作符用于获取对象或类型的type_info对象,核心特性:
int、Base),返回静态类型的type_infotype_infonullptr解引用会抛出std::bad_typeid异常基本语法
// 获取类型的type_info(编译时确定)
const type_info& ti1 = typeid(int);
const type_info& ti2 = typeid(Base);
// 获取表达式的type_info(运行时确定,仅当表达式是多态类型时)
const type_info& ti3 = typeid(*base_ptr); // base_ptr是多态类型指针typeid的行为取决于操作数是否为多态类型:
场景 | 非多态类型(无虚函数) | 多态类型(有虚函数) |
|---|---|---|
变量直接类型 | 静态类型(声明类型) | 静态类型(声明类型) |
基类指针指向派生类对象 | 静态类型(基类) | 动态类型(派生类) |
基类引用绑定派生类对象 | 静态类型(基类) | 动态类型(派生类) |
示例代码:
#include <iostream>
#include <typeinfo>
using namespace std;
class NonPolyBase {}; // 非多态类(无虚函数)
class NonPolyDerived : public NonPolyBase {};
class PolyBase { public: virtual ~PolyBase() = default; }; // 多态类(有虚函数)
class PolyDerived : public PolyBase {};
int main() {
// 非多态类型测试
NonPolyBase* npb = new NonPolyDerived();
cout << "Non-poly typeid(*npb): " << typeid(*npb).name() << endl; // 输出NonPolyBase
// 多态类型测试
PolyBase* pb = new PolyDerived();
cout << "Poly typeid(*pb): " << typeid(*pb).name() << endl; // 输出PolyDerived
delete npb;
delete pb;
return 0;
}
typeid(*指针)返回静态类型(基类),因为编译器无法在运行时跟踪其实际类型type_info指针,因此typeid(*指针)能返回实际类型type_info的使用type_info类提供了类型比较操作符(==/!=)和排序操作(before()),常用于类型判断。
#include <iostream>
#include <typeinfo>
using namespace std;
void printTypeInfo(const Animal& animal) {
const type_info& ti = typeid(animal);
cout << "Type name: " << ti.name() << endl;
if (ti == typeid(Dog)) {
cout << "It's a Dog" << endl;
} else if (ti == typeid(Cat)) {
cout << "It's a Cat" << endl;
} else {
cout << "It's a generic Animal" << endl;
}
}
int main() {
Dog dog;
Cat cat;
Animal animal;
printTypeInfo(dog); // 输出Dog类型信息
printTypeInfo(cat); // 输出Cat类型信息
printTypeInfo(animal); // 输出Animal类型信息
return 0;
}
type_info::name()返回的类型名是编译器特定的修饰名(Mangled Name),例如 GCC 中Dog的修饰名是3Dog(3表示类名长度,Dog是类名)。可通过abi::__cxa_demangle函数美化(需链接libstdc++)。
#include <iostream>
#include <typeinfo>
#include <cxxabi.h>
#include <cstdlib>
using namespace std;
string demangle(const char* mangled_name) {
int status;
char* demangled = abi::__cxa_demangle(mangled_name, nullptr, nullptr, &status);
string result = (status == 0) ? demangled : mangled_name;
free(demangled); // 注意释放内存
return result;
}
int main() {
const type_info& ti = typeid(Dog);
cout << "Mangled name: " << ti.name() << endl;
cout << "Demangled name: " << demangle(ti.name()) << endl;
return 0;
}
type_info类详解type_info类由编译器隐式生成,用于存储类型元数据,其核心成员函数如下:
成员函数 | 功能描述 |
|---|---|
const char* name() const | 返回类型的修饰名(编译器特定) |
bool operator==(const type_info& rhs) const | 判断两个类型是否相同 |
bool operator!=(const type_info& rhs) const | 判断两个类型是否不同 |
bool before(const type_info& rhs) const | 判断当前类型是否在rhs类型之前(用于排序,顺序由编译器定义) |
size_t hash_code() const | 返回类型的哈希值(C++11 引入,用于std::unordered_map等容器) |
type_info对象只能通过typeid获取,无法直接构造或复制type_info存储:多态类的虚表(vtable)中包含type_info指针,因此dynamic_cast和typeid可通过虚表访问运行时类型信息type_info::operator==比较的是类型的唯一标识符(如 GCC 使用__type_info结构体的地址作为唯一标识) 
当容器存储基类指针,而实际元素是不同派生类对象时,RTTI 可用于动态调用派生类特有的方法(尽管更推荐虚函数,但某些场景 RTTI 更灵活)。
#include <vector>
#include <memory>
using namespace std;
int main() {
vector<unique_ptr<Animal>> zoo;
zoo.push_back(make_unique<Dog>());
zoo.push_back(make_unique<Cat>());
zoo.push_back(make_unique<Animal>());
for (const auto& animal : zoo) {
// 使用typeid判断类型
if (typeid(*animal) == typeid(Dog)) {
static_cast<Dog*>(animal.get())->wagTail();
} else if (typeid(*animal) == typeid(Cat)) {
static_cast<Cat*>(animal.get())->scratch();
}
}
return 0;
}序列化时需记录对象类型信息,反序列化时根据类型信息重建具体对象。RTTI 可用于获取类型名称作为序列化标签。
#include <fstream>
#include <string>
void serializeAnimal(const Animal& animal, ofstream& file) {
// 写入类型标签(使用typeid获取类型名)
file << typeid(animal).name() << "\n";
// 写入对象数据(示例省略具体字段)
}
unique_ptr<Animal> deserializeAnimal(ifstream& file) {
string type_name;
file >> type_name;
if (type_name == typeid(Dog).name()) {
return make_unique<Dog>();
} else if (type_name == typeid(Cat).name()) {
return make_unique<Cat>();
}
return make_unique<Animal>();
}在调试日志中打印对象类型信息,帮助定位问题。结合demangle函数可输出易读的类型名。
void logObjectInfo(const void* obj, const type_info& ti) {
cout << "Object at " << obj
<< " is of type: " << demangle(ti.name()) << endl;
}
int main() {
Dog dog;
logObjectInfo(&dog, typeid(dog)); // 输出:Object at 0x7ffd... is of type: Dog
return 0;
}dynamic_cast和typeid涉及运行时类型检查,比静态类型操作慢(约 10-100 倍)type_info::name()的输出格式不标准,美化函数(如abi::__cxa_demangle)依赖具体编译器多数情况下,虚函数可替代 RTTI 实现类型相关行为。例如,前面的interactWithAnimal函数可通过虚函数重构:
class Animal {
public:
virtual ~Animal() = default;
virtual void interact() const = 0; // 纯虚函数定义交互行为
};
class Dog : public Animal {
public:
void interact() const override {
cout << "Dog barks and wags tail" << endl;
}
};
class Cat : public Animal {
public:
void interact() const override {
cout << "Cat meows and scratches" << endl;
}
};
int main() {
vector<unique_ptr<Animal>> zoo = {
make_unique<Dog>(),
make_unique<Cat>()
};
for (const auto& animal : zoo) {
animal->interact(); // 多态调用,无需RTTI
}
return 0;
}优势:
RTTI 是 C++ 面向对象编程的重要补充,尤其在需要运行时类型判断的场景中提供了关键能力。但需注意:
dynamic_cast引用转换需用try-catch保护type_info::name()的输出和dynamic_cast的性能可能因编译器而异通过合理使用 RTTI(如异构容器的类型分发、序列化标签),结合面向对象设计原则,可以在灵活性和性能之间取得平衡。