纸上谈“型”不如运行识“真”:深入 C++ RTTI 与多态的底层真相!

纸上谈“型”不如运行识“真”:深入 C++ RTTI 与多态的底层真相!

文章目录

在这里插入图片描述

本篇摘要

本文详解 C++ RTTI 的核心组成(typeid、dynamic_cast)、底层原理(vptr、vtable、继承关系图)及使用场景,强调其仅适用于多态类型,并分析其开销与设计原则,指导合理应用。


RTTI(Run-Time Type Information,运行时类型信息) 介绍

C++ 的 RTTI(Run-Time Type Information,运行时类型信息) 是一套在程序运行时查询和操作对象类型信息的机制。它主要用于在继承体系中安全地识别和转换对象的实际类型。


RTTI 的核心组成

C++ 标准库通过以下两个主要组件提供 RTTI 支持:

1. typeid 运算符
  • 用于获取对象或类型的类型信息
  • 返回一个 const std::type_info& 对象。
  • 可比较两个类型是否相同。
#include<iostream>#include<typeinfo>classBase{virtual~Base()=default;};// 必须有虚函数!classDerived:publicBase{};intmain(){ Base* b =newDerived(); std::cout <<typeid(*b).name()<< std::endl;// 输出实际类型(如 "7Derived") std::cout <<(typeid(*b)==typeid(Derived))<< std std::endl;// truedelete b;}
注意:只有多态类型(含虚函数的类)才能通过基类指针/引用正确识别派生类类型。否则 typeid 返回的是静态类型(即指针声明的类型也就是对应的Base)。

2. dynamic_cast 运算符
  • 用于安全地在继承层次结构中转换指针或引用(主要是向下转型,常用作子转父,然后父再转回子)。
  • 依赖 RTTI 信息进行运行时检查。
Base* b =newDerived(); Derived* d =dynamic_cast<Derived*>(b);if(d){ std::cout <<"Cast succeeded!"<< std::endl;}else{ std::cout <<"Cast failed!"<< std::endl;}
  • 指针版本:失败返回 nullptr
  • 引用版本:失败抛出 std::bad_cast 异常
同样要求:被转换的类型必须是多态类型(有虚函数),否则编译报错。

RTTI 如何工作?(底层原理)

下面我们简单说,就从编写完对应代码时候,对应编译器处理转化方面讲起:

① 编译器为多态类型做了什么?

当一个类被定义为多态类型时,编译器会在编译期自动插入额外的数据结构和指针,主要包括:

1. 虚函数表(vtable)

  • 每个多态类在程序中对应一个全局唯一的 vtable。
  • vtable 是一个函数指针数组,存储该类所有虚函数的地址(对应的虚表存储在只读数据段也就是静态储存区)。

重点来啦:现代主流编译器(如 GCC、Clang、MSVC)还会在 vtable 的开头或特定位置,存入一个指向 std::type_info 对象的指针;也就是说:vtable 不仅用于虚函数调用,也承载了类型信息。

2. 对象内存布局增加 vptr

每个多态类的对象在内存中的最开始处会隐式包含一个指针,称为 vptr(virtual table pointer);vptr 指向该对象所属类的 vtable。
例如:

Base obj;// 内存布局:// [vptr] → 指向 Base 的 vtable// [其他成员变量...]

注意:这个 vptr 是编译器自动添加的,程序员不可见,但真实存在。

3·类型继承关系图(type hierarchy graph)

  • 编译器在程序启动时已构建了一个 类型继承关系图(type hierarchy graph),记录了所有类之间的继承关系,也就是为什么我们使用dynamic_cast等的时候(多态时候),它可以判断是是否能转换,进而做出对应应答!

② 当我们调用对应接口,RTTI底层是如何实现呢?

因此当我们运行时候就会存在这样一个结构:

在这里插入图片描述
  • 上面也就是我们RTTI执行的时候需要走的途径(剩下还有就是编译器生成的对应类型继承关系图了)。
场景 1:typeid(obj)
Base* p =newDerived(); std::cout <<typeid(*p).name();

