可行但需规避三类坑:生命周期(禁用&typeid(T),须用std::type_index{typeid(T)})、类型修饰符(注册与查询需退修饰)、哈希器(旧编译器建议改用std::map)。

直接用 std::type_index 作异构容器(如 std::map 或 std::unordered_map)的键是可行的,但只要踩中生命周期、类型修饰符、哈希器这三类坑,find() 就会静默返回 end(),甚至触发未定义行为——不是代码写错了,而是语义理解偏差导致的“查不到”。
std::type_index{typeid(T)} 必须作用于具体表达式,禁用 &typeid(T)
错误根源在于 typeid 返回的是临时 std::type_info 对象,取其地址(如 &typeid(int))会导致悬垂指针。后续任何比较或哈希都可能崩溃或返回错误结果。
- 禁用写法:
factory[&typeid(MyClass)] = ...、std::type_index(typeid(T))(可能被解析为函数声明) - 正确写法:统一用花括号初始化,让
std::type_index自行持有并管理指针生命周期,例如:std::type_index{typeid(MyClass)}、std::type_index{typeid(*ptr)} - 模板内慎用
typeid(T):T 是类型名而非表达式,非法;应改用std::type_index{typeid(T{})}(要求可默认构造)或std::type_index{typeid(std::declval())}
注册与查询必须用退修饰后的值类型,cv 和引用不一致就查不到
std::type_index 忠实反映 typeid 的语义:int、const int、const int&、int* 全是不同键。工厂注册时用了 typeid(MyClass),调用方传 const MyClass& 却查 typeid(x),必然失败。
- 注册时一律用裸类型:例如
factory[std::type_index{typeid(ConcreteA)}] = []{...}; - 查询时对引用/const 参数,显式退修饰:
std::type_index{typeid(std::remove_cv_t<:remove_reference_t>)>},或更简洁地用模板参数std::type_index{typeid(T)}(当函数签名为template)void handle(const T& x) - 多态指针必须解引用:
typeid(ptr)得到的是Base*类型,typeid(*ptr)才能获得实际派生类类型
std::unordered_map<:type_index t> 编译失败?优先换 std::map
标准虽从 C++11 起要求特化 std::hash<:type_index>,但 GCC 4.8、MSVC 2013 等旧环境仍可能缺失,报错 no matching function for call to ‘hash’。这不是你漏头文件,是实现未就绪。
- 最稳妥做法:改用
std::map<:type_index t>—— 它依赖operator,而std::type_index已完整支持,无需额外配置 - 若坚持用
std::unordered_map,显式传哈希器:std::unordered_map<:type_index t std::hash>>;不要自己封装调ti.hash_code(),那是底层依据,重复实现无意义且易错 - 验证是否可用:
static_assert(std::is_default_constructible_v<:hash>>);,不通过就必须手动传
RTTI 必须开启,且 hash_code() 值不可跨进程稳定
关掉 RTTI(如 GCC 加 -fno-rtti)后,typeid 行为未定义,std::type_index 构造看似成功,但比较结果不可靠——不会编译报错,只会静默出错。
- 确认开启 RTTI:
-frtti(GCC/Clang 默认开启),MSVC 确保未启用/GR- -
hash_code()只保证单次运行内桶分布一致,**不能用于日志、数据库存储、网络传输或跨进程比对**;真正可靠的判断永远是==比较 - 别用
typeid(T).name()当键:字符串由编译器生成,GCC 输出"i",MSVC 可能是"int",无语义、不可比、不跨平台
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











