C++原生不支持自定义注解,因[[attribute]]仅限标准/扩展属性,无元数据注册与运行时查询能力;模拟需宏+静态注册表+类型安全访问器。

为什么 C++ 原生不支持 [[attribute]] 之外的自定义注解
C++ 标准里的 [[attribute]] 是编译器识别的**语法标记**,仅限标准或扩展属性(如 [[nodiscard]]、[[maybe_unused]]),不能用于用户定义语义。你写 [[MyTag("value")]],除非编译器内置支持,否则直接报错:unknown attribute 'MyTag'。这不是限制反射,而是 C++ 的属性机制本身不提供元数据注册与运行时查询能力。
所以“模拟实现”本质是绕过语法层,用宏 + 类型特征 + 静态注册表,在编译期构造可查的元信息。
用宏 + 静态变量注册字段/类的元数据
核心思路:每个被“标注”的成员,通过宏展开为一个全局静态对象,在其构造函数中把类型名、字段名、标签值等写入一个全局注册表(比如 std::map<:string std::any> 或更轻量的 std::vector)。
-
REFLECTABLE宏包裹类声明,触发ReflectableTraits特化 -
REFLECT_FIELD(name, type, ...)宏在类内生成静态注册代码,例如:static const FieldRegistrar __reg_##name(typeid(T), #name, "serialize", true);
- 注册器构造函数将自身插入全局
g_field_registry,确保链接期存在 - 运行时可通过
get_field_meta("MyClass", "id")查到该字段是否带"serialize"标签
如何让 std::string_view 或 const char* 标签在编译期可用
如果想在宏里传字符串字面量并参与编译期判断(比如做 if constexpr 分支),必须用 std::string_view 且保证它是字面量——C++20 起支持 constexpr std::string_view,但要注意:只有真正字面量才能进常量表达式上下文。
- ✅ 正确:
REFLECT_FIELD(id, int, "json_key"sv)(C++20,"json_key"sv是constexpr) - ❌ 错误:
auto key = "json_key"sv; REFLECT_FIELD(id, int, key)(运行时变量,无法用于模板参数) - 若需多标签组合,建议用位掩码枚举(如
Serialize | Json | Db),比字符串匹配更快更类型安全
运行时反射调用字段前必须解决类型擦除问题
注册表存的是字段偏移、类型 ID 和标签,但读写字段需要知道具体类型。不能只靠 void* + 偏移——那会丢失类型信息,强制 reinterpret_cast 极易出错。
推荐做法是为每种类型生成专用访问器:
- 宏展开时,对
int字段生成int& get_int_ref(void* obj)函数指针注册到元数据中 - 对
std::string字段生成std::string& get_string_ref(void* obj) - 调用时:先查元数据拿到访问器函数指针,再传入对象地址,安全取值
- 避免
std::any或std::variant带来的额外开销和动态分配
这套机制能跑通,但代价是编译时间增长、二进制体积略增;真正难的是跨模块注册顺序不可控——静态变量初始化顺序未定义,g_registry 可能在某些字段注册器构造前就被访问。得加 guard 或用局部静态变量延迟初始化。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