执行过程如下:

  • 程序通过 p 访问对象;
  • 从对象内存开头读取 vptr;
  • 通过 vptr → vtable;
  • 从 vtable 中取出 type_info 指针;
  • 返回对应的 std::type_info&。

因此,即使 p 是 Base 类型,typeid(p) 也能知道它实际指向的是 Derived 对象!

注意: 如果 Base 没有虚函数(非多态),则对象没有 vptr,编译器无法在运行时知道实际类型,此时 typeid(*p) 只能返回 Base(静态类型),且某些编译器甚至会直接报错或行为未定义。

场景 2:dynamic_cast<Derived*> ( p )

执行过程更复杂一些:

  • 同样通过 p 获取对象的 vptr → vtable → type_info(即实际类型 T);
  • 首先我们要先知道:编译器在程序启动时已构建了一个 类型继承关系图(type hierarchy graph),记录了所有类之间的继承关系;

这也就是RTTI核心(运行时检查目标类型 Derived 是否是 T 的基类或派生类?):

此时就是拿着对应type_info类型按照对应的继承关系图去对比,如果在里面存着这个继承关系也就是在里面就进行转化,不在进行异常处理。

  • 如果是 → 安全转换,计算偏移量(考虑多重继承),返回正确指针;
  • 如果不是 → 返回 nullptr(指针版本)或抛异常(引用版本)。
比如: 若 p 实际指向 OtherClass,而 OtherClass 与 Derived 无继承关系,则 dynamic_cast 失败。

std::type_info 类简介

  • 由编译器实现,不可复制(C++11 起可移动);
  • 主要成员:
    • name():返回类型的实现定义名称(通常是 mangled name,可用 c++filt 解码);
    • operator== / operator!=:比较两个类型是否相同;
    • before():用于 std::type_index 的排序(C++11)。

如:

std::cout <<typeid(int).name()<< std::endl; std::cout <<typeid(std::string).name()<< std::endl;
在这里插入图片描述
  • 这就是它们底层的类型表现形式!

解码操作:

在这里插入图片描述

  • 可以成功看到对应类型。

RTTI 的开销与争议

优点:
  • 实现安全的向下转型;
  • 支持运行时类型查询(调试、序列化、插件系统等场景有用)。
缺点:
  • 空间开销:每个含虚函数的类增加一个 type_info 对象;
  • 时间开销dynamic_cast遍历继承树,比 static_cast 慢;
  • 设计争议:频繁使用 RTTI 往往意味着面向对象设计不佳(应优先用虚函数实现多态,而非类型判断)。
许多高性能项目(如游戏引擎、嵌入式系统)会 禁用 RTTI(编译选项 -fno-rtti)以节省资源。

何时使用 RTTI?

场景是否推荐
安全向下转型(无法用虚函数替代)✅ 谨慎使用
调试/日志打印类型名✅ 可接受
序列化/反序列化框架✅ 合理
日常业务逻辑中频繁类型判断❌ 应重构为多态设计

禁用 RTTI操作

在 GCC/Clang 中:

g++ -fno-rtti main.cpp 

此时:

  • typeiddynamic_cast无法使用,编译报错;
  • 适合对性能/体积敏感的场景。

为什么非多态类型不支持 RTTI?

  • 非多态类 没有虚函数
  • 编译器 不会为其生成 vtable;
  • 对象内存中 没有 vptr;

因此,运行时无法获取其实际类型信息。

总结

特性说明
作用运行时获取类型信息、安全类型转换
前提类必须是多态类型(有虚函数)
核心工具typeiddynamic_caststd::type_info
性能有运行时开销,非零成本
最佳实践优先用虚函数,RTTI 仅作兜底方案
所以说 善用多态,少用类型判断,才是面向对象的精髓。

本篇小结

RTTI 是 C++ 实现运行时类型识别的关键机制,依赖虚函数表与 type_info,在多态类型中支持安全转型和类型查询,但存在性能开销,应优先通过虚函数实现多态,谨慎使用 RTTI。

Read more

AI革命先锋:DeepSeek与蓝耘通义万相2.1的无缝融合引领行业智能化变革

AI革命先锋:DeepSeek与蓝耘通义万相2.1的无缝融合引领行业智能化变革

