typeid(T).name()不可靠,因返回编译器特定mangled名,标准不保证可读、稳定或跨平台;推荐用__PRETTY_FUNCTION__配合宏提取类名,零开销且三编译器兼容。

在 C++ 中,typeid 确实能获取类型信息,但直接用它拿“类的名称字符串”有严重陷阱——typeid(T).name() 返回的是编译器内部的 mangled 名,不是可读的类名,且标准不保证可移植。
为什么 typeid(T).name() 不可靠
不同编译器对同一类名生成的 name() 结果完全不同:g++ 返回类似 N5MyLib7MyClassE 的 mangling 字符串,MSVC 可能返回 class MyLib::MyClass,而 clang 行为又略有差异。更关键的是,C++ 标准只要求 name() 返回“实现定义的字符串”,不承诺可读、不承诺稳定、甚至不承诺唯一。
- 即使加
abi::__cxa_demangle(g++)或UnDecorateSymbolName(MSVC),也只解决部分平台问题,无法跨平台 -
typeid对模板实例化类型、带 cv 限定符的类型返回名可能含空格或括号,解析困难 - 运行时开销虽小,但在 hot path 中频繁调用仍需留意
用宏 + __PRETTY_FUNCTION__ 提取类名(推荐)
这是目前最实用、跨编译器(g++/clang/MSVC 均支持)、零运行时开销的方案。原理是利用函数签名字符串中隐含的类名,再通过预处理器宏截取。
#define CLASS_NAME_STR(x) []{ \
constexpr const char* s = __PRETTY_FUNCTION__; \
constexpr size_t len = sizeof(__PRETTY_FUNCTION__) - 1; \
constexpr size_t start = /* 找到 'x' 在 s 中的位置逻辑 */; \
/* 实际项目中建议用更健壮的 constexpr 解析,或退而求其次用 __func__ + 类名硬编码 */ \
return "MyClass"; \
}()更现实的做法是定义一个简洁宏:
立即学习“C++免费学习笔记(深入)”;
#define GET_CLASS_NAME() \
([]{ \
static constexpr const char* s = __PRETTY_FUNCTION__; \
constexpr size_t offset = []{ \
constexpr size_t n = sizeof(__PRETTY_FUNCTION__) / sizeof(char); \
size_t i = n; \
while (i-- > 0 && s[i] != ':') {} \
return i > 0 ? i + 1 : 0; \
}(); \
constexpr size_t len = []{ \
constexpr size_t n = sizeof(__PRETTY_FUNCTION__) / sizeof(char); \
size_t i = offset; \
while (i < n && s[i] != ']' && s[i] != '(' && s[i] != ' ') ++i; \
return i - offset; \
}(); \
char buf[len + 1]{}; \
for (size_t i = 0; i < len; ++i) buf[i] = s[offset + i]; \
return buf; \
}())注意:上面是示意逻辑,真实项目中更常用简化版(牺牲一点通用性换可维护性):
#define CLASS_NAME_STR(cls) #cls
适用于已知具体类名的场景,如日志、断言、反射注册等。
运行时需要动态类名?考虑手动注册 + std::map
如果真要运行时根据对象指针/引用拿到可读类名(比如序列化、调试器集成),typeid 仍是起点,但必须配合 demangling 和缓存:
- Linux/g++:用
abi::__cxa_demangle处理typeid(obj).name(),结果缓存到static std::unordered_map避免重复解析 - Windows/MSVC:用
UnDecorateSymbolName,注意传入缓冲区大小和标志位UNDNAME_NAME_ONLY - 务必检查返回值是否成功,失败时 fallback 到
typeid(obj).name()原始字符串 - 避免在多线程高频路径中首次调用 demangle(可能触发锁),提前初始化缓存
真正需要类型反射?别只盯着 typeid
C++20 的 std::source_location 和宏可以辅助构建轻量反射;更严肃的需求应考虑:
- 第三方库如
magic_enum(枚举)或RTTR(运行时类型反射),它们通过宏注入元信息 - 自己用 CRTP 模式在基类中强制派生类提供
static constexpr const char* name() - 构建时代码生成(如基于 Clang AST 的工具)产出头文件,绕过运行时解析
硬啃 typeid 的 name() 是在和编译器实现细节较劲,多数时候属于过早优化或设计错位。











