C++反射需用宏+静态注册模拟,核心是标准布局类中通过offsetof获取字段偏移并配合static_cast安全取值,避免reinterpret_cast和RTTI;REFLECTABLE宏生成__reflect_fields()返回含name、offset、type_id、annotations的FieldInfo向量,注解用constexpr统一管理。

C++ 原生不支持运行时反射,所谓“实现反射机制”本质上是用宏 + 类型擦除 + 静态注册模拟出类似效果;直接硬上 std::any 或 std::variant 易踩类型安全与生命周期坑,必须靠编译期约束兜底。
用宏在类定义时注入 metadata 信息
不能依赖 RTTI 或动态字符串解析字段名——那会失去编译期检查且无法绑定到成员指针。正确做法是在类声明后立即用宏注册字段元数据,把字段名、类型、偏移量、注解字符串一次性固化。
-
REFLECTABLE宏需展开为私有静态函数(如__reflect_fields()),返回std::vector<fieldinfo></fieldinfo>,每个FieldInfo包含:name(const char*)、offset(size_t)、type_id(std::type_info&)、annotations(std::map<:string std::string></:string>) - 字段注解写成键值对形式,例如
[[json_name="user_id"]]→ 存入annotations["json_name"] = "user_id";宏里用offsetof计算偏移,**必须确保类是标准布局(standard-layout)**,否则offsetof行为未定义 - 避免在宏中调用非 trivial 构造函数(如
std::string),改用字面量字符串或constexpr字符数组,防止静态初始化顺序问题
解析注解时如何安全提取字段值
拿到 FieldInfo 后,不能直接 reinterpret_cast 取值——类型擦除后若原字段是 std::string 或含虚函数的子类,仅靠偏移读内存会崩溃。必须通过 std::visit 或特化函数指针分发。
- 推荐方案:每个
FieldInfo持有一个std::function<void void></void>,用于从对象内存拷贝字段值到目标缓冲区(如 JSON 序列化时写入rapidjson::Value) - 宏注册时,针对每种基础类型(
int、double、bool、const char*)生成对应 lambda,捕获offsetof偏移和类型信息;对于std::string等,lambda 内部做static_cast到具体类型再调用.c_str() - 若字段是 const 或 private,宏中用
friend声明反射辅助类,绕过访问控制——但注意友元不能跨模板实例化,需配合template<typename> friend class Reflector;</typename>
运行时解析失败的典型错误现象
最常遇到的是字段值全为零、字符串变乱码、或程序在 offsetof 处 SIGSEGV——这基本不是反射逻辑错,而是底层内存模型没对齐。
立即学习“C++免费学习笔记(深入)”;
-
error: offsetof within non-standard-layout type:类含虚函数、多重继承、或非 public 非 static 成员混合访问控制;解决:删虚函数,用策略类替代多态,所有反射字段设为public - JSON 序列化输出空字符串:字段类型是
std::string,但 lambda 中用了reinterpret_cast<const char>(ptr + offset)</const>直接读——实际应取static_cast<const std::string>(ptr + offset)->c_str()</const> - 注解键名拼写不一致(如宏里写
[[db_column]],解析时查"db_column_name"):建议统一用constexpr std::string_view常量定义注解名,避免字符串字面量散落各处
真正难的不是怎么注册字段,而是怎么让编译器相信你传进去的 void* 真是指向那个类的实例——所有类型转换都得靠 static_cast 和 offsetof 联动验证,漏掉一个 static_assert(std::is_standard_layout_v<t>)</t>,后续就全是未定义行为。


















