Files
Structive/docs/CORE_GUIDE.zh-CN.md
T
2026-08-07 17:15:02 +08:00

16 KiB
Raw Blame History

Structive Property Core 完整指南

English

1. Include 与 CMake Target

完整 Core 接口:

#include <structive/property/property.hpp>

CMake

target_link_libraries(my_target PRIVATE structive::property_core)

Core 是 header-only,要求 C++20。

2. 定义 Managed Object

通常让业务对象继承 Property_Object<Derived>

using namespace structive;
struct Device : Property_Object<Device> {
    double temperature{25.0};
    double pressure{101.3};
    double min_speed{10.0};
    double max_speed{100.0};
    std::string name{"device-1"};
};

这些成员仍然都是普通 C++ 成员。

3. 定义 Type Descriptor

特化 Type_Descriptor<T>,通过 object<T>(...) 返回 Object_Schema

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>
            ),
            synchronization(
                sync_all_independent,
                sync_group<&Device::min_speed, &Device::max_speed>("speed_range")
            ),
            field<&Device::temperature>(
                key<"temperature">,
                unit<"C">,
                min_value<-50.0>,
                max_value<200.0>
            ),
            field<&Device::pressure>(key<"pressure">, unit<"kPa">),
            field<&Device::min_speed>(key<"min_speed">),
            field<&Device::max_speed>(key<"max_speed">),
            field<&Device::name>(key<"name">)
        );
    }
};

field<Member>(...)property<Member>(...) 的别名,产生 member-backed Property_Descriptor

4. Schema 提供的静态保证

Schema 会检查:

  • 每个 property 都必须拥有非空 key
  • 同一 Schema 内 key 唯一;
  • 同一个成员存储不能重复注册;
  • single-valued Attribute category 不能在同一声明中重复;
  • constraint 必须能作用于 property value type
  • defaults(...) 只能放 inheritable Attribute
  • External/Persistence capability 不能声明 accessor 实际不支持的读写能力。

因此大量结构错误会直接成为编译期错误。

5. 获取 Schema

类型级:

const auto& schema = type_descriptor<Device>();

实例级:

Device device;
const auto& schema = device.schema();

可以通过编译期 index 或成员指针定位 property

const auto& first = schema.property<0>();
const auto& temperature = schema.property<&Device::temperature>();

业务 typed code 优先成员指针;index 主要用于泛型遍历。

6. Property Descriptor

获取 key

auto key_value = temperature.key();

静态能力:

using Property = std::remove_cvref_t<decltype(temperature)>;
static_assert(Property::readable);
static_assert(Property::writable);

按 category 查询 Attribute

static_assert(Property::has_attribute<Unit_Category>);
const auto& attribute = temperature.attribute<Unit_Category>();

遍历全部声明 Attribute

temperature.for_each_attribute([](const auto& attribute) {
    // 根据 attribute 类型处理
});

单独遍历 constraint

temperature.for_each_constraint([](const auto& constraint_value) {
    // 检查 constraint
});

7. Object Defaults 与 Effective Attribute

defaults(...) 给 inheritable Attribute 提供对象级默认值:

defaults(
    external_access<External_Access::read_write>,
    persistence_access<Persistence_Access::load_store>,
    sensitive<false>
)

Property 可以覆盖:

field<&Device::name>(
    key<"name">,
    external_access<External_Access::read>
)

Core 提供 effective_external_access_vexternal_readable_vexternal_writable_vpersistence_loadable_vpersistence_storable_veffective_sensitive_v 等有效属性计算入口。

Extension 自己的 inheritable category 可以通过 declared_effective_attribute<Index, Category>(schema) 读取 property 声明或 object default 中的有效值。

8. Core Attribute

8.1 Key

key<"temperature">

每个 property 必须存在,不能为空,同一 Schema 内唯一。

8.2 External Access

external_access<External_Access::none>
external_access<External_Access::read>
external_access<External_Access::write>
external_access<External_Access::read_write>

支持在 defaults(...) 中继承。

8.3 Persistence Access

persistence_access<Persistence_Access::none>
persistence_access<Persistence_Access::load>
persistence_access<Persistence_Access::store>
persistence_access<Persistence_Access::load_store>

同样支持继承。

8.4 Unit

unit<"C">

描述元数据,不支持 object default 继承。

8.5 Sensitive

sensitive<>
sensitive<false>

支持继承。Core 会计算 effective value,但不会自动执行脱敏或隐藏输出。

9. Constraint 与 Validation

内置 constraint

min_value<0>
max_value<100>
finite

自定义 constraint

constraint<"even">([](int value) {
    return value % 2 == 0;
})

按成员指针验证:

auto error = validate_property_value<&Device::temperature>(device.schema(), candidate);

