--- tags: [] date created: 星期一, 十一月 24日 2025, 11:19:42 晚上 date modified: 星期一, 十一月 24日 2025, 11:20:43 晚上 --- # 2.5.4 零拷贝数据容器规范 (Zero-Copy Data Container Specification) **基线核心宗旨**:**“RAII for Ownership, Header for Context (RAII 管资产,Header 管上下文)”**。 容器必须是 **Movable-Only (仅可移动)** 的,严禁拷贝。它利用 C++ 类型系统强制执行所有权的单一性,并通过标准化的 Header 确保全链路的可观测性。 ----- ## 1\. 核心设计契约 (Design Contracts) 1. **仅移动语义 (Move-Only Semantics)**: - `DataPacket` 的拷贝构造函数和拷贝赋值操作符必须被 **显式删除 (`= delete`)**。 - **理由**:防止开发者无意中触发深拷贝,破坏零拷贝原则;防止多个对象持有同一块物理内存的所有权,导致 Double Free。 2. **统一元数据 (Unified Metadata)**: - 所有流转的数据包,无论其负载是原始波形还是航迹,都必须携带相同的 Header 结构。 - **理由**:使得中间件(如调度器、监控探针)可以在不解析 Payload 的情况下读取 `TraceID` 和 `Timestamp`。 3. **自动回收 (Self-Reclamation)**: - 容器析构时,必须自动触发 Payload 的资源释放(归还内存池或释放堆内存)。 - **理由**:消除内存泄漏风险,简化模块内的错误处理逻辑(异常安全)。 ----- ## 2\. 通用容器定义 (Generic Definition) ```cpp /** * @brief 通用零拷贝数据容器 * @tparam PayloadType 负载类型 (必须支持移动语义) */ template struct DataPacket { // --- 1. 标准化头部 (Fixed Header) --- struct Header { // 全链路追踪 ID (从 TraceContext 继承) uint64_t trace_id; // 数据生成时间戳 (UTC Microseconds, 总控授时) uint64_t timestamp_us; // 流水线序列号 (用于检测内部丢包/乱序) uint64_t sequence_id; // 源模块标识 (用于调试拓扑) uint32_t source_module_id; // 标志位 (e.g., END_OF_STREAM, CONFIG_UPDATE_BARRIER) uint32_t flags; } header; // --- 2. 业务负载 (Flexible Payload) --- PayloadType payload; // --- 3. 构造与移动语义 --- // 默认构造 DataPacket() = default; // 显式移动构造 DataPacket(DataPacket&& other) noexcept : header(other.header), payload(std::move(other.payload)) {} // 显式移动赋值 DataPacket& operator=(DataPacket&& other) noexcept { if (this != &other) { header = other.header; payload = std::move(other.payload); } return *this; } // 严禁拷贝 !!! DataPacket(const DataPacket&) = delete; DataPacket& operator=(const DataPacket&) = delete; }; ``` ----- ## 3\. 核心特化与所有权管理 (Specializations & Ownership) 根据 **2.5.1** 定义的业务对象,我们需要针对两类不同性质的数据定义具体的容器行为。 ### 3.1 原始回波数据包 (`RawDataPacket`) - **场景**:`DataReceiver` -\> `SignalProcessor`。数据量极大(MB 级),必须使用页锁定内存池。 - **Payload 类型**:`std::unique_ptr`。 - **所有权逻辑**: - `RawDataPacket` 持有 `unique_ptr`。 - 当 Packet 在队列中移动时,`unique_ptr` 随之移动。 - 当 Packet 在消费端(`SignalProcessor`)析构时,`MemoryPoolDeleter` 被调用,将底层的 `void*` 归还给 `PinnedMemoryPool`。 ```cpp // 定义特定的删除器 struct MemoryPoolDeleter { IMemoryPool* pool; void operator()(void* ptr) const { if (pool && ptr) pool->release(ptr); } }; // 别名定义 using RawPayload = std::unique_ptr; using RawDataPacket = DataPacket; ``` ### 3.2 结果数据包 (`DetectionPacket` / `TrackPacket`) - **场景**:`SignalProcessor` -\> `DataProcessor` -\> `DisplayController`。数据量较小(KB 级),使用堆内存或对齐分配器。 - **Payload 类型**:`std::vector` (Aligned)。 - **所有权逻辑**: - `std::vector` 本身就是 RAII 容器。 - `DataPacket` 的移动会自动触发 `vector` 的移动(仅交换内部指针 `begin/end/capacity`),成本极低(3 个指针大小)。 - **注意**:此处不需要自定义删除器,除非我们想引入“对象池”来复用 vector 的内存(对于 100Hz 的频率,`std::vector` 的默认分配器性能通常可以接受,暂不引入对象池以降低复杂度)。 ```cpp // 别名定义 // 使用 2.5.1 定义的对齐容器 using DetectionPayload = AlignedVector; using DetectionPacket = DataPacket; using TrackPayload = AlignedVector; using TrackPacket = DataPacket; ``` ----- ## 4\. 辅助工厂方法 (Factory Helpers) 为了简化 Header 的填充(防止开发者忘记填 TraceID),应提供工厂函数。 ```cpp template DataPacket MakePacket(T&& payload, uint64_t seq_id) { DataPacket packet; // 自动捕获当前上下文 packet.header.trace_id = TraceContext::getCurrentId(); packet.header.timestamp_us = Clock::nowInUs(); packet.header.sequence_id = seq_id; packet.header.source_module_id = ModuleContext::getCurrentModuleId(); packet.header.flags = 0; // 移动负载 packet.payload = std::move(payload); return packet; } ``` ----- ## 总结 **2.5.4 章节基线** 已确立为: 1. **Generic Envelope**:统一的 `DataPacket` 模板,强制包含 Header。 2. **Move-Only**:通过删除拷贝构造函数,物理上杜绝深拷贝。 3. **Hybrid Payload Strategy**: - 大内存(Raw):`unique_ptr` + `CustomDeleter` (Pool)。 - 小内存(Result):`std::vector` (Move Semantics)。 这一设计确保了数据在“集装箱”里流转时,既安全(不会泄露),又轻快(只有指针在动)。