【c++】多态+RTTI (运行时的类型识别信息)
【c++】多态+RTTI (运行时的类型识别信息)
1. RTTI(运行时类型识别)
RTTI(Run-Time Type Information,运行时类型识别) 是 C++ 提供的一种机制,允许在程序运行时获取对象的实际类型。
它主要依赖 虚函数表(vtable) 进行类型信息存储和查找,适用于多态场景。
指向虚函数的指针/引用,操纵对象时获取指针/引用所指对象的实际派生类型
RTTI 主要包括以下内容:
typeid
运算符- 用于在运行时获取对象的实际类型信息。
- typeid 运算符不仅限于多态场景,它可以用于任何类型,无论该类型是否含有虚函数。
但在多态类中,typeid 依赖 RTTI(运行时类型识别) 来获取真实类型,否则它只能返回静态类型
dynamic_cast
类型转换- 通过 RTTI 判断是否能安全地将基类指针/引用转换为派生类指针/引用。
- 隐藏的类型信息存储
- 当类包含 虚函数 时,编译器会生成一个特殊的类型信息结构,用于存储
type_info
相关信息。 - 该信息通常存储在虚表(vtable)的起始部分。指针指向存储该列的类型数据以及继承关系等等
- 当类包含 虚函数 时,编译器会生成一个特殊的类型信息结构,用于存储
2. RTTI 的实现机制
在 C++ 中,如果一个类包含虚函数,则编译器会在其对象的 虚表(vtable) 中额外存储类型识别信息(RTTI)。
虚表结构示意
假设我们有以下类:
#include <iostream>
#include <typeinfo>class Base {
public:virtual ~Base() {} // 含虚函数,产生虚表
};class Derived : public Base {};int main() {Base* ptr = new Derived();std::cout << typeid(*ptr).name() << std::endl;delete ptr;
}
当 ptr
指向 Derived
时:
- Base 含有虚函数,所以对象包含 vtable 指针(vptr)。
- vtable 中存储 RTTI 信息,包括
typeid
需要的类型名。 - typeid 通过 RTTI 获取
Derived
类型的信息。
示意结构:
Base 对象:
[ vptr -> Base vtable ] [ RTTI(类型信息) ] [ 虚函数地址 ]Derived 对象:
[ vptr -> Derived vtable ] [ RTTI(类型信息) ] [ 虚函数地址 ]
3. typeid
运算符
typeid
运算符不仅限于多态场景,它可以用于任何类型,无论该类型是否含有虚函数。
但在多态类中,typeid
依赖 RTTI(运行时类型识别) 来获取真实类型,否则它只能返回静态类型。
3.1 typeid
的基本使用
typeid
用于获取类型信息,返回 std::type_info
对象,可以通过 .name()
获取类型名称:
#include <iostream>
#include <typeinfo>class A {};
class B { virtual void func() {} }; // 含虚函数int main() {A a;B b;std::cout << "Type of a: " << typeid(a).name() << std::endl; // 静态类型std::cout << "Type of b: " << typeid(b).name() << std::endl; // 静态类型return 0;
}
输出(可能因编译器不同有所变化):
Type of a: A
Type of b: B
说明:
typeid(a).name()
直接返回A
,因为A
没有虚函数,不涉及 RTTI。typeid(b).name()
直接返回B
,尽管B
有虚函数,但这里b
是对象(非指针/引用),仍然是静态类型。
3.2 typeid
在多态中的行为
如果 typeid
作用于指针或引用,且对象属于多态类(含虚函数),它会返回实际类型:
#include <iostream>
#include <typeinfo>class Base {
public: virtual ~Base() {}
};
class Derived : public Base {};int main() {Base* p = new Derived();std::cout << "Actual type: " << typeid(*p).name() << std::endl; // 获取派生类类型delete p;
}
输出:
Actual type: Derived
说明:
Base
含有虚函数,因此typeid(*p)
通过 RTTI 识别真实类型Derived
,而不是Base
。 通过虚函数指针找到虚表 虚表中RTTI指向存储的类型信息、- 如果
Base
没有虚函数,那么typeid(*p)
只能识别静态类型Base
。
3.3 typeid
在非多态情况下
即使类没有虚函数,typeid
仍然适用,但它只能返回编译时的静态类型:
#include <iostream>
#include <typeinfo>class X {};
class Y : public X {};int main() {X* p = new Y();std::cout << "Type: " << typeid(*p).name() << std::endl;delete p;
}
输出:
Type: X
说明:
X
没有虚函数,因此typeid(*p)
只能识别静态类型X
,无法获取Y
。- 只有多态类(含虚函数)时,RTTI 才能生效,返回
Y
。
3.4 typeid
用于基本数据类型
typeid
也适用于基本数据类型:
#include <iostream>
#include <typeinfo>int main() {int a = 10;double b = 5.5;char c = 'A';std::cout << typeid(a).name() << std::endl; // 输出 "int"std::cout << typeid(b).name() << std::endl; // 输出 "double"std::cout << typeid(c).name() << std::endl; // 输出 "char"return 0;
}
输出(可能因编译器不同有所变化)
i
d
c
说明:
i
表示int
d
表示double
c
表示char
不同编译器对 name()
的输出可能不同(例如 GCC 可能返回 _Z1i
等被修饰的名称)。
3.5 typeid
和 std::type_info
typeid
返回 std::type_info
对象,可用于比较类型:
#include <iostream>
#include <typeinfo>int main() {int a;double b;if (typeid(a) == typeid(int)) {std::cout << "a is int" << std::endl;}if (typeid(a) != typeid(b)) {std::cout << "a and b are different types" << std::endl;}return 0;
}
输出:
a is int
a and b are different types
说明:
typeid(a) == typeid(int)
直接比较类型信息。typeid(a) != typeid(b)
说明int
和double
是不同类型。
4. RTTI 的使用限制
- RTTI 依赖虚表(vtable),仅适用于包含虚函数的类
- 没有虚函数的类不会生成 RTTI 信息,
dynamic_cast
对非多态类无效。
- 没有虚函数的类不会生成 RTTI 信息,
dynamic_cast
只能用于指针或引用- 不能用于值类型转换。
- RTTI 可能影响性能
dynamic_cast
需要查找继承层次,性能比普通转换稍低。
5总结
特性 | typeid | dynamic_cast |
---|---|---|
适用范围 | 任何类型 | 仅适用于多态 |
作用 | 获取类型信息 | 进行类型转换 |
依赖 RTTI | 是 | 是 |
空指针行为 | 可能抛异常 | 返回 nullptr |
性能 | 低成本 | 可能较慢 |
dynamic_cast会在后续的c++四种类型转换中详细说明