纸上谈“型”不如运行识“真”:深入 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

深挖 DeepSeek 隐藏玩法·智能炼金术2.0版本

深挖 DeepSeek 隐藏玩法·智能炼金术2.0版本

前引:屏幕前的你还在AI智能搜索框这样搜索吗?“这道题怎么写”“苹果为什么红”“怎么不被发现翘课” ,。看到此篇文章的小伙伴们!请准备好你的思维魔杖,开启【霍格沃茨模式】,看我如何更新秘密的【知识炼金术】,我们一起来解锁更加刺激的剧情!友情提醒:《《《前方高能》》》 目录 在哪使用DeepSeek 如何对提需求  隐藏玩法总结 几个高阶提示词 职场打工人 自媒体创作 电商实战 程序员开挂 非适用场地 “服务器繁忙”如何解决 (1)硅基流动平台 (2)Chatbox + API集成方案 (3)各大云平台 搭建个人知识库 前置准备 下载安装AnythingLLM 选择DeepSeek作为AI提供商 创作工作区 导入文档 编辑  编辑 小编寄语 ——————————————————————————————————————————— 在哪使用DeepSeek 我们解锁剧情前,肯定要知道在哪用DeepSeek!咯,为了照顾一些萌新朋友,它的下载方式我放在下面了,拿走不谢!  (1)

By Ne0inhk
【AI大模型】DeepSeek + 通义万相高效制作AI视频实战详解

【AI大模型】DeepSeek + 通义万相高效制作AI视频实战详解

目录 一、前言 二、AI视频概述 2.1 什么是AI视频 2.2 AI视频核心特点 2.3 AI视频应用场景 三、通义万相介绍 3.1 通义万相概述 3.1.1 什么是通义万相 3.2 通义万相核心特点 3.3 通义万相技术特点 3.4 通义万相应用场景 四、DeepSeek + 通义万相制作AI视频流程 4.1 DeepSeek + 通义万相制作视频优势 4.1.1 DeepSeek 优势 4.1.2 通义万相视频生成优势 4.2

By Ne0inhk
【DeepSeek微调实践】DeepSeek-R1大模型基于MS-Swift框架部署/推理/微调实践大全

【DeepSeek微调实践】DeepSeek-R1大模型基于MS-Swift框架部署/推理/微调实践大全

系列篇章💥 No.文章01【DeepSeek应用实践】DeepSeek接入Word、WPS方法详解:无需代码,轻松实现智能办公助手功能02【DeepSeek应用实践】通义灵码 + DeepSeek:AI 编程助手的实战指南03【DeepSeek应用实践】Cline集成DeepSeek:开源AI编程助手,终端与Web开发的超强助力04【DeepSeek开发入门】DeepSeek API 开发初体验05【DeepSeek开发入门】DeepSeek API高级开发指南(推理与多轮对话机器人实践)06【DeepSeek开发入门】Function Calling 函数功能应用实战指南07【DeepSeek部署实战】DeepSeek-R1-Distill-Qwen-7B:本地部署与API服务快速上手08【DeepSeek部署实战】DeepSeek-R1-Distill-Qwen-7B:Web聊天机器人部署指南09【DeepSeek部署实战】DeepSeek-R1-Distill-Qwen-7B:基于vLLM 搭建高性能推理服务器10【DeepSeek部署实战】基于Ollama快速部署Dee

By Ne0inhk

DeepSeek各版本说明与优缺点分析_deepseek各版本区别

DeepSeek各版本说明与优缺点分析 DeepSeek是最近人工智能领域备受瞩目的一个语言模型系列,其在不同版本的发布过程中,逐步加强了对多种任务的处理能力。本文将详细介绍DeepSeek的各版本,从版本的发布时间、特点、优势以及不足之处,为广大AI技术爱好者和开发者提供一份参考指南。 1. DeepSeek-V1:起步与编码强劲 DeepSeek-V1是DeepSeek的起步版本,这里不过多赘述,主要分析它的优缺点。 发布时间: 2024年1月 特点: DeepSeek-V1是DeepSeek系列的首个版本,预训练于2TB的标记数据,主打自然语言处理和编码任务。它支持多种编程语言,具有强大的编码能力,适合程序开发人员和技术研究人员使用。 优势: * 强大编码能力:支持多种编程语言,能够理解和生成代码,适合开发者进行自动化代码生成与调试。 * 高上下文窗口:支持高达128K标记的上下文窗口,能够处理较为复杂的文本理解和生成任务。 缺点: * 多模态能力有限:该版本主要集中在文本处理上,缺少对图像、语音等多模态任务的支持。 * 推理能力较弱:尽管在自然语言

By Ne0inhk