Files
Structive/README.zh-CN.md
T
2026-08-07 16:21:44 +08:00

328 lines
9.9 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) · [核心指南](docs/CORE_GUIDE.zh-CN.md) · [扩展体系](docs/EXTENSIONS.zh-CN.md)
## Structive 是什么
Structive 是一个 C++20 属性与结构描述系统。它刻意把“类型结构描述”和“对象实例管理”分成两层:
```text
C++ 原生对象模型
普通成员、成员函数、原生布局、直接访问
├── Type_Descriptor<T> → Object_Schema
│ 类型级结构、key、Attribute、约束、访问能力
└── Property_Object<T>
实例级受管理访问、同步、遍历、运行时访问
```
业务类型仍然可以保持非常普通:
```cpp
#include <structive/property/property.hpp>
using namespace structive;
struct Device : Property_Object<Device> {
double temperature{25.0};
double pressure{101.3};
};
template <>
struct structive::Type_Descriptor<Device> {
static auto get() {
return object<Device>(
defaults(
external_access<External_Access::read_write>,
persistence_access<Persistence_Access::load_store>
),
field<&Device::temperature>(
key<"temperature">,
unit<"C">,
min_value<-50.0>,
max_value<200.0>
),
field<&Device::pressure>(
key<"pressure">,
unit<"kPa">
)
);
}
};
```
成员仍然是真实的 C++ 成员。Structive 只是额外建立一层可以被泛型系统理解的结构语义。
## 核心思想
Structive 最核心的原则只有一句:
> **增强 struct,而不是替代 struct。**
因此它坚持:
- 注册后的字段仍然是普通 C++ 成员。
- 未注册成员完全不进入属性系统。
- 业务代码优先使用成员指针作为编译期属性身份。
- 字符串 `key` 主要服务运行时和适配器边界。
- 不要求把每个成员改造成 `Property<T>` 之类的包装类型。
- Validation、Synchronization、Persistence、Presentation 等语义相互分离。
这样,同一个业务结构体可以被 UI、持久化、序列化、RPC、脚本或工具系统理解,但业务类型本身不需要依赖这些系统。
## 项目分层
```text
core/
└── structive::property_core
├── INTERFACE target
├── Type_Descriptor<T> / Object_Schema
├── member / computed accessor
├── 统一 Attribute 协议
├── constraint 与显式 validation
├── access capability 元数据
├── synchronization plan 与 guard
├── typed / runtime access
└── traversal
extensions/
└── structive::property_extensions
├── STATIC target
├── 只依赖 property_core
├── 定义扩展自己拥有的 Attribute category
└── 当前实现 presentation 元数据解释
```
依赖方向必须保持单向:**Extension 依赖 CoreCore 永远不 include、不 link Extension。**
## 为什么不使用 `Property<T>` 成员
Structive 不要求这样定义对象:
```cpp
struct Device {
Property<double> temperature;
};
```
而是保留原生存储:
```cpp
struct Device : Property_Object<Device> {
double temperature;
};
```
然后通过 `Type_Descriptor<Device>` 单独声明结构语义。
这样可以保留真实成员指针、原生成员语义和必要时的直接访问能力。Structive 是增强层,不建立第二套替代 C++ 的对象模型。
## Schema 和 Managed Object 是两种概念
`Type_Descriptor<T>` 描述的是**类型**`Property_Object<T>` 管理的是**实例**。
Schema 保存注册属性列表、对象默认 Attribute 和默认同步计划。`Property_Object<T>` 在每个实例上解析同步计划,并持有自己的锁拓扑和 mutex 存储。
这个边界必须长期保持:结构描述属于类型级;同步状态属于实例级。
## 只有一套 Attribute 协议
Core Attribute 和 Extension Attribute 使用同一机制,不存在额外的 `hint` 通道。
例如一个单值 Attribute category 可以这样定义:
```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;
};
```
然后直接挂到属性上:
```cpp
field<&Device::temperature>(
key<"temperature">,
presentation::label<"Temperature">
)
```
Core 负责统一保存和遍历,但只解释自己拥有的 category。Presentation、JSON、RPC 等扩展应该自己解释自己的 category。
这是 Structive 最主要的扩展边界。
## Managed Access 不是强制封装
下面两种写法都合法,但语义不同:
```cpp
device.temperature = 30.0;
device.write<&Device::temperature>(30.0);
```
第一种是 **raw C++ path**,不会经过 Structive 的同步和 capability 管理。
第二种是 **managed path**,会定位注册属性并按该对象实例的同步计划执行。
Structive 有意保留两条路径。业务代码应该根据对象所有权和并发规则选择,而不是假装所有成员访问都能被框架强制拦截。
## 访问能力视图
Core 当前定义三种受管理访问模式:
- `internal`:应用内部正常受管理访问。
- `external`:由 `External_Access` 元数据控制。
- `persistence`:由 `Persistence_Access` 元数据控制。
```cpp
device.write<&Device::temperature>(30.0);
device.external().write<&Device::temperature>(31.0);
device.persistence().load<&Device::temperature>(32.0);
auto stored = device.persistence().store<&Device::temperature>();
```
Schema 会在编译期约束 capability 与 accessor 能力一致,例如只读 computed property 不能声明为可写。
## Validation 必须显式
Constraint 是结构元数据;`write()` **不会自动执行 constraint**
```cpp
auto error = validate_property_value<&Device::temperature>(device.schema(), 500.0);
if (error) {
// error->property_key
// error->code
}
```
这是设计选择,不是缺功能。单字段校验、跨字段不变量、事务和回滚是不同概念,不应该偷偷塞进一个通用 setter。
## Synchronization 明确且可组合
默认同步模式包括:
```cpp
sync_all_independent
sync_all_shared
sync_all_unsynchronized
```
也可以覆盖单个字段或建立同步组:
```cpp
synchronization(
sync_all_independent,
sync_group<&Device::min_speed, &Device::max_speed>("speed_range")
)
```
同一 group 的属性会解析到同一个 lock slot。多属性 Guard 会对 lock slot 去重,并按稳定顺序获取锁。
```cpp
auto guard = device.lock_unique<&Device::min_speed, &Device::max_speed>();
auto old_min = guard.get<&Device::min_speed>();
guard.set<&Device::min_speed>(20.0);
```
Synchronization 只负责同步,不自动等价于 validation、transaction、rollback 或事件系统。
## Computed Property
Structive 支持基于同步视图读取依赖项的 computed property
```cpp
computed_property<Device, double>([](const auto& view) {
return view.template get<&Device::max_speed>() - view.template get<&Device::min_speed>();
}, key<"speed_span">, external_access<External_Access::read>)
```
这种 computed property 的依赖字段必须和 computed property 本身落在同一个同步 slot,否则运行时会拒绝跨 slot 读取。同步计划因此显式表达了 computed value 的一致性边界。
Core 还提供 `trusted_computed_property``trusted_accessor_property` 作为高级入口。这类 accessor 直接通过受信任对象访问执行成员函数,不经过 synchronized view 的依赖检查,因此只应该在调用方明确掌握同步语义时使用。
## 运行时访问
`Property_Object_Base` 提供 type-erased runtime access
```cpp
Property_Object_Base& erased = device;
auto type = erased.runtime_object_type();
auto count = erased.runtime_property_count();
```
Runtime read/write 使用 property key、access mode 和 `std::type_info`,返回:
```text
ok
unknown_property
not_readable
not_writable
type_mismatch
```
它适合 JSON、HTTP/RPC、脚本桥接、动态工具等边界。普通编译期业务代码仍然应优先使用成员指针。
## Core 当前元数据
当前 Core 提供:
- `key<"...">`
- `external_access<...>`
- `persistence_access<...>`
- `unit<"...">`
- `sensitive<>`
- `min_value<...>`
- `max_value<...>`
- `finite`
- `constraint<"code">(...)`
其中 `external_access``persistence_access``sensitive` 支持继承,可以放入 `defaults(...)`。同 category 的 property 级 Attribute 会覆盖 object default。
## 当前 Presentation Extension
当前扩展层定义:
- `presentation::label<"...">`
- `presentation::description<"...">`
- `presentation::group<"...">`
- `presentation::order<N>`
`presentation::describe()` 解释这些 Attribute;未提供 label 时由 Presentation Extension 使用 property key 作为 fallback。这个 fallback 不属于 Core。
## 构建
当前 CMake 目标适用于作为子目录接入:
```cmake
add_subdirectory(path/to/Structive)
target_link_libraries(my_target PRIVATE structive::property_core)
```
使用链接型扩展:
```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
```
## 详细文档
- [设计理念与原则](docs/DESIGN.zh-CN.md)
- [Core 完整指南](docs/CORE_GUIDE.zh-CN.md)
- [Extension 架构指南](docs/EXTENSIONS.zh-CN.md)
- [English README](README.md)
- [Design Philosophy](docs/DESIGN.md)
- [Core Guide](docs/CORE_GUIDE.md)
- [Extension Architecture](docs/EXTENSIONS.md)