c++原生仅提供typeid,无字段/方法等类成员信息,因编译期擦除元数据且rtti仅支持多态类型;实用反射需宏+静态注册模拟,配合offsetof与std::any/variant实现安全读写。

为什么 C++ 原生没有 typeid 之外的类成员信息?
C++ 编译期擦除几乎所有类型元信息,typeid 只能给出类型名(且常为 mangled 名),不包含字段、方法、访问权限等。RTTI 仅支持多态类型的动态类型识别,对 struct 或无虚函数的 class 几乎无效。这意味着你不能靠标准库直接遍历一个类的所有成员变量名或自动序列化它们。
用宏 + 静态注册模拟反射:最轻量可行方案
核心思路是:在定义类时,用宏显式声明其“可反射字段”,并在编译期生成静态注册表。不依赖运行时解析,零额外依赖,兼容 C++11 起。
实操建议:
- 每个需反射的类定义一个
reflect()静态成员函数,内部调用宏(如REFLECT_FIELD(name, type))逐个注册字段名与偏移量 - 用
offsetof获取字段相对于class起始地址的字节偏移,配合std::type_info或字符串记录类型 - 注册结果存入全局
std::map<:string const classinfo></:string>,键为类名(手动传入字符串,避免typeid不稳定) - 注意:所有被反射字段必须是 public,且不能是 bit-field、引用、或含非 trivial 构造/析构的类型(否则
offsetof行为未定义)
示例片段:
立即学习“C++免费学习笔记(深入)”;
#define REFLECT_FIELD(name, type) \
fields.push_back({#name, offsetof(MyClass, name), typeid(type)});
struct MyClass {
int x;
float y;
static const ClassInfo& reflect() {
static ClassInfo info{"MyClass", []{
std::vector<FieldInfo> fields;
REFLECT_FIELD(x, int);
REFLECT_FIELD(y, float);
return fields;
}()};
return info;
}
};
std::any 和 std::variant 怎么配合反射做通用字段读写?
有了字段名和偏移量,还需安全地读写任意类型值。不能直接用 void* 强转——类型擦除和生命周期管理会出问题。
推荐做法:
- 读取字段时,根据
FieldInfo.type分支构造std::any:例如std::any{*(int*)((char*)obj + offset)} - 写入时,先用
std::any_cast检查类型是否匹配,再解引用赋值;不匹配则抛异常或返回 false - 若已知字段类型集合有限(如仅支持基本类型+
std::string),用std::variant<int float std::string></int>替代std::any,性能更高、无堆分配 - 切勿对 non-trivial 类型(如
std::vector)直接memcpy或裸指针解引用——必须调用构造/赋值操作符
为什么不要碰 Clang LibTooling 或 RTTI 解析 ELF?
有人尝试用 Clang AST 或解析二进制符号表提取类信息,这类方案在实践中几乎不可用:
- Clang 插件需完整重编译整个项目,无法用于第三方库或 release 构建产物
- ELF/DWARF 符号在 strip 后丢失,且不同编译器/平台格式不一致(MSVC 用 PDB,GCC 用 DWARF4/5)
- 即使有调试信息,字段顺序、padding、模板实例化名都难以稳定映射到源码语义
- 运行时加载 DWARF 解析器(如 libdwarf)体积大、依赖多、Windows 支持差
真正落地的 C++ 反射,99% 是宏驱动的显式注册。隐式、全自动、跨平台的方案目前不存在——这不是技巧问题,是语言设计的根本取舍。











