typeid通过编译器静态写入.rodata段的type_info对象获取类型名,其name()返回mangled字符串,需demangle才可读;比较用地址而非字符串;对多态类型依赖vtable,非多态类型则纯编译期处理。

typeid 是怎么拿到类型名字的?
它不靠运行时解析符号表,也不靠反射元数据——C++ 没有内置反射。typeid 的结果来自编译器在生成目标文件时**静态写入的只读数据段(.rodata 或类似节)**,每个具名类型(包括类、枚举、甚至 int* 这样的复合类型)都对应一个全局唯一的 type_info 对象实例。
这个实例里最关键的成员是 __name(GCC/Clang 下)或类似命名的私有指针,指向一段以 null 结尾的字符串字面量,内容就是该类型的“mangled name”(例如 _ZTSNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE)。你调用 typeid(obj).name() 得到的,就是这个 raw 字符串——它不保证可读,也不跨编译器一致。
- 要看到可读名,得用
abi::__cxa_demangle(GCC/Clang)或UnDecorateSymbolName(MSVC)手动解码 -
type_info::operator==比较的是地址(同一类型的所有typeid返回引用指向同一个type_info实例),不是字符串内容比对 - 如果类型不含虚函数(即无 vtable),
typeid仍有效,但此时它纯靠编译期生成的静态信息,和对象内存布局无关
为什么 typeid(*ptr) 有时崩溃,而 typeid(ptr) 没事?
因为 typeid 对表达式求值的规则不同:typeid(ptr) 只看指针类型(Base*),不访问内存;而 typeid(*ptr) 要求 ptr 是有效的、指向完整对象的指针——否则触发未定义行为,常见于空指针、悬垂指针或指向未构造内存。
- 若
ptr是nullptr,typeid(*ptr)抛std::bad_typeid异常(仅当ptr是多态类型时;非多态类型直接 UB) - 多态类型 = 含虚函数的类,此时
typeid会通过 vtable 查找对应的type_info*(vtable 开头通常存有指向type_info的指针) - 非多态类型(如
struct A { int x; };)的typeid完全不碰对象内存,只依赖编译期类型信息
dynamic_cast 和 typeid 共享同一套 RTTI 数据吗?
是,但用法隔离。它们都依赖编译器为多态类生成的 RTTI 数据块,该数据块通常紧邻 vtable 存储,包含:type_info*、继承关系图、虚基类偏移等。但 dynamic_cast 用的是整个继承图做安全向下转型校验,而 typeid 只取其中的 type_info* 部分。
立即学习“C++免费学习笔记(深入)”;
- 关闭 RTTI(如 GCC 的
-fno-rtti)会让dynamic_cast编译失败(除非转成 void*),也会让typeid对多态类型失效 - 关闭后,非多态类型的
typeid仍可用——因为它根本没用到运行时数据 - RTTI 数据增加二进制体积,且每次
dynamic_cast或typeid都有轻微间接寻址开销(查 vtable → 查 type_info)
哪些情况会让 typeid 返回结果“不可靠”?
不是不可靠,而是语义受限:它返回的是“静态表达式类型”或“动态最派生类型”,但无法穿透某些边界。
- 对数组退化指针(
int arr[5]; typeid(arr).name()返回int [5];但int* p = arr; typeid(p).name()返回int*—— 丢掉了长度信息) - 对模板实例化类型,
typeid(T)中的T必须是具体类型;不能是未推导的auto或依赖型名称(SFINAE 场景下需谨慎) - 跨 DLL/so 边界时,若两个模块用不同编译器或不同 STL 实现,
type_info对象地址可能不等价,==判断失效(即使名字相同) - 使用
std::any或std::variant时,typeid能告诉你当前存储的类型,但无法告诉你std::any内部是否做了类型擦除重包装
真正容易被忽略的是:RTTI 不提供字段名、成员函数签名或继承链遍历能力——它只回答“这是哪个类型”,不回答“它长什么样”。想做序列化或调试打印,光靠 typeid 远不够。










