C++中如何基于std::type_info实现轻量级的运行期插件类型检查?

5次阅读

最安全的运行期类型比较是 typeid(a) == typeid(b);插件跨模块需用 hash_code() 或字符串 id,禁用 name() strcmp 和 type_info 指针存储。

C++中如何基于std::type_info实现轻量级的运行期插件类型检查?

std::type_info::name() 返回的字符串不可靠,别直接 strcmp

它不保证跨编译器、跨构建一致,甚至同一编译器在不同优化等级下都可能返回不同字符串(比如 std::String 可能是 Ssstd::string)。用 strcmp 比对 typeid(T).name() 的结果,在 CI 或 Release 构建里大概率崩。

  • 真正安全的比较方式只有 typeid(A) == typeid(B) —— 这是标准保证的 O(1) 操作
  • 如果需要可读名用于日志,只在调试时调用 abi::__cxa_demangle(GCC/Clang),且必须检查返回值和内存分配失败
  • windows MSVC 用 __unDName,但同样不能用于逻辑分支判断

插件注册时必须用 static_cast 而非 dynamic_cast 触发类型擦除

想让插件工厂返回统一接口指针(如 IPlugin*),又保留原始类型信息,常见错误是写成 dynamic_cast<iplugin>(new MyPlugin)</iplugin> —— 这会强制要求 IPlugin虚函数表,且运行期开销大,还依赖 RTTI 全局开启。

  • 正确做法:插件基类 IPlugin 不需要虚函数;注册时用 static_cast<void>(new MyPlugin)</void> 存原始指针,同时缓存 &typeid(MyPlugin)
  • 调用方取回后,先比对 stored_typeid == typeid(T),再用 static_cast<t>(ptr)</t> 强转 —— 零虚函数开销,类型安全由程序员保证
  • 注意:typeid 表达式必须作用于完整类型,不能是 void* 或未定义类型

std::type_info 对象本身不能跨 DLL/so 边界直接比较

在 Windows 动态库或 linux .so 中,即使两个模块编译自同一份头文件,typeid(MyPlugin) 在主程序和插件中产生的 std::type_info 实例地址不同,== 比较会返回 false。

  • 根本原因是各模块维护独立的 type_info 表,RTTI 信息不共享
  • 解决方案:插件导出一个唯一字符串 ID(如 "com.example.myplugin.v1"),注册时存为 std::string,比对用字符串而非 typeid
  • 若坚持用 typeid,需确保所有插件与主程序链接同一份 STL(如全静态链接 libstdc++ 或 libc++),但这在实践中极难管控

std::type_info 的生命周期很短,别保存指针

typeid(T) 返回的是临时对象,其地址在表达式结束后就失效。下面这段代码是未定义行为:

立即学习C++免费学习笔记(深入)”;

const std::type_info* ti = &typeid(MyPlugin); // 危险! register_plugin(ptr, ti); // ti 指向已销毁对象
  • 正确做法:用 std::type_info const& 接收并立即复制其内部状态?不行 —— std::type_info 禁止拷贝
  • 实际可行方案:调用 typeid(T).hash_code()(C++11 起),它返回 size_t,可安全存储和比较
  • 注意:hash_code() 不保证全局唯一,但同类型必相同,不同类型大概率不同;用于插件检查足够,无需强唯一性

hash_code 是目前最轻量、最跨平台、最不易踩坑的运行期类型标识手段。其他所有“取地址”“存 name 字符串”“跨模块比 typeid 对象”的做法,都在生产环境里栽过跟头。

text=ZqhQzanResources