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

运行时拿不到真正的类名,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_demangle,Windows 上走 MSVC 的逻辑,行为差异照旧 - 名字是否“漂亮”取决于编译器输出是否可解,比如模板嵌套过深时,demangle 后仍是乱码或截断
- 引入 Boost 增加构建依赖,而多数项目只需要一个类名字符串,杀鸡用牛刀
C++ 没有语言级自省,所谓“运行时获取类名”本质上是个伪需求——你要的稳定标识,只能靠自己写死;你要的调试信息,source_location 或日志宏更直接;任何试图从 typeid 挖出干净名字的方案,都卡在 ABI 差异和标准未定义上。










