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

9.9 KiB
Raw Blame History

Structive

Structive 的目标是在不替换 C++ 原生数据模型的前提下,为普通 struct 增加显式的结构描述、属性元数据和受管理访问能力。

English · 设计理念 · 核心指南 · 扩展体系

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 依赖 CoreCore 永远不 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_propertytrusted_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<...>
  • finite
  • constraint<"code">(...)

其中 external_accesspersistence_accesssensitive 支持继承,可以放入 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

详细文档