9.9 KiB
Structive
Structive 的目标是在不替换 C++ 原生数据模型的前提下,为普通 struct 增加显式的结构描述、属性元数据和受管理访问能力。
Structive 是什么
Structive 是一个 C++20 属性与结构描述系统。它刻意把“类型结构描述”和“对象实例管理”分成两层:
C++ 原生对象模型
普通成员、成员函数、原生布局、直接访问
│
├── Type_Descriptor<T> → Object_Schema
│ 类型级结构、key、Attribute、约束、访问能力
│
└── Property_Object<T>
实例级受管理访问、同步、遍历、运行时访问
业务类型仍然可以保持非常普通:
#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、脚本或工具系统理解,但业务类型本身不需要依赖这些系统。
项目分层
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 依赖 Core;Core 永远不 include、不 link Extension。
为什么不使用 Property<T> 成员
Structive 不要求这样定义对象:
struct Device {
Property<double> temperature;
};
而是保留原生存储:
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 可以这样定义:
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;
};
然后直接挂到属性上:
field<&Device::temperature>(
key<"temperature">,
presentation::label<"Temperature">
)
Core 负责统一保存和遍历,但只解释自己拥有的 category。Presentation、JSON、RPC 等扩展应该自己解释自己的 category。
这是 Structive 最主要的扩展边界。
Managed Access 不是强制封装
下面两种写法都合法,但语义不同:
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元数据控制。
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。
auto error = validate_property_value<&Device::temperature>(device.schema(), 500.0);
if (error) {
// error->property_key
// error->code
}
这是设计选择,不是缺功能。单字段校验、跨字段不变量、事务和回滚是不同概念,不应该偷偷塞进一个通用 setter。
Synchronization 明确且可组合
默认同步模式包括:
sync_all_independent
sync_all_shared
sync_all_unsynchronized
也可以覆盖单个字段或建立同步组:
synchronization(
sync_all_independent,
sync_group<&Device::min_speed, &Device::max_speed>("speed_range")
)
同一 group 的属性会解析到同一个 lock slot。多属性 Guard 会对 lock slot 去重,并按稳定顺序获取锁。
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:
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:
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,返回:
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<...>finiteconstraint<"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 目标适用于作为子目录接入:
add_subdirectory(path/to/Structive)
target_link_libraries(my_target PRIVATE structive::property_core)
使用链接型扩展:
target_link_libraries(my_target PRIVATE structive::property_extensions)
仓库构建测试:
cmake -S . -B build -DBUILD_TESTING=ON
cmake --build build
ctest --test-dir build --output-on-failure