当前位置: 首页 > news >正文

【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 时:

  1. Base 含有虚函数,所以对象包含 vtable 指针(vptr)。
  2. vtable 中存储 RTTI 信息,包括 typeid 需要的类型名。
  3. 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 typeidstd::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) 说明 intdouble 是不同类型。

4. RTTI 的使用限制

  • RTTI 依赖虚表(vtable),仅适用于包含虚函数的类
    • 没有虚函数的类不会生成 RTTI 信息dynamic_cast 对非多态类无效。
  • dynamic_cast 只能用于指针或引用
    • 不能用于值类型转换。
  • RTTI 可能影响性能
    • dynamic_cast 需要查找继承层次,性能比普通转换稍低。

5总结

特性typeiddynamic_cast
适用范围任何类型仅适用于多态
作用获取类型信息进行类型转换
依赖 RTTI
空指针行为可能抛异常返回 nullptr
性能低成本可能较慢

dynamic_cast会在后续的c++四种类型转换中详细说明

http://www.xdnf.cn/news/1437769.html

相关文章:

  • 深度学习篇---DenseNet
  • 深入解析Linux进程概念与操作系统核心
  • 深度学习篇---SGD优化器
  • 「数据获取」《安徽建设统计年鉴》(2002-2007)(2004、2006缺失)(获取方式看绑定的资源)
  • spring boot驴友结伴游网站的设计与实现(代码+数据库+LW)
  • 使用Global Watersheds提取水文站控制区域(水文站上下游 流域水系等)
  • 【自记】Python 中 简化装饰器使用的便捷写法语法糖(Syntactic Sugar)示例
  • 复刻 Python 实现的小智语音客户端项目py-xiaozhi日记
  • 【算法笔记 day six】二分算法的第三部分
  • 手写Muduo网络库核心代码1-- noncopyable、Timestamp、InetAddress、Channel 最详细讲解
  • 测试覆盖率不够高?这些技巧让你的FastAPI测试无懈可击!
  • maven【maven】技术详解
  • ARM编译器生成的AXF文件解析
  • 平衡车-ADC采集电池电压
  • 综合诊断板CAN时间戳稳定性测试报告8.28
  • Linux内核进程管理子系统有什么第四十回 —— 进程主结构详解(36)
  • 安装部署k3s
  • Java试题-选择题(29)
  • 算法题打卡力扣第3题:无重复字符的最长子串(mid)
  • Suno AI 新功能上线:照片也能唱歌啦!
  • Netty从0到1系列之NIO
  • 进程优先级(Process Priority)
  • 猫猫狐狐的“你今天有点怪怪的”侦察日记
  • CentOS7安装Nginx服务——为你的网站配置https协议和自定义服务端口
  • Java注解深度解析:从@ResponseStatus看注解奥秘
  • 大模型RAG项目实战:Pinecone向量数据库代码实践
  • 二叉树经典题目详解(下)
  • 【数据分享】31 省、342 个地级市、2532 个区县农业机械总动力面板数据(2000 - 2020)
  • MySQL数据库——概述及最基本的使用
  • Python实现浅拷贝的常用策略