C++ 无法原生支持如 C# 的自定义 Attribute,因其标准语法无用户可读写属性块机制,C++20 的[[attribute]]仅限编译器使用;所有“反射”均为宏+静态注册表+模板元编程的编译期模拟,不具运行时动态性。

C++ 本身不支持运行时反射和注解(如 Java 的 @Override 或 C# 的 [Serializable]),所谓“自定义 Attribute”只能是编译期模拟或宏辅助的伪反射 —— 没有 RTTI 支持、不能动态查属性、不能绕过类型系统,所有“标签”行为都靠宏展开 + 静态注册表 + 模板元编程硬编码实现。
为什么不能像 C# 那样直接写 [MyAttr]?
C++ 标准语法里没有 attribute 块声明机制(C++20 的 [[attribute]] 是给编译器用的,不可自定义语义,也不能绑定到成员变量/函数上供程序读取)。所有“模拟”都必须把元信息转成可被代码访问的 C++ 实体:
- 用宏在类定义处插入静态成员(如
static const MyAttr attr_XXX;) - 用模板特化注册类型到全局映射表(如
attr_registry)::value - 用
constexpr字符串或整型常量“假装”是标签值,但无法携带任意结构化数据 - 无法在运行时对任意
obj.member反射出它有没有某个 tag —— 必须提前知道类型、成员名、标签名三者组合
最简可行方案:宏 + 静态注册表 + constexpr 字符串标签
适用于需要为少量关键类/字段打标记,并在初始化阶段统一扫描的场景(比如序列化配置、测试用例发现)。不依赖第三方库,纯 C++17:
#define REFLECTABLE(...) \
static constexpr const char* reflect_class_name = #__VA_ARGS__; \
static auto& get_reflection() { \
static auto reg = []{ \
reflection_registry::register_type<__va_args__>(); \
return true; \
}(); \
return reg; \
}
<h1>define ATTR_SERIALIZE \</h1>
<pre class="brush:php;toolbar:false;">static constexpr bool attr_serialize = true;
struct Person { std::string name; int age; ATTR_SERIALIZE REFLECTABLE(Person) };
关键点:
-
REFLECTABLE(Person)触发reflection_registry::register_type,该函数可将() Person类型指针、字段偏移、标签状态(如attr_serialize)存入全局std::map - 字段级标签(如“这个 int 字段要忽略序列化”)需额外宏,例如
ATTR_SKIP+ 字段声明宏REFLECT_FIELD(age, ATTR_SKIP),否则无法区分字段粒度 - 所有标签必须是
constexpr或静态常量,不能是运行时构造对象(C++ 不允许模板参数为非常量表达式)
常见崩溃点:宏展开顺序、ODR 违反、RTTI 关闭
这类模拟极易在多文件工程中出问题:
- 宏定义必须在所有使用它的头文件之前包含,否则
REFLECTABLE展开后找不到reflection_registry - 如果两个 .cpp 都包含同一
REFLECTABLE(MyClass),而register_type不加() inline或static修饰,会触发 ODR 违反(multiple definition) - 若编译时加了
-fno-rtti,typeid(T).name()不可用,必须改用__PRETTY_FUNCTION__解析或手动字符串表 - 模板注册函数若未显式实例化(
template reflection_registry::register_type),链接时可能找不到符号();
真正需要反射时,别硬刚 —— 用工具生成代码
手写宏反射维护成本高、调试困难、IDE 支持差。实际项目中更可靠的做法是:
- 用 Python / Clang LibTooling 扫描头文件,提取类/字段/注释,生成
MyClass_reflection.cpp(含字段名数组、类型 ID 映射、标签布尔值) - 用
magic_enum(C++17)替代手写枚举反射,但它只支持 enum,不解决 class 成员问题 - 接受限制:只对 POCO(Plain Old C++ Object)类做反射,且要求字段 public、无虚函数、无模板成员
- 放弃“运行时动态添加标签”的幻想 —— C++ 的类型系统在链接后就固化了,任何“动态”都是假的,只是把决策前移到了编译或加载时
最易被忽略的是:哪怕你实现了完美的宏注册表,只要没把字段偏移、名字字符串、类型 size 全部对齐,serialize(obj) 依然会读错内存。这不是语法问题,是 ABI 层面的硬约束。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