按编译期 key 验证:

auto error = validate_property_key_value<"temperature">(device.schema(), candidate);

失败结果:

struct Validation_Error {
    std::string_view property_key;
    std::string_view code;
};

Validation 是显式操作,不会在 managed write 中自动执行。

10. Intrinsic Managed Read/Write

成员指针形式:

auto temperature = device.read<&Device::temperature>();
device.write<&Device::temperature>(30.0);

编译期 key 形式:

auto temperature = device.read_key<"temperature">();
device.write_key<"temperature">(30.0);

这些操作直接使用 Property 的固有能力,不再存在单独的 internal capability mode;可读、可写能力由 accessor 本身决定。

read() 返回值对象而不是底层存储引用。启用同步时,读取发生在配置的 shared lock 持有期间。

11. External Capability View

可以通过 object defaults 默认开放 external access

defaults(external_access<External_Access::read_write>)

使用:

device.external().write<&Device::temperature>(30.0);
auto value = device.external().read<&Device::temperature>();

Const object 返回 const capability view,因此没有 write API。

Typed API 对静态不可访问操作直接在编译期拒绝。

12. Persistence Capability View

Persistence 使用 load/store 语义:

device.persistence().load<&Device::temperature>(30.0);
auto value = device.persistence().store<&Device::temperature>();

编译期 key 版本:

device.persistence().load_key<"temperature">(30.0);
auto value = device.persistence().store_key<"temperature">();

能力由 Persistence_Access 元数据决定。

13. Synchronization Plan

13.1 Independent

默认 Synchronization_Plan 就是 independent:每个同步 property 拥有独立逻辑锁域。

显式写法:

synchronization(sync_all_independent)

13.2 Shared

synchronization(sync_all_shared)

除显式 override 外,全部 property 共用一个 lock slot。

13.3 Unsynchronized

synchronization(sync_all_unsynchronized)

property 使用 unsynchronized_slotStructive 不提供真实 mutex 保护。

13.4 单 Property Override

成员指针形式:

sync_independent<&Device::temperature>()
sync_unsynchronized<&Device::immutable_id>()

运行时字符串形式:

sync_independent("temperature")
sync_unsynchronized("immutable_id")

静态已知 property 优先成员指针形式。

13.5 Group

成员指针形式:

sync_group<&Device::min_speed, &Device::max_speed>("speed_range")

字符串形式:

sync_group("speed_range", "min_speed", "max_speed")

同一 group 的 property 共用 lock slot。

Resolver 会拒绝未知 key、重复 group name、空 group,以及同一 property 被重复显式配置。

14. 每实例覆盖同步策略

Type Descriptor 提供默认同步计划,但单个实例可以通过 Property_Synchronization 覆盖:

struct Device : Property_Object<Device> {
    Device() = default;
    explicit Device(Property_Synchronization synchronization) : Property_Object(std::move(synchronization)) {}
};
Device shared_device{
    property_synchronization(synchronization(sync_all_shared))
};

如果使用成员指针同步规则,可以显式提供对象类型完成 Schema materialization

auto policy = property_synchronization<Device>(
    synchronization(
        sync_all_independent,
        sync_group<&Device::temperature, &Device::pressure>("environment")
    )
);

Schema 本身不变,只有这个实例使用覆盖后的 lock topology。默认实例按类型共享一份解析结果,因此不会为每个对象重复解析默认同步计划。

15. 查看解析后的同步拓扑

单字段:

auto slot = device.lock_slot<&Device::temperature>();

完整 view

auto resolved = device.resolved_synchronization();
auto count = resolved.lock_count;
auto slot = resolved.slot(0);
auto uses_lock = resolved.uses_lock(0);

Resolved_Synchronization_View::unsynchronized_slot 表示无同步 property。

16. 静态多属性 Guard

Shared guard

auto guard = device.lock_shared<&Device::temperature, &Device::pressure>();
auto temperature = guard.get<&Device::temperature>();
auto pressure = guard.get<&Device::pressure>();

Unique guard

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);
guard.set<&Device::max_speed>(120.0);

Guard 只允许访问自己持有同步集合中的 property。

Capability view 也提供受其 access mode 限制的静态 guard

auto guard = device.external().lock_shared<&Device::temperature>();

17. 动态 Key Guard

运行时才知道属性集合时:

std::array<std::string_view, 2> keys{"temperature", "pressure"};
auto guard = device.lock_shared(keys);

或者:

auto guard = device.lock_unique({"min_speed", "max_speed"});

动态 guard 会在运行时检查 key 和 capability。获取锁前会对 lock slot 排序、去重。

