)
RTTI运行时类型识别一、RTTI 概述RTTIRun-Time Type Identification运行时类型识别是 C 中允许程序在运行时获取对象实际类型信息的机制。它主要通过两个操作符实现操作符作用typeid查询表达式的类型信息返回type_info对象dynamic_cast在类层次结构中进行安全的向下转换// RTTI 的核心作用classBase{virtual~Base(){}};classDerived:publicBase{};Base*pnewDerived();// 通过 p 只能看到 Base 的接口但 RTTI 可以知道实际类型是 Derivedcouttypeid(*p).name()endl;// 输出 DerivedDerived*ddynamic_castDerived*(p);// 安全转换二、typeid 操作符2.1 基本语法与用法#includetypeinfointmain(){int*ipnewint(10);intar[10];int(*par)[10]ar;// 指向数组的指针// typeid 返回 type_info 对象name() 返回类型名称couttypeid(ip).name()endl;// int* (具体输出取决于编译器)couttypeid(*ip).name()endl;// intcouttypeid(par).name()endl;// int(*)[10]couttypeid(*par).name()endl;// int[10]return0;}2.2 typeid 与 decltype 的区别特性typeiddecltype求值时机运行时多态类型编译时非多态类型编译时返回类型type_info对象类型本身用于声明是否计算表达式多态类型会计算非多态不计算不计算主要用途运行时类型识别编译时类型推导2.3 忽略顶层 const 和 volatileintmain(){constinta10;intb20;constint*paa;int*pbb;// 指针本身的类型pa 和 pb 类型不同const int* 与 int*cout(typeid(pa)typeid(pb))endl;// false// 指向的对象类型去除顶层 const 后相同int 与 intcout(typeid(*pa)typeid(*pb))endl;// true// const 变量本身constintc10;intd20;cout(typeid(c)typeid(d))endl;// trueconst 被忽略return0;}规则typeid会忽略表达式的顶层const和volatile限定符。2.4 多态类型与非多态类型classObject{intvalue;public:Object(intx0):value(x){}// 没有虚函数 → 非多态类型};classBase:publicObject{intnum;public:Base(intx0):num(x){}};intmain(){Base base;Object*opbase;// 非多态类型typeid 在编译时确定返回静态类型couttypeid(*op).name()endl;// Object不是 Basereturn0;}// 添加虚函数后 classObject{public:virtualvoidfun(){}// 有了虚函数 → 多态类型virtual~Object(){}};classBase:publicObject{public:// ...};intmain(){Base base;Object*opbase;// 多态类型typeid 在运行时确定返回动态类型couttypeid(*op).name()endl;// Base运行时确定return0;}关键区别typeid 对多态/非多态类型的处理 1. 非多态类型无虚函数 typeid(*op) 在编译时确定 → 总是返回编译时类型Object 即使 op 指向派生类对象 2. 多态类型有虚函数 typeid(*op) 在运行时确定 → 通过虚表查找实际类型 返回 op 实际指向的对象类型Base2.5 type_info 类typeid返回std::type_info类型的对象该类提供了类型信息的接口#includetypeinfoclasstype_info{public:// 比较类型是否相同booloperator(consttype_inforhs)constnoexcept;booloperator!(consttype_inforhs)constnoexcept;// 返回类型的可读名称编译器相关constchar*name()constnoexcept;// 返回类型在编译器中是否被排序用于 map 等容器size_thash_code()constnoexcept;// 返回是否在转换为目标类型前boolbefore(consttype_inforhs)constnoexcept;};使用示例intmain(){inta10;constintba;consttype_infoti1typeid(a);consttype_infoti2typeid(b);// 比较类型coutstd::boolalpha;cout(ti1ti2)endl;// true忽略引用和 const// 获取类型名称coutti1.name()endl;// int具体输出取决于编译器// 获取哈希值coutti1.hash_code()endl;return0;}2.6 对空指针解引用的处理intmain(){Object*opnullptr;// 安全对指针本身求 typeid指针类型是编译时已知couttypeid(op).name()endl;// Object*// 危险对空指针解引用try{couttypeid(*op).name()endl;// 抛出 std::bad_typeid 异常}catch(conststd::bad_typeide){cout异常: e.what()endl;}return0;}规则对空指针解引用使用typeid会抛出std::bad_typeid异常。三、dynamic_cast3.1 概述dynamic_cast是 C 中唯一一个运行时执行类型转换的运算符用于在类层次结构中进行安全的向下转换。// 语法dynamic_cast目标类型(表达式)// 特点// 1. 必须在运行时执行需要 RTTI 支持// 2. 必须有虚函数多态类型// 3. 必须是公有继承3.2 上行转换Derived → BaseclassObject{public:virtual~Object(){}virtualvoidfun(){coutObject::funendl;}};classBase:publicObject{public:voidfunc(){coutBase::funcendl;}};intmain(){Base base;// 上行转换static_cast 和 dynamic_cast 效果相同Object*p1static_castObject*(base);Object*p2dynamic_castObject*(base);Objectr1static_castObject(base);Objectr2dynamic_castObject(base);return0;}3.3 下行转换Base → Derived—— 指针版本intmain(){Base base;// 真正的 Base 对象Object obj;// 真正的 Object 对象Object*p1base;// 指向 Base 对象Object*p2obj;// 指向 Object 对象// 情况1实际类型匹配Base*bp1dynamic_castBase*(p1);// 成功返回有效指针coutbp1 bp1endl;// 情况2实际类型不匹配指针所指向对象的实际类型与要转换的目标类型不一致// dynamic_cast 会检查 p2 所指向对象的实际类型。因 p2 实际指向是 Object 类型的对象而转换的目标类型是 Base 类型// 实际类型与目标类型不匹配所以转换失败bp2 会被赋值为 nullptr。Base*bp2dynamic_castBase*(p2);// 失败返回 nullptrcoutbp2 bp2endl;// 输出 0// static_cast 不会检查返回无效指针危险// static_cast 是一种静态类型转换不会在运行时检查指针所指向对象的实际类型。// 即使 p2 实际指向的是 Object 类型的对象static_cast 也会直接进行转换返回一个 Base 类型的指针。// 但这个指针实际上指向的是 Object 类型的对象对其进行 Base 类型相关的操作会导致未定义行为是非常危险的。Base*bp3static_castBase*(p2);// 危险未定义行为return0;}3.4 下行转换Base → Derived—— 引用版本intmain(){Object obj;Base base;Objectr1base;// 引用 Base 对象Objectr2obj;// 引用 Object 对象// 情况1成功Basebr1dynamic_castBase(r1);// 成功// 情况2失败抛出 std::bad_cast 异常try{Basebr2dynamic_castBase(r2);// 抛出异常}catch(conststd::bad_caste){cout异常: e.what()endl;}return0;}3.5 dynamic_cast 行为总结转换类型目标类型失败时行为指针转换指针类型返回nullptr引用转换引用类型抛出std::bad_cast四、总结4.1 typeid 总结场景行为备注非多态类型表达式编译时确定类型返回静态类型多态类型表达式运行时确定类型通过虚表查找指针本身编译时确定不涉及解引用空指针解引用抛出std::bad_typeid需要 try-catch顶层 const/volatile被忽略不影响类型比较4.2 dynamic_cast 总结场景行为失败返回上行转换派生→基类总是成功—下行转换基类→派生类运行时检查指针nullptr引用bad_cast多态类型要求必须有虚函数否则编译错误访问权限要求必须是公有继承否则编译错误RTTI运行时类型识别内存布局一、RTTI 与虚表的关系RTTI 的实现与虚函数表vtable密切相关。当类中有虚函数时编译器会在虚表中添加RTTI 信息指针指向type_info对象。带 RTTI 信息的虚表结构 Object 虚表 Base 虚表 ----------------- ------------------ 偏移 内容 偏移 内容 -8 RTTI 指针 -8 RTTI 指针 0 fun()地址 0 fun()地址 8 ~Object() 8 ~Base() ↑ ↑ Object 对象 Base 对象 ----------------- ----------------- _vfptr _vfptr value value num RTTI 指针指向 type_info 对象只读数据段 type_info 对象包含 - 类型名称mangled name - 类型比较函数 - 类型哈希值二、有无虚函数时的内存对比2.1 无虚函数的类非多态类型classObject{private:intvalue;public:Object(intx0):value(x){}// 没有虚函数};classBase:publicObject{private:intnum;public:Base(intx0):num(x){}// 没有虚函数};内存布局无虚函数的类内存布局 Object 对象 Base 对象 ------------------ ------------------- value (4B) Object 部分value (4B) num (4B) 没有虚表指针 sizeof(Object) 4 没有虚表指针 sizeof(Base) 8 没有 RTTI 信息无法运行时识别实际类型2.2 有虚函数的类多态类型classObject{private:intvalue;public:Object(intx0):value(x){}virtualvoidfun(){}// 虚函数virtual~Object(){}// 虚析构};classBase:publicObject{private:intnum;public:Base(intx0):num(x){}voidfun()override{}~Base()override{}};内存布局三、typeid 的内存访问过程3.1 对多态类型使用 typeidintmain(){Base base;Object*opbase;// 对多态类型解引用typeid 需要访问虚表获取 RTTIconsttype_infotitypeid(*op);coutti.name()endl;// 输出 Basereturn0;}内存访问流程步骤 1. 通过 op 找到对象 2. 读取对象的 _vfptr 3. 从虚表偏移 -8 处读取 RTTI 指针 4. 通过 RTTI 指针访问 type_info 对象3.2 对非多态类型使用 typeidintmain(){Base base;Object*opbase;// 但 Object 没有虚函数// 非多态类型typeid 在编译时确定不访问虚表consttype_infotitypeid(*op);// 返回 Object 的类型信息coutti.name()endl;// 输出 Objectreturn0;}内存访问流程编译时就确定无运行时开销typeid 对非多态类型的访问过程 源代码typeid(*op) 编译器分析op 的类型是 Object*而 Object 没有虚函数 → 静态类型就是实际类型 → 编译时确定类型为 Object 编译结果直接替换为 Object 的 type_info 地址 不需要运行时查找 const type_info ti __type_info_for_Object; 优点无运行时开销 缺点无法识别派生类的实际类型四、两种类型的内存对比总结特性非多态类型无虚函数多态类型有虚函数对象内存只有数据成员数据成员 虚表指针_vfptr虚表没有有RTTI 信息没有有存储在虚表中typeid 求值时机编译时运行时typeid 是否访问虚表否是能否识别派生类型不能能通过虚表查找sizeof 大小较小较大多一个指针五、dynamic_cast 的内存访问过程intmain(){Circlecircle(5.0,1);Shape*shapecircle;// 向下转换Shape* → Circle*Circle*circlePtrdynamic_castCircle*(shape);return0;}内存访问流程在 RTTI 中type_info有以下作用类型判断供dynamic_cast比较对象实际类型与目标类型确定能否转换。信息提供通过name()函数返回类型名称助于调试与日志记录。类型比较重载和!操作符支持运行时类型比较。六、总结类型特性虚表RTTI 信息typeid 行为dynamic_cast无虚函数无无编译时确定不可用有虚函数有有运行时查表可用关键结论RTTI 通过虚表中的 RTTI 指针实现只有多态类型有虚函数的类才支持运行时类型识别typeid对多态类型解引用时会访问虚表有运行时开销dynamic_cast依赖 RTTI 信息进行安全检查