云边有个稻草人-ZEEKLOG博客 目录 引言 一、什么是DeepSeek? 1.1 DeepSeek平台概述 1.2 DeepSeek的核心功能与技术 二、蓝耘通义万相2.1概述 2.1 蓝耘科技简介 2.2 蓝耘通义万相2.1的功能与优势 1. 全链条智能化解决方案 2. 强大的数据处理能力 3. 高效的模型训练与优化 4. 自动化推理与部署 5. 行业专用解决方案 三、蓝耘通义万相2.1与DeepSeek的对比分析 3.1 核心区别 3.2 结合使用的优势 四、蓝耘注册流程 五、DeepSeek与蓝耘通义万相2.1的集成应用 5.1 集成应用场景 1. 智能医疗诊断

By Ne0inhk
如何通过 3 个简单步骤在 Windows 上本地运行 DeepSeek

如何通过 3 个简单步骤在 Windows 上本地运行 DeepSeek

它是免费的——社区驱动的人工智能💪。         当 OpenAI 第一次推出定制 GPT 时,我就明白会有越来越多的人为人工智能做出贡献,并且迟早它会完全由社区驱动。         但从来没有想过它会如此接近😂让我们看看如何在 Windows 机器上完全免费使用第一个开源推理模型!  步骤 0:安装 Docker 桌面         我确信很多人已经安装了它,所以可以跳过,但如果没有 — — 这很简单,只需访问Docker 的官方网站,下载并运行安装 👍         如果您需要一些特定的设置,例如使用 WSL,那么有很多指导视频,请查看!我将继续下一步。 步骤 1:安装 CUDA 以获得 GPU 支持         如果您想使用 Nvidia 显卡运行 LLM,则必须安装 CUDA 驱动程序。(嗯……是的,它们需要大量的计算能力)         打开CUDA 下载页面,

By Ne0inhk
在 VSCode 中本地运行 DeepSeek,打造强大的私人 AI

在 VSCode 中本地运行 DeepSeek,打造强大的私人 AI

本文将分步向您展示如何在本地安装和运行 DeepSeek、使用 CodeGPT 对其进行配置以及开始利用 AI 来增强您的软件开发工作流程,所有这些都无需依赖基于云的服务。  步骤 1:在 VSCode 中安装 Ollama 和 CodeGPT         要在本地运行 DeepSeek,我们首先需要安装Ollama,它允许我们在我们的机器上运行 LLM,以及CodeGPT,它是集成这些模型以提供编码辅助的 VSCode 扩展。 安装 Ollama Ollama 是一个轻量级平台,可以轻松运行本地 LLM。 下载Ollama 访问官方网站:https://ollama.com * 下载适合您的操作系统(Windows、macOS 或 Linux)的安装程序。 * 验证安装 安装后,打开终端并运行: ollama --version  如果 Ollama 安装正确,

By Ne0inhk
DeepSeek-R1是真码农福音?我们问了100位开发者……

DeepSeek-R1是真码农福音?我们问了100位开发者……

从GitHub Copilot到DeepSeek-R1,AI编程工具正在引发一场"效率革命",开发者们对这些工具的期待与质疑并存。据Gartner预测,到2028年,将有75%的企业软件工程师使用AI代码助手。 眼看着今年国产选手DeepSeek-R1凭借“深度思考”能力杀入战场,它究竟是真码农福音还是需要打补丁的"潜力股"? ZEEKLOG问卷调研了社区内来自全栈开发、算法工程师、数据工程师、前端、后端等多个技术方向的100位开发者(截止到2月25日),聚焦DeepSeek-R1的代码生成效果、编写效率、语法支持、IDE集成、复杂代码处理等多个维度,一探DeepSeek-R1的开发提效能力。 代码生成效果:有成效但仍需提升 * 代码匹配比例差强人意 在代码生成与实际需求的匹配方面,大部分开发者(58人)遇到生成代码与实际需求完全匹配无需修改的比例在40%-70%区间,12人遇到代码匹配比例在70%-100%这样较高的区间。 然而,有30人代码匹配比例低于40%。这说明DeepSeek-R1在代码生成方面有一定效果,但在部分复杂或特定场景下,仍有很大的提升空间。

By Ne0inhk