C++的RTTI(运行时类型信息)是面向对象编程中用于运行时识别对象真实类型的核心机制,它允许程序在运行阶段获取对象的类型信息,主要依赖dynamic_cast和typeid两个操作符实现,通常和多态特性配合使用。

RTTI的基本前提
要使用RTTI相关功能,首先必须满足一个核心条件:基类必须包含至少一个虚函数。因为C++的RTTI信息通常存储在虚函数表(vtable)中,只有包含虚函数的类才会有对应的类型信息元数据,否则dynamic_cast和typeid无法正确工作。
为什么需要虚函数
当类中声明了虚函数后,编译器会为该类生成虚函数表,每个包含虚函数的类实例都会包含一个指向对应虚函数表的指针(vptr)。RTTI的类型信息就存放在虚函数表的特定位置,运行时通过vptr找到虚函数表,就能获取到对象的真实类型信息。
typeid操作符的使用
typeid操作符用于获取表达式的类型信息,返回的是一个type_info类的常引用,通过type_info的name()方法可以获取类型的名称,也可以通过==或!=操作符比较两个类型是否相同。
基础使用示例
以下代码演示了typeid的基本用法,包含基类和子类的类型识别场景:
#include <iostream>
#include <typeinfo>
// 基类,包含虚函数才能支持RTTI
class Base {
public:
virtual void func() {}
virtual ~Base() {}
};
// 子类1
class Derived1 : public Base {
public:
void func() override {}
};
// 子类2
class Derived2 : public Base {
public:
void func() override {}
};
int main() {
Base* base1 = new Derived1();
Base* base2 = new Derived2();
Base* base3 = new Base();
// 获取指针指向对象的真实类型
std::cout << "base1指向的类型: " << typeid(*base1).name() << std::endl;
std::cout << "base2指向的类型: " << typeid(*base2).name() << std::endl;
std::cout << "base3指向的类型: " << typeid(*base3).name() << std::endl;
// 类型比较
if (typeid(*base1) == typeid(Derived1)) {
std::cout << "base1指向的是Derived1类型" << std::endl;
}
if (typeid(*base2) == typeid(Derived2)) {
std::cout << "base2指向的是Derived2类型" << std::endl;
}
delete base1;
delete base2;
delete base3;
return 0;
}
typeid的注意事项
- 如果操作数是一个引用或者解引用的指针,且对应的类型包含虚函数,那么typeid会在运行时计算,返回对象的真实类型。
- 如果操作数是一个不包含虚函数的类类型,或者是一个基本类型,那么typeid在编译时计算,返回的是操作数声明的类型,而非真实指向的类型。
- 不要对空指针解引用后使用typeid,这会抛出
std::bad_typeid异常。
dynamic_cast操作符的使用
dynamic_cast用于在继承体系中进行安全的指针或引用转换,它会在运行时检查转换是否合理,只有转换是有效的时候才会返回转换后的指针,否则返回空指针(指针转换场景)或者抛出std::bad_cast异常(引用转换场景)。
指针转换示例
以下代码演示了dynamic_cast的指针转换用法:
#include <iostream>
class Base {
public:
virtual void func() {}
virtual ~Base() {}
};
class Derived1 : public Base {
public:
void func() override {}
void derived1Func() {
std::cout << "调用Derived1的特有方法" << std::endl;
}
};
class Derived2 : public Base {
public:
void func() override {}
};
int main() {
Base* base1 = new Derived1();
Base* base2 = new Derived2();
// 尝试将基类指针转换为Derived1指针
Derived1* d1 = dynamic_cast<Derived1*>(base1);
if (d1 != nullptr) {
std::cout << "转换成功,base1指向的是Derived1" << std::endl;
d1->derived1Func();
} else {
std::cout << "转换失败,base1不是Derived1类型" << std::endl;
}
// 尝试将base2转换为Derived1指针,会失败
Derived1* d2 = dynamic_cast<Derived1*>(base2);
if (d2 != nullptr) {
std::cout << "转换成功,base2指向的是Derived1" << std::endl;
} else {
std::cout << "转换失败,base2不是Derived1类型" << std::endl;
}
delete base1;
delete base2;
return 0;
}
引用转换示例
引用转换失败时不会返回空引用,而是抛出异常,使用的时候需要捕获异常:
#include <iostream>
#include <typeinfo>
class Base {
public:
virtual void func() {}
virtual ~Base() {}
};
class Derived : public Base {
public:
void func() override {}
};
int main() {
Base baseObj;
Derived derivedObj;
Base& baseRef = derivedObj;
try {
// 尝试将基类引用转换为子类引用
Derived& derivedRef = dynamic_cast<Derived&>(baseRef);
std::cout << "引用转换成功" << std::endl;
} catch (const std::bad_cast& e) {
std::cout << "引用转换失败: " << e.what() << std::endl;
}
try {
// 尝试将指向基类对象的引用转换为子类引用,会失败
Derived& derivedRef = dynamic_cast<Derived&>(baseObj);
std::cout << "引用转换成功" << std::endl;
} catch (const std::bad_cast& e) {
std::cout << "引用转换失败: " << e.what() << std::endl;
}
return 0;
}
dynamic_cast和typeid的使用限制
- 两者都只能用于包含虚函数的类类型,对于没有虚函数的类,使用dynamic_cast会导致编译错误,typeid无法获取真实类型。
- dynamic_cast不能用于转换非多态类型的指针或引用,也不能在不同继承分支的类之间进行转换。
- RTTI会带来一定的性能开销,因为运行时需要查询类型信息,在对性能要求极高的场景中需要谨慎使用。
- 不要过度依赖RTTI,很多时候可以通过合理的多态设计来替代类型判断,比如使用虚函数重写来实现不同子类的不同行为,避免显式的类型判断。
两者的适用场景
如果需要判断对象的真实类型,或者需要比较两种类型是否相同,优先使用typeid;如果需要在继承体系中进行安全的向下转换,获取子类特有的接口,优先使用dynamic_cast。实际开发中可以根据具体需求选择合适的操作符,同时尽量通过多态设计减少RTTI的使用,让代码更符合面向对象的设计原则。
RTTIdynamic_casttypeidC++_runtime_type_information修改时间:2026-07-08 06:33:36