# Structive **Structive 在不替代 C++ 原生数据模型的前提下,为普通 struct 增加显式结构元数据和受管理属性行为。** [English](README.md) · [设计理念](docs/DESIGN.zh-CN.md) · [Core 指南](docs/CORE_GUIDE.zh-CN.md) · [Extension 指南](docs/EXTENSIONS.zh-CN.md) ## Structive 是什么 Structive 是一个 C++20 结构属性系统,明确分成两层: ```text C++ 对象模型 普通成员、成员函数和原生布局 │ ├── Type_Descriptor → Object_Schema │ 类型级结构、key、属性固有能力、Attribute、Constraint、同步描述 │ └── Property_Object 实例级 managed read/write、同步、遍历和 runtime access ``` 类型本身仍然是普通 C++: ```cpp #include using namespace structive; struct Device : Property_Object { double temperature{25.0}; int serial_number{1001}; }; template <> struct structive::Type_Descriptor { static auto get() { return object( field<&Device::temperature>(key<"temperature">, unit<"C">), field<&Device::serial_number>(key<"serial_number">, read_only) ); } }; ``` 成员仍是真实成员。Structive 只在它们旁边增加一层显式结构语义。 ## 核心思想 Structive 的第一原则: > **增强 struct,而不是替代 struct。** 因此: - 注册字段仍然是普通 C++ 成员; - 未注册成员完全不进入 Structive; - C++ 业务代码优先使用 member pointer 作为编译期身份; - string key 用于动态系统和 adapter 边界; - 不要求把字段替换成 `Property` 包装器; - 保留 raw C++ access; - Structive 不试图给 public member 建立安全边界; - 外部系统是否暴露、允许读还是允许写,由外部系统自己决定; - Core 只描述属性本身固有能做什么。 ## Core 完全不做访问控制 Structive 不再内建 `internal`、`external`、`persistence`、role、context 或 policy 访问模式。 Core 不暴露领域专用访问 View、权限枚举或持久化专用访问模式。 GUI、RPC、序列化器、插件系统、持久化系统都自己决定: ```text 我要不要暴露这个属性? 我要不要允许用户修改? 我要不要保存它? ``` Structive Core 只回答结构事实: ```text 这个属性自身能不能读? 这个属性自身能不能写? 它的 key 是什么? 它有哪些 Attribute 和 Constraint? managed access 是否需要同步? ``` ## 属性自身的 Intrinsic Capability 每个 Property 只有一套固有能力: ```text none read write read_write ``` 正常情况下由 Accessor 自动推导。普通可写成员天然是 `read_write`,getter-only computed property 天然是 `read`。 Schema 可以显式收窄能力: ```cpp field<&Device::serial_number>( key<"serial_number">, read_only ) ``` 预定义能力 Attribute: ```cpp read_only write_only read_write inaccessible ``` 这些是**属性自身契约**,不是用户权限。 只读属性可以: ```cpp auto id = device.read<&Device::serial_number>(); ``` 但: ```cpp device.write<&Device::serial_number>(1002); ``` 在编译期就不可用。 如果 C++ 成员本身是 public,raw path 仍然可以: ```cpp device.serial_number = 1002; ``` 这代表调用方主动绕过 Structive,同时也绕过 Structive 的同步保证。Structive 采用“君子不防小人”的协作模型,不把自己伪装成 C++ 内存保护机制。 ## Read-Only 必须带来真正的优化 Property metadata 不只是文档,而应该影响实现。 一个 intrinsic read-only 的**存储属性**不会进入同步拓扑: ```text read-only stored property ↓ 不分配 lock slot ↓ 不贡献 mutex ↓ managed read 不查询 slot ↓ 不构造 shared_lock ↓ 直接执行 accessor.read() ``` 例如: ```cpp struct Device : Property_Object { int id{1}; int value{0}; }; template <> struct structive::Type_Descriptor { static auto get() { return object( synchronization(sync_all_shared), field<&Device::id>(key<"id">, read_only), field<&Device::value>(key<"value">) ); } }; ``` 即使默认是 `sync_all_shared`,`id` 仍然固定解析成 `unsynchronized_slot`。只有 `value` 会为对象贡献 mutex。 前提是调用方遵守 managed contract。如果另一个线程直接写 `device.id`,那么它已经绕过 Structive,相关 data race 由调用方负责。 ## Computed Read-Only Property Computed Property 通常自身不可写,但它可能依赖可写字段: ```cpp computed_property(depends_on<&Device::min_speed, &Device::max_speed>, [](const auto& view) { return view.template get<&Device::max_speed>() - view.template get<&Device::min_speed>(); }, key<"speed_span">) ``` Computed value 本身没有可写存储。它的 synchronized view 保护的是**可写依赖的一致性域**。 - dependency 是 Schema 的显式结构事实,computed view 只能读取已声明的直接依赖; - read-only 存储依赖可以直接读取,不需要锁; - writable 依赖如果需要同一快照,只需要彼此处于同一个 synchronization group,computed property 的读取 slot 会从 dependency 自动推导; - computed property 不再直接加入 synchronization rule,它的同步语义由 dependency graph 决定; - dependency graph 必须是 DAG,Schema 形成时会在编译期拒绝 cycle; - `depends_on<>` 是合法的显式零依赖声明,此类 computed read 固定为 unsynchronized。 ## Managed Access 与 Raw Access 下面两句语义不同: ```cpp device.temperature = 30.0; device.write<&Device::temperature>(30.0); ``` 第一句是 raw C++ path,第二句是 Structive managed path。 Managed path 使用 Schema 描述的 intrinsic capability 和 synchronization。Raw path 完全绕过这些行为。 ## Schema 与 Managed Object 分层 `Type_Descriptor` 描述类型,`Property_Object` 给实例增加 managed behavior。 默认同步拓扑每个类型只解析并共享一次。实例不再保存默认的 per-property `vector`。实例只保存真正需要的 mutex storage;只有显式传入 `Property_Synchronization` 时才保存紧凑的实例级覆盖布局。 `No_Lock_Policy` 完全不保存真实 mutex。 ## 统一 Property Metadata 模型 Property Descriptor 只有一份 metadata storage,其中可以同时保存 Attribute 和 Constraint。Core 与 Extension 的描述性 metadata 共用 Attribute 协议,Constraint 保持自己的 validation 协议: ```cpp struct Label_Category {}; template struct Label_Attribute { using attribute_category = Label_Category; static constexpr bool single_valued = true; static constexpr bool inheritable = false; static constexpr auto value = Value; }; ``` Extension metadata 可以直接挂在 Property 上。`for_each_metadata()` 遍历全部 metadata,`for_each_attribute()` 只遍历 Attribute,`for_each_constraint()` 只遍历 Constraint: ```cpp field<&Device::temperature>( key<"temperature">, presentation::label<"Temperature"> ) ``` Core 负责保存和遍历,但不解释 Extension 自己拥有的 category。 ## Validation 必须显式 Constraint 是元数据,`write()` 不自动执行 validation: ```cpp auto error = validate_property_value<&Device::temperature>(device.schema(), 500.0); ``` Validation、transaction、rollback、synchronization 是不同问题,不隐藏在一个 setter 里。 ## Synchronization Synchronization 是 topology 层,不是访问控制。它只描述 intrinsically mutable Property 在 managed 并发访问时如何组成一致性域。Stored read-only Property 会在 lock slot 创建前被裁掉,因此属性元数据会直接转化为更低的运行时同步成本。 Structive 提供三种默认规则: ```cpp sync_all_independent sync_all_shared sync_all_unsynchronized ``` 以及 typed / runtime-key override 和 Group: ```cpp synchronization( sync_all_independent, sync_group<&Device::min_speed, &Device::max_speed>("speed_range") ) ``` 这些入口并不是重复设计:compile-time member rule 服务 typed C++,runtime-key Plan 服务动态 Adapter,per-instance override 服务少数确实需要特殊 topology 的对象,Guard 服务一次临时的多 Property 一致性操作。它们最终都解析为同一套紧凑 lock-slot 模型。 多 Property Guard 会对 lock domain 去重,并按稳定 slot 顺序获取锁: ```cpp auto guard = device.lock_unique<&Device::min_speed, &Device::max_speed>(); auto old_min = guard.get<&Device::min_speed>(); auto old_max = guard.get<"maximum_speed">(); guard.set<"minimum_speed">(20); guard.set<&Device::max_speed>(120); ``` Typed Guard 的 capability 在编译期通过 constraint 控制;runtime-key Guard 在运行期验证 key 和 capability。详细契约见 [Core Guide: Synchronization](docs/CORE_GUIDE.zh-CN.md#14-synchronization-plan),对应边界、阻塞和锁顺序测试见 `core/tests/synchronization_test.cpp`。 ## Runtime Access `Property_Object_Base` 故意保持为底层 type-erased Adapter 边界,面向只有运行期才知道 key 的代码: ```cpp Property_Object_Base& erased = device; auto type = erased.runtime_object_type(); auto count = erased.runtime_property_count(); ``` 动态操作只有: ```text runtime_read(key, context, callback) runtime_write(key, type_info, value) ``` 返回 `ok`、`unknown_property`、`not_readable`、`not_writable`、`unsupported_runtime_write` 或 `type_mismatch`。Runtime write 是精确类型的 copy-input 边界,不做隐式转换。如果 Accessor 只能接收 move-only 输入,Property 仍然可以保持 intrinsic `writable`,但会暴露 `runtime_copy_writable == false`;typed `write` 仍然支持这种 Property。Read callback 收到的是借用指针,只在 callback 期间有效;对于需要同步的 writable state,callback 执行期间 managed read lock 仍然持有。Stored read-only Property 继续走和 typed read 一样的 zero-lock fast path。 Core 不在这个边界强制引入 `variant`、`any`、转换注册表或 serialization 所有权策略,上层 Adapter 可以按领域需要封装。这里没有 runtime access mode,也没有访问控制;外部系统自行决定暴露策略,Structive 只报告 Property intrinsic capability。详细契约见 [Core Guide: Runtime Access](docs/CORE_GUIDE.zh-CN.md#22-runtime-type-erased-access),测试见 `core/tests/runtime_api_test.cpp`。 ## Core 当前元数据 Core 当前定义: - `key<"...">` - `read_only` - `write_only` - `read_write` - `inaccessible` - `unit<"...">` - `sensitive<>` - `min_value<...>` - `max_value<...>` - `finite` - `constraint<"code">(...)` 其中四种 capability 只描述 Property 自身,不承担访问控制职责。 ## 当前 Extension 元数据 Presentation Extension 定义: - `presentation::label<"...">` - `presentation::description<"...">` - `presentation::group<"...">` - `presentation::order` Presentation consumer 是否显示、是否允许编辑,由 consumer 自己决定,不属于 Property Core。 ## 构建 ```cmake add_subdirectory(path/to/Structive) target_link_libraries(my_target PRIVATE structive::property_core) ``` 使用 linked extension: ```cmake target_link_libraries(my_target PRIVATE structive::property_extensions) ``` 构建与测试: ```bash cmake -S . -B build -DBUILD_TESTING=ON cmake --build build ctest --test-dir build --output-on-failure ``` 独立构建提供 `STRUCTIVE_BUILD_EXAMPLES`、`STRUCTIVE_BUILD_TESTS`、`STRUCTIVE_INSTALL`。Example 只在 Structive 作为顶层工程时默认开启;测试跟随 `BUILD_TESTING`;独立安装默认开启。安装后可通过 `find_package(Structive CONFIG)` 使用 `structive::property_core` 与 `structive::property_extensions`,并且 standalone CTest 会真实验证外部 install consumer。 测试按契约分工,避免在一个大用例中重复覆盖: - `property_core_test.cpp`:Schema、Attribute、显式 Validation、typed access、traversal、computed property 与对象复制语义; - `runtime_api_test.cpp`:type-erased result code、callback metadata、copy-write 边界、managed blocking 与只读零锁; - `synchronization_test.cpp`:topology、非法 Plan、Guard held-set、阻塞关系与稳定锁顺序; - `compile_fail/`:重复 key/storage/单值 Attribute、缺失 key、capability/constraint/member 不匹配; - 独立公共头测试与 install consumer:保护 include 自足性和导出包边界。 ## 详细文档 - [设计理念与原则](docs/DESIGN.zh-CN.md) - [Core 完整指南](docs/CORE_GUIDE.zh-CN.md) - [Extension 架构](docs/EXTENSIONS.zh-CN.md) - [English](README.md) - [Design Philosophy](docs/DESIGN.md) - [Core Guide](docs/CORE_GUIDE.md) - [Extension Architecture](docs/EXTENSIONS.md)