C++中的自省(Introspection)是什么?(如何在运行时获取类名)

7次阅读

typeid返回的是编译器修饰后的名字,非源码类名;稳定获取类名需用宏字符串化,如declare_class_name(widget)生成Static constexpr const char* class_name() { return “widget”; }。

C++中的自省(Introspection)是什么?(如何在运行时获取类名)

运行时拿不到真正的类名,typeid 返回的是编译器修饰后的名字

你写 typeid(MyClass).name(),得到的大概率不是 "MyClass",而是类似 "N5mylib7MyClassE" 这种符号名——这是 ABI(比如 Itanium c++ ABI)规定的 mangling 结果,不同编译器、不同平台输出完全不同。它不是设计来给人读的,也不保证稳定。

  • MSVC 可能返回 "class mylib::MyClass",看着像人话,但仍是实现定义,不能依赖
  • Clang/GCC 默认返回 mangled 名,需调用 abi::__cxa_demangle 才能转成可读形式,但该函数不处理失败、不跨平台、且需要手动管理内存
  • typeid 对带模板参数的类型(如 std::vector<int></int>)同样返回修饰名,解析后也未必符合直觉

想稳定拿到“源码里写的类名”,得自己加宏或字符串字面量

C++ 标准没提供反射机制,所以所有“获取类名”的可靠方案,本质都是在编译期把名字硬编码进去。最轻量、最可控的方式是用宏注入:

#define DECLARE_CLASS_NAME(cls)      static constexpr const char* class_name() { return #cls; } <p>struct Widget { DECLARE_CLASS_NAME(Widget) };
  • 调用 Widget::class_name() 永远返回 "Widget",不依赖 RTTI,无运行时开销
  • 宏里用 #cls 是预处理字符串化,完全在编译期完成,和 typeid 无关
  • 如果类在头文件中定义,宏必须也在头文件里展开,否则链接时报 undefined reference

std::source_location 不能替代类名,但能补位调试场景

如果你真正想要的不是“类名”,而是“这个对象是在哪声明/构造的”,std::source_location::current() 更实用——它抓的是调用点位置,不是类型本身:

void log_creation(std::source_location loc = std::source_location::current()) {     std::cout << loc.file_name() << ":" << loc.line() << "n"; } // Widget w; → log_creation() 输出的是 Widget w; 那一行的位置
  • 它不解决“我是谁”,只回答“我在哪被造出来”,对日志、诊断有用,但不能用于类型分发或元编程
  • 注意:GCC 12+、Clang 14+、MSVC 2022 17.5+ 才完整支持;老版本可能返回空字符串或固定值
  • 别把它和 typeid 混用——两者粒度不同,一个指向代码位置,一个指向类型对象

第三方库如 Boost.TypeIndex 只是封装typeid,没绕过根本限制

boost::typeindex::type_id<t>().pretty_name()</t> 看起来更友好,但它底层还是调 typeid(T).name() + 解析,只是帮你做了 demangle 和内存管理:

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

  • linux 上仍依赖 abi::__cxa_demanglewindows 上走 MSVC 的逻辑,行为差异照旧
  • 名字是否“漂亮”取决于编译器输出是否可解,比如模板嵌套过深时,demangle 后仍是乱码或截断
  • 引入 Boost 增加构建依赖,而多数项目只需要一个类名字符串,杀鸡用牛刀

C++ 没有语言级自省,所谓“运行时获取类名”本质上是个伪需求——你要的稳定标识,只能靠自己写死;你要的调试信息,source_location 或日志宏更直接;任何试图从 typeid 挖出干净名字的方案,都卡在 ABI 差异和标准未定义上。

text=ZqhQzanResources