Guard 获取以后仍然可以通过编译期成员或 key 读写,但访问未包含在 held set 中的 property 会抛出 std::logic_error

18. Traversal

Schema 遍历:

schema.for_each_property([](auto index, const auto& property) {
    // compile-time index + descriptor
});

Managed value 遍历:

device.for_each_readable([](auto index, const auto& descriptor, const auto& value) {
    // 每个 property 单独 managed read
});

Locked traversal 会先获取完整 readable 同步集合:

device.external().for_each_readable_locked([](auto index, const auto& descriptor, const auto& value) {
    // 全部选中 property 在 guard 下访问
});

集中 writable 操作:

device.with_all_writable_locked([](auto& guard) {
    // guard.get / guard.set
});

Persistence view 对应提供 with_all_loadable_locked(...)

19. Computed Property

同步 computed property 接收 read view

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 自身与通过 view 读取的每个 dependency 必须解析到同一个 lock slot。

例如:

synchronization(
    sync_all_independent,
    sync_group("speed", "min_speed", "max_speed", "speed_span")
)

Computed property 是只读属性。

20. Trusted Accessor Property

Core 还支持基于成员函数的 trusted access

trusted_computed_property<&Device::get_temperature>(key<"temperature">)

以及 getter/setter

trusted_accessor_property<&Device::get_temperature, &Device::set_temperature>(key<"temperature">)

它们直接调用对象成员函数,并标记为 trusted object access,不受 synchronized computed view 的 dependency slot 检查。只有当成员函数本身拥有明确同步/一致性契约时才应该使用。

21. Runtime Type-Erased Access

任何 Property_Object<T> 同时也是 Property_Object_Base

Property_Object_Base& erased = device;

Runtime introspection

erased.runtime_object_type();
erased.runtime_property_count();

Intrinsic runtime write 不需要 mode

double value = 35.0;
auto result = erased.runtime_write(
    "temperature",
    typeid(double),
    &value
);

当 adapter 需要遵守 external 或 persistence capability metadata 时,再显式选择边界投影:

auto external_result = erased.runtime_write(
    Managed_Access_Mode::external,
    "temperature",
    typeid(double),
    &value
);

Runtime read 使用同样的 overload 模型,并通过 callback 返回值:

static void read_double(void* context, std::size_t, std::string_view, const std::type_info& type, const void* value) {
    if (type == typeid(double)) {
        *static_cast<double*>(context) = *static_cast<const double*>(value);
    }
}
double output = 0.0;
auto result = erased.runtime_read(
    "temperature",
    &output,
    &read_double
);
auto external_result = erased.runtime_read(
    Managed_Access_Mode::external,
    "temperature",
    &output,
    &read_double
);

结果枚举:

Runtime_Access_Result::ok
Runtime_Access_Result::unknown_property
Runtime_Access_Result::not_readable
Runtime_Access_Result::not_writable
Runtime_Access_Result::type_mismatch

Intrinsic runtime overload 使用 accessor 的固有能力,并与 typed read()/write() 使用同一套 synchronization topology。带 Managed_Access_Mode 的 overload 会先应用 external 或 persistence 投影,再使用同样的同步规则。

22. Lock Policy

默认:

struct Device : Property_Object<Device, Shared_Mutex_Policy> {
};

等价简写:

struct Device : Property_Object<Device> {
};

也提供 no-op lock policy

struct Device : Property_Object<Device, No_Lock_Policy> {
};

No_Lock_Policy 使用 Null_Shared_Mutex,它取消真实互斥;只有外部所有权规则能够保证正确性时才应该使用。默认 managed-object 路径不保存 mutex 数组,也不会为同步状态产生每实例堆分配。显式使用自定义 Property_Synchronization 时仍可能分配一份紧凑覆盖 topology,因为 computed property 的同步域检查必须保留该实例选择的布局。

23. Raw Object Access

unsafe_object() 可以拿到底层 derived object

Device& raw = device.unsafe_object();

普通成员访问当然也仍然存在:

device.temperature = 40.0;

这些入口故意绕过 Structive managed access。适合调用方已经持有正确同步、或者明确在 Structive 管理契约之外操作的情况。

24. 推荐使用规则

  • Typed 业务访问优先 member pointer。
  • Runtime integration boundary 使用 string key。
  • Key 按协议标识对待。
  • 稳定的对象级 capability 放 defaults(...),特殊字段单独 override。
  • 需要一致观察的字段放同一个 synchronization group。
  • Validation 在业务操作边界显式执行。
  • 跨字段 invariant 使用静态 multi-property guard。
  • 默认优先 synchronized computed property。
  • Runtime access 用于 adapter,不要把普通 typed code 动态化。
  • Raw access 只在调用方明确拥有被绕过的管理保证时使用。