Files
Structive/README.zh-CN.md
T
2026-08-11 12:40:28 +08:00

385 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<T> → Object_Schema
│ 类型级结构、key、属性固有能力、Attribute、Constraint、同步描述
└── Property_Object<T>
实例级 managed read/write、同步、遍历和 runtime access
```
类型本身仍然是普通 C++
```cpp
#include <structive/property/property.hpp>
using namespace structive;
struct Device : Property_Object<Device> {
double temperature{25.0};
int serial_number{1001};
};
template <>
struct structive::Type_Descriptor<Device> {
static auto get() {
return object<Device>(
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<T>` 包装器;
- 保留 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++ 成员本身是 publicraw 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<Device> {
int id{1};
int value{0};
};
template <>
struct structive::Type_Descriptor<Device> {
static auto get() {
return object<Device>(
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<Device, int>(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 groupcomputed property 的读取 slot 会从 dependency 自动推导;
- computed property 不再直接加入 synchronization rule,它的同步语义由 dependency graph 决定;
- dependency graph 必须是 DAGSchema 形成时会在编译期拒绝 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<T>` 描述类型,`Property_Object<T>` 给实例增加 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 <Fixed_String Value>
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 服务动态 Adapterper-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 statecallback 执行期间 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<N>`
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)