去除所有访问级别

This commit is contained in:
2026-08-07 17:50:04 +08:00
parent 0c7fac70b2
commit 3205dd84e8
17 changed files with 2086 additions and 2424 deletions
+264 -305
View File
@@ -4,434 +4,393 @@
## 1. 设计目标
Structive 的目标是给普通 C++ 对象增加一层机器可理解的结构语义,同时不强迫业务对象改用框架自己的存储模型
Structive 的目标是在保留 C++ 原生对象模型的前提下,为普通 struct 增加显式结构语义
应该能够回答:
不是第二套语言、反射运行时、安全边界、ORM 或对象框架。它是一层尽可能贴近普通 C++ 的结构元数据和 managed property 能力。
- 哪些成员属于公开结构模型
- 某个属性稳定的 runtime key 是什么?
- 哪些属性允许外部读取或写入?
- 哪些属性参与持久化?
- 哪些元数据属于 Presentation、Validation 或其他扩展?
- 哪些属性位于同一个同步域?
- 动态适配器如何在遵守 capability 的前提下读写属性?
目标模型
但完成这些事情以后,底层类型仍然应该看起来像正常的 C++。
```text
普通 C++ 数据
+ 显式 Schema
+ Property 固有能力
+ Metadata
+ 可选 Managed Synchronization
+ 可选 Runtime Adapter
```
## 2. 第一原则:增强,而不是替代
Structive 不是第二套对象模型,而是附加在已有 C++ 类型旁边的结构层。
第一原则:
推荐形态:
> **增强 struct,而不是替代 struct。**
```cpp
struct Device : structive::Property_Object<Device> {
double temperature;
std::string name;
};
```
注册字段仍是真实 C++ 成员。未注册状态仍然是普通 C++。只要 C++ 类型本身允许,raw access 永远存在。
而不是:
Structive 不要求每个字段变成 `Property<T>`,也不应该迫使业务对象进入第二套对象模型。
```cpp
struct Device {
Framework_Property<double> temperature;
Framework_Property<std::string> name;
};
```
### 原则
这个原则保护了原生 C++ 的几个关键性质:
如果一个能力可以通过 metadata 或很薄的 managed layer 实现,就不要改变原生 member model。
1. 成员仍然是真实成员。
2. 成员指针仍然可以作为可靠身份。
3. 所有权规则允许时仍然可以 raw access。
4. 未注册成员可以继续作为实现细节存在。
5. Structive 元数据可以和存储形式独立演进。
## 3. 类型信息与实例行为必须分层
这个原则也有明确代价:Structive 不可能拦截直接成员访问。只有通过 managed path 的访问才会获得 Structive 管理行为
## 3. 必须保持两层,而不是揉成一层
Structive 把类型级描述和实例级管理分开。
Structive 有两层架构
### 3.1 类型层
`Type_Descriptor<T>` 产生 `Object_Schema`,描述
`Type_Descriptor<T>` `Object_Schema` 保存结构事实
- 注册属性
- 属性 key
- accessor
- 注册 Property
- key
- intrinsic readable/writable
- Attribute
- constraint
- object 级可继承默认值;
- 默认 synchronization plan。
- Constraint
- 默认 synchronization 描述。
是类型的结构定义
些属于类型
### 3.2 实例层
`Property_Object<T>` 提供:
`Property_Object<T>` 提供实例行为
- 同一类型共享的默认解析 lock slot 拓扑
- 只有真实锁策略才需要的每实例 mutex 存储;
- 只有显式同步覆盖实例才持有的紧凑 topology
- managed typed read/write
- capability view
- 静态与运行时多属性 guard
- managed value traversal
- `Property_Object_Base` type-erased runtime access。
- managed read/write
- 可写同步域所需的锁存储;
- 多属性 Guard
- traversal
- type-erased runtime access
- 可选的每实例 synchronization override。
这是实例行为,不是 Schema 身份。
### 原则
### 3.3 设计约束
不要把可变的实例同步状态塞进 Schema,也不要让 Schema 必须依赖某一种特定的实例管理策略。由类型级默认同步计划推导出的不可变 topology 可以由同类型所有实例共享。
以后完全可能有用户只想描述大量普通对象,却不愿意为每个对象承担同步状态成本。当前架构应该持续保留这种可能性。
除非信息真的随实例变化,否则不要把类型级事实复制到每个对象里。
## 4. 注册必须显式
Structive 不认为所有 C++ 成员都天然属于结构模型
Structive 不假设所有成员都是 Property
```cpp
struct Device : structive::Property_Object<Device> {
int id;
double temperature;
mutable int internal_cache;
struct Device : Property_Object<Device> {
int temperature;
int internal_cache;
};
```
如果 Schema 只注册 `id` `temperature`那么 `internal_cache` Structive 完全不可见
这是故意的。结构暴露本身就是 API 设计,不应该根据物理布局自动推断。
只注册 `temperature``internal_cache` 完全不属于 Structive Schema
### 原则
**Schema 才是公开结构契约;struct 的物理成员集合不是自动 Schema。**
注册决定参与;不在 Schema 中就不属于 Structive。
## 5. C++ 内部优先使用编译期身份
## 5. Intrinsic Capability 属于 Property 自己
业务 C++ 代码通常应该通过成员指针定位 member-backed property
Structive **没有内建访问控制系统**
```cpp
device.read<&Device::temperature>();
device.write<&Device::temperature>(30.0);
device.schema().property<&Device::temperature>();
```
这样编译器能够建立最强的“对象类型—字段”关系。
数字 index 适合编译期泛型遍历;字符串 key 适合运行时边界。
### 身份层级
Property 只描述自己固有支持什么:
```text
业务 C++ 代码 → member pointer
编译期泛型代码 → property index
runtime / adapter → declared string key
none
read
write
read_write
```
不要因为存在 key,就把本来可以静态确定的 C++ 业务代码全部字符串化
## 6. Key 是结构协议标识,不是显示名称
每个 `Property_Descriptor` 都必须拥有非空 `key`,同一 Schema 内 key 必须唯一。
Key 被 Schema lookup、动态锁选择、runtime read/write 使用。因此它不只是 UI label。
即使 C++ 成员名不变,修改 key 也可能改变外部协议或持久化契约。
### 原则
**把 property key 当成协议级名字,重命名必须有意识。**
## 7. 只有一套 Attribute 系统
Structive 不应该再长出 `hint``annotation``ui metadata``serializer metadata` 等彼此独立的元数据容器。
一个 Attribute 可以声明:
- `attribute_category`:用于按 category 查询;
- `single_valued`:同一个声明中该 category 是否只能有一个值;
- `inheritable`:是否允许进入 object defaults
- category 自己需要的 payload。
Core 和 Extension 使用完全相同的协议。
### 原则
**增加语义时优先增加 Attribute category + interpreter,而不是再造一套 metadata framework。**
## 8. Category 必须有明确所有者
Core 只解释属于 Core 的 category。
例如 External Access 和 Persistence Access 会直接影响 Core capability view,所以 Core 理解它们是合理的;但 Core 不需要理解 `presentation::label`
Presentation 解释 Presentation;未来 JSON 扩展解释 JSONRPC 扩展解释 RPC。
Core 只负责统一保存、遍历和提供查询机制。
### 依赖约束
```text
Extension implementation
Structive Property Core
```
不能为了某个扩展写起来方便,就反过来让 Core 依赖 Extension。
## 9. Defaults 是继承,不是隐藏行为
`defaults(...)` 用于支持可继承 Attribute。Property 级声明仍然可以覆盖同 category 的默认值。
当前 Core 中可继承的 category 包括 External Access、Persistence Access 和 Sensitive。
Defaults 应该用于稳定的对象级政策默认值,而不是用来制造大量隐式行为。
### 原则
**Defaults 用来减少重复,但不能让一个属性最终生效的政策变得无法从 Schema 规则中推导。**
## 10. Managed Access 与 Raw Access 是两种契约
Raw access
通常由 Accessor 自动推导,也可以通过 metadata 显式收窄
```cpp
device.temperature = 30.0;
field<&Device::serial_number>(key<"serial_number">, read_only)
```
Managed access
它表达
```cpp
device.write<&Device::temperature>(30.0);
```
> 在 Structive managed model 中,这个属性可读、不可写。
Raw path 就是普通 C++,不会自动获得 Structive 锁和 capability 管理
Managed path 会经过 descriptor 和该实例的同步拓扑。
两条路径同时存在是设计结果,不是漏洞。
它不表达哪个用户、服务、GUI 或进程有权限看到它
### 原则
不要假装继承 `Property_Object<T>` 以后 public member 就自动变成强封装属性。如果某个子系统要求受管理同步,那么该子系统自己的编码约束必须要求使用 managed path
Property capability 描述结构,不描述授权
## 11. 固有能力先于边界投影
## 6. Core 不拥有外部访问策略
每个 Property 首先拥有 accessor 自身决定的能力
Structive 不定义下面这些 managed access mode
```text
intrinsic
├── read
└── write
internal
external
persistence
role
context
permission
```
应用内部的普通 managed code 通过 `read()``write()`、lock 和 traversal 直接使用这套固有能力,因此 `internal` 不再是单独的 capability mode
GUI 自己决定哪些属性可编辑,RPC 自己决定暴露哪些字段,持久化系统自己决定保存哪些字段
External 和 Persistence 是同一份 Property definition 之上的边界投影:
```text
Property
├── intrinsic: read / write
├── external: read / write projection
└── persistence: load / store projection
```
Core 会把投影 Attribute 与 accessor 的实际能力结合起来。投影可以收窄固有能力,但绝不能凭空创造 accessor 本身不具备的能力。
External view 不应该发展成第二份 SchemaPersistence view 也不应该成为第二份 Schema。它们都只是同一 Schema 的能力投影。
Core 只提供结构事实,Consumer 自己定义策略。
### 原则
**一份 property definition,固有能力明确,边界投影显式。**
不要因为某个 Adapter 需要策略,就把这个策略塞进 Core。
## 12. Validation = 元数据 + 显式操作
## 7. Raw Access 与 Managed Access 是两种契约
Constraint 描述候选值是否合法,但 `write()` 不会自动执行它。
必须保持分开的概念包括:
```text
单字段 constraint
跨字段 invariant
locking
transaction boundary
rollback strategy
error reporting
side effects
```
一个通用 `write()` 不可能替所有业务正确猜出这些规则。
### 示例
下面两句故意具有不同语义:
```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<&Device::max_speed>();
guard.set<&Device::min_speed>(candidate_min);
guard.set<&Device::max_speed>(candidate_max);
if (candidate_min > candidate_max) {
guard.set<&Device::min_speed>(old_min);
guard.set<&Device::max_speed>(old_max);
}
device.temperature = 30;
device.write<&Device::temperature>(30);
```
业务操作自己拥有 invariant 和 rollback 语义
Raw path 遵守普通 C++。Managed path 遵守 Structive Schema 与同步模型
如果代码直接写一个 `read_only` public member,它就是主动绕过 Structive contract。
### 原则
**不要把 `write()` 变成隐藏事务引擎。**
Structive 服务于遵守 managed contract 的代码,不假装能阻止故意绕过系统的 raw C++。
## 13. Synchronization 就只负责 Synchronization
## 8. Read-Only Metadata 必须产生真实优化
Synchronization plan 的本质是把 property 映射到 lock slot,支持 independent、shared、unsynchronized、grouped 等配置
如果一个存储属性无法通过 Structive 被写入,那么不存在另一个 Structive managed writer 与它竞争
Group 表示一致性域:同一个 group 的 property 使用同一个 mutex slot。
因此 intrinsic read-only 的存储属性:
多属性锁会对 slot 去重,并按稳定 slot 顺序获取锁。
- 不分配 lock slot
- 不贡献 mutex
- 不受宽泛默认同步模式影响;
- managed read 不查询 lock slot
- managed read 不构造 `shared_lock`
### Synchronization 不等于
这是 Schema 信息直接产生的结构优化。
- validation
- transaction
- rollback
- event emission
- dirty tracking
- persistence commit。
### 原则
这些能力可以构建在 Structive 上层,但不能偷偷和锁耦合
如果 Schema 已经证明某项运行时状态没有必要,就不要分配,也不要执行
## 14. Computed Property 必须明确一致性边界
## 9. 编译期信息必须消除运行时工作
`computed_property` 通过 synchronized view 读取依赖项。这个 view 只允许读取和 computed property 本身处于同一个 resolved lock slot 的属性。
所以依赖 `min_speed``max_speed``speed_span` 应该和它们进入同一个同步组:
Typed API 在编译期知道目标 Property
```cpp
synchronization(
sync_all_independent,
sync_group("speed", "min_speed", "max_speed", "speed_span")
)
device.read<&Device::serial_number>();
```
这样一致性关系由同步计划明确表达,而不是让 getter 随意读取任意字段
对于 stored read-only property,编译期路径直接绕过同步
### Trusted Accessor
同样,对只读属性的 typed write 应该通过 `requires` 从接口中消失,而不是运行后再拒绝。
`trusted_computed_property``trusted_accessor_property` 会直接调用对象上的受信任成员函数,设计上绕开 synchronized view 的依赖检查
动态 key API 因为 key 只能运行期确定,所以才做 runtime check
### 原则
**默认优先 synchronized computed property。只有 accessor 自身明确拥有或保证同步语义时才使用 trusted accessor。**
静态事实优先变成 `constexpr``requires``if constexpr`,不要变成 runtime branch。
## 15. Runtime Access 是边界能力
## 10. Synchronization 描述的是可变一致性域
`Property_Object_Base` 提供 type-erased runtime interface,核心输入包括:
Synchronization 的目的,是协调 mutable managed state。
- string key
- 不传 mode 时使用 intrinsic access
- 只有选择 `external``persistence` 投影时才使用 `Managed_Access_Mode`
- `std::type_info`
- 显式 result code。
默认模式:
它适用于编译期不知道具体类型的 adapter。
```text
independent
shared
unsynchronized
```
它不应该成为把所有正常 typed C++ 代码动态化的理由
Group 让多个可变 Property 共享一个锁域
Read-only stored property 即使被宽泛规则包含,也会从最终 resolved topology 中移除。
### 原则
**能在编译期确定的代码就留在编译期;只有真正跨动态边界时才进入 runtime path。**
Synchronization 解决 mutable consistency,不解决可见性。
## 16. Compile-time Error 和 Runtime Error 分工明确
## 11. Computed Read-Only 是特殊情况
Structive 对静态可知问题尽量在编译期拒绝:
Computed Property 自身可以只读,但它可能依赖可写字段。
- member 未注册;
- key 重复;
- 同一成员存储被重复注册;
- single-valued category 重复;
- 非 inheritable Attribute 被放入 `defaults(...)`
- accessor 实际能力与声明 capability 冲突。
Computed value 本身没有可写存储,但读取它时可能需要 mutable dependency 的一致快照。
Runtime failure 留给 runtime 输入和 runtime 配置:
`Synchronized_Computed_Accessor` 因此使用 synchronized read view。需要原子一致性的 writable dependency 应和 computed property 处于同一个同步域。
- 动态 key 不存在;
- 动态属性对该 view 不可访问;
- synchronization plan 引用了未知 key
- runtime 同步规则重复配置同一个 property;
- runtime type mismatch。
Read-only stored dependency 没有 managed writer,所以可以直接读取而无需锁。
### 原则
**静态可知的 Schema 错误不要拖到运行时;真正动态的 adapter 输入也不要硬伪装成编译期问题。**
不要给 read-only stored field 创建 mutex。Computed read 的同步只服务于需要一致性的 mutable dependency。
## 17. 同步策略必须保持可替换
## 12. 只有一套 Attribute 协议
`Property_Object<Derived, Lock_Policy>` 支持同步策略,默认是 `Shared_Mutex_Policy`,同时提供 `No_Lock_Policy`
Core 与 Extension 共用同一套 Attribute mechanism
这是重要架构缝隙。Core 语义不应该和某一种 mutex 或某一个全局 scheduler 焊死
Attribute 拥有 category,并可以声明 single-valued 与 inheritable 语义
`No_Lock_Policy` 只是取消真实互斥,不代表并发访问自动安全
UI、serialization、诊断等新领域不应该另起第二套 metadata system
## 18. Extension 设计规
### 原
一个好的 Structive Extension 应该:
新的 metadata domain 应扩展 Attribute protocol,而不是创造另一套框架。
1. 定义属于自己领域的 category。
2. 复用 Core Attribute 协议。
3. 只解释自己拥有或明确依赖的 category。
4. 依赖 Core,而不是要求 Core 反向依赖自己。
5. 把领域 fallback 行为留在扩展里。
6. 优先消费已有 Schema,不复制第二棵 descriptor tree。
7. 不因为 adapter 需要便利规则就修改 Core 基础语义。
## 13. Category 必须有明确所有者
## 19. Core非目标
定义 Attribute category组件拥有它的语义。
Structive Core 不应该同时变成:
Presentation 拥有 Presentation Category。Core 可以保存、遍历、泛型读取,但不解释它。
依赖方向保持:
```text
Extension → Core
Core -X→ Extension
```
### 原则
Core 可以存储未知 Extension metadata,但不能学习 Extension 语义。
## 14. Defaults 只做 Metadata 继承
`defaults(...)` 用于 inheritable Attribute,不用于偷偷执行行为。
Property-level metadata 可以覆盖同一 single-valued category 的 object default。
Intrinsic capability 不允许继承,因为每个 Property 自己的读写能力必须单独成立。
### 原则
Defaults 只降低 metadata 重复,不得变成隐藏行为引擎。
## 15. Validation 必须显式
Constraint 描述有效值,但不自动塞进每次 `write()`
```cpp
auto error = validate_property_value<&Device::temperature>(schema, candidate);
```
单字段 Validation、跨字段 invariant、transaction、rollback 是不同操作。
### 原则
不要让 `write()` 变成 validation + event + transaction + rollback 的黑盒工作流。
## 16. Runtime Access 是适配能力,不是主要编程模型
`Property_Object_Base` 提供动态 key 的 type-erased access。
Runtime access 只遵守 intrinsic capability,没有 runtime permission mode。
Adapter 自己决定是否暴露某个 Property、是否调用 runtime read/write。
### 原则
普通 C++ 业务代码优先 member-pointer typed APIruntime access 留给动态边界。
## 17. Compile-Time Error 与 Runtime Error 分工明确
静态 typed operation 应在编译期拒绝不可能的结构操作,例如:
- 写 intrinsic read-only property
- 对 read-only property 请求 typed unique guard
- 读取 write-only property。
Runtime key API 才使用 `Runtime_Access_Result` 返回动态错误。
### 原则
静态可知的 Property 错误不要拖到运行期。
## 18. Synchronization Policy 必须保持可替换
`Shared_Mutex_Policy` 提供真实 shared/exclusive lock。`No_Lock_Policy` 不保存真实 mutex。
NoLock 不应该分配假的 mutex array,也不应该构造无意义的 lock object。
### 原则
一个 Policy 移除某项能力时,应尽可能同时移除它的存储和热路径成本。
## 19. 每实例 Synchronization Override 只让使用者付费
默认 resolved topology 每个类型共享。对象可以显式传入 `Property_Synchronization` 覆盖。
只有这种对象才保存 override layout。Copy/Move construction 保留该布局;Assignment 保留目标对象自身的 synchronization policy。
### 原则
默认路径不为少数实例才使用的功能承担存储成本。
## 20. Extension 自己决定领域行为
Persistence Extension 可以根据自己的 metadata 决定保存字段,GUI 可以决定 editabilityRPC 可以实现 authorization。
这些系统可以读取 Structive 的 `readable``writable``sensitive` 或自定义 Attribute,但 Structive 不替它们做策略决定。
### 原则
Core 描述结构,Consumer 在自己的边界决定行为。
## 21. Core 的非目标
Property Core 不准备成为:
- Authorization Engine
- ORM
- JSON
- GUI 框架
- RPC 框架
- signal/slot 系统
- 事务引擎
- 替代所有语言能力的“万能反射”
- JSON Library
- RPC Framework
- GUI Binding Framework
- Transaction Manager
- Event Bus
- Scripting Engine
Structive 应该提供足够强的结构契约,让这些系统来消费它
这些系统可以消费 Structive,但应该独立存在
## 20. 演进
## 22. 演进审核规
修改库时依次问
以后扩展 Structive 时逐条检查
1. 这是 structural model、instance management 还是 extension 的职责
2. 这个新概念本质上是不是一个 Attribute category,而不是新的 metadata channel
3.个错误能否在编译期发现
4. 修改后是否仍然保留原生 C++ 成员语义
5. Raw access 与 managed access 的边界是否仍然清晰
6. 是否把 synchronization 和 validation、transaction、event 混在一起了
7. Core 是否开始理解本应属于 Extension 的领域概念
8. Member pointer 是否仍然是 typed API 最自然的身份
9. 新的 convenience API 是否重复制造了另一条同语义路径
10. 是否保持了已有函数签名和功能语义,而不是悄悄改义
1. 它是在增强普通 C++,还是替代普通 C++
2. 这个信息属于类型还是实例
3.是 intrinsic structural capability,还是外部 policy
4. 编译期信息能不能直接消掉运行时工作
5. Read-only stored property 是否仍然完全不进入 lock topology
6. Synchronization 是否只服务于 mutable consistency
7. Validation 是否仍然显式
8. Extension 是否拥有自己的 category 语义
9. Runtime adaptation 是否与 typed business API 保持分离
10. 新 Core 概念是否真的普适
如果一个功能连续违反这些问题,应先重新考虑它属于哪一层,而不是立刻实现。
## 23. 架构总结
## 21. 架构总结
Structive 最强的形态应该是少量强概念,而不是大量魔法便利接口:
最终架构:
```text
普通 C++ 类型
+ 显式 Schema
+ 一套 Attribute 模型
+ 显式 Capability
+ 显式 Validation
+ 显式 Synchronization
+ 可选 Managed Instance 行为
+ Extension 自己解释自己的语义
Native C++ struct
├── raw C++ access
└── Structive Schema
├── intrinsic readable/writable
├── Attributes
├── Constraints
├── synchronization description
│ └── 只有 mutable consistency domain 创建锁
└── managed object
├── typed read/write
├── guards
├── traversal
└── intrinsic runtime access
External systems
├── GUI policy
├── RPC policy
├── persistence policy
└── other domain policy
外部 Policy 消费 Structive 的结构事实,但不是 Structive Core 的 access mode。
```
当泛型系统能够理解业务类型、却不需要接管业务类型时,Structive 的价值最大。
一句话概括:
> **Structive 描述 Property 是什么、它自身能做什么;Structive 不决定谁能使用它。**