ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

F´ (F Prime) FifoQueueBase 抽象基类深度解析:FIFO 队列的接口设计与实现原理

F´ (F Prime) FifoQueueBase 抽象基类深度解析:FIFO 队列的接口设计与实现原理 F´ (F Prime) FifoQueueBase 抽象基类深度解析FIFO 队列的接口设计与实现原理【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprimeFifoQueueBase是 F´ 飞行软件框架 Fw/DataStructures 中 FIFO 队列家族的抽象基类模板定义了先进先出FIFO队列的统一接口与公共行为。本文以该基类文档为骨架结合 FifoQueueBase.hpp、ExternalFifoQueue.hpp 与 FifoQueue.hpp 的真实源码系统讲解其模板参数、继承关系、构造策略、纯虚接口与公共函数实现并给出基于Success返回码的完整使用示例帮助你在嵌入式与飞行软件项目中正确实现与使用 FIFO 队列。1. 设计定位FIFO 队列家族的抽象基类FifoQueueBase是一个 C 类模板定义于Fw/DataStructures命名空间Fw表示一个 FIFO 队列的抽象基类。F´ 的Fw/DataStructures目录提供了一组基础数据结构库其中 FIFO 队列家族由三个模板构成模板描述存储位置ExternalFifoQueue使用外部内存存储队列项的 FIFO 队列外部调用方提供FifoQueue使用内部内存存储队列项的 FIFO 队列内部对象自身持有FifoQueueBaseFIFO 队列的抽象基类—从 sdd.md 中的类图可见SizedContainer位于整个数据结构体系的最顶端FifoQueueBase派生于它而ExternalFifoQueue与FifoQueue又均公开继承FifoQueueBase基类本身不持有任何队列存储而是把队列这一抽象形态的接口与公共算法固化下来由派生类负责具体的内存布局与索引管理——这正是抽象基类 内部/外部存储两种实现这一设计模式的核心价值。2. 模板参数与继承关系2.1 模板参数FifoQueueBase只有一个模板参数即队列中元素的类型种类名称用途typenameT队列中一个元素的类型2.2 基类SizedContainerFifoQueueBaseT公开派生于SizedContainer见 FifoQueueBase.hpp。SizedContainer是有容量与大小的容器抽象它定义了三个纯虚函数和两个基于它们的公共便捷函数见 SizedContainer.hpp函数类型语义void clear()纯虚清空容器FwSizeType getSize() const纯虚当前存储的元素个数sizeFwSizeType getCapacity() const纯虚最大可存储元素个数capacitybool isEmpty() const非虚返回getSize() 0bool isFull() const非虚返回getSize() getCapacity()概念约定源自 sdd.mdsize是数据结构当前存储的元素数capacity是最大可存储元素数。对固定大小数组二者相同对 FIFO 队列等容器二者通常不同。这一对概念贯穿整个Fw/DataStructures库。FifoQueueBase继承这些接口后不新增自己的抽象容器操作clear、getSize、getCapacity仍由SizedContainer提供而是新增队列特有的操作enqueue、dequeue、at、peek、copyDataFrom。3. 构造与析构策略防拷贝、可多态3.1 私有拷贝构造函数已删除FifoQueueBase(const FifoQueueBaseT queue)在基类中被定义为 delete。基类删除拷贝构造的目的是禁止通过基类引用对未知派生类型执行浅拷贝从而避免派生类内部存储如环形缓冲区索引、外部存储指针被错误复制。真正的拷贝语义由派生类各自实现见第 6 节的operator。3.2 受保护的零参数构造函数FifoQueueBase()受保护protected使用成员默认初始化。它仅初始化基类SizedContainer()不分配任何存储保证只有派生类可以构造它见 FifoQueueBase.hpp。3.3 虚析构函数virtual ~FifoQueueBase()定义为 default。将析构函数声明为virtual是删除拷贝构造后的多态基类的标准做法确保通过FifoQueueBaseT*/删除派生类对象时能正确调用派生类析构函数。3.4 已删除的赋值运算符FifoQueueBase operator(const FifoQueueBase)同样定义为 delete见 FifoQueueBase.hpp。源码注释说明了原因We avoid virtual user-defined operators——C 不允许虚的operator若在基类中开放赋值会因静态类型信息丢失而无法正确复制派生状态因此干脆在基类层禁止由各派生类提供类型安全的赋值。4. 纯虚接口队列的三项基本操作FifoQueueBase定义了三个纯虚函数构成 FIFO 队列的最小操作集派生类必须实现4.1 at —— 按下标取元素不做移除virtual const T at(FwSizeType index) const 0返回指定索引处的元素。约定索引 0 是队列最左端最早入队的元素索引递增方向为从左到右。若索引越界则触发断言失败FW_ASSERT。at不修改队列状态返回const T引用可零拷贝地读取元素。派生类 ExternalFifoQueue.hpp 中的实现印证了越界断言逻辑const T at(FwSizeType index) const override { FW_ASSERT(index this-m_size, static_castFwAssertArgType(index), static_castFwAssertArgType(this-m_size)); auto ci this-m_dequeueIndex; const auto i ci.increment(index); return this-m_items[i]; }注意它内部通过CircularIndex从m_dequeueIndex出发偏移index步因此即使队列经历过多次出队队首不在物理下标 0 处at(0)也始终正确指向逻辑上最早的元素。4.2 enqueue —— 入队加到右端virtual Success enqueue(const T e) 0FifoQueueBase规定的语义如下初始化status Success::FAILURE若队列有容纳新元素的空间则把e添加到队列右端置status Success::SUCCESS返回status。注意入队失败队列已满不触发断言而是返回Success::FAILURE让调用方决策。这也解释了为什么文档示例中都要检查返回值void f(FifoQueueBaseU32 queue) { queue.clear(); auto status queue.enqueue(3); ASSERT_EQ(status, Success::SUCCESS); }4.3 dequeue —— 出队从最左端移除virtual Success dequeue(T e) 0初始化status Success::FAILURE若size 0则移除队列最左端的元素并存入e置status Success::SUCCESS返回status。空队列出队同样返回Success::FAILURE而非断言符合查询/操作失败通过返回值表达的 F´ 风格void f(FifoQueueBaseU32 queue) { queue.clear(); U32 val 0; auto status queue.dequeue(val); ASSERT_EQ(status, Success::FAILURE); // 空队列出队失败 status queue.enqueue(3); ASSERT_EQ(status, Success::SUCCESS); status queue.dequeue(val); ASSERT_EQ(status, Success::SUCCESS); ASSERT_EQ(val, 3); }接口设计小结enqueue/dequeue用Success返回码报告满/空这种业务性失败at用断言报告越界这种编程错误。这一分工是理解整个队列接口的关键。5. 公共非虚函数基类固化的算法与纯虚接口不同copyDataFrom和peek在基类中给出了完整实现模板方法模式所有派生类直接复用5.1 copyDataFrom —— 拷贝另一队列的全部数据void copyDataFrom(const FifoQueueBaseT queue)算法步骤与 FifoQueueBase.hpp 实现一一对应若queue ! this自拷贝直接忽略调用clear()清空自身令size min(queue.getSize(), getCapacity())即只拷贝能容纳的部分对i从 0 到size-1令e queue.at(i)调用enqueue(e)并断言status Success::SUCCESS。由于第 2 步已保证size ≤ getCapacity()拷贝过程中enqueue必然成功所以第 3 步的断言是永真的防御性检查。源码中还把断言失败时的status一并传入FW_ASSERT作为调试参数const auto status this-enqueue(e); FW_ASSERT(status Fw::Success::SUCCESS, static_castFwAssertArgType(status));示例源队列元素多于目标容量时只拷贝容量上限个void f(FifoQueueBaseU32 q1, FifoQueueBaseU32 q2) { q1.clear(); // Enqueue an item U32 value 42; (void) q1.enqueue(value); q2.clear(); ASSERT_EQ(q2.getSize(), 0); q2.copyDataFrom(q1); ASSERT_EQ(q2.getSize(), 1); }copyDataFrom是FifoQueue::operator与拷贝构造的底层支撑见第 6.2 节实现了跨不同存储布局实现之间拷贝队列数据的能力。5.2 peek —— 偷看指定索引元素而不移除Success peek(T e, FwSizeType index 0) const参数index默认值为 0即默认查看队首元素。算法初始化status Success::FAILURE若index getSize()则令e at(index)置status Success::SUCCESS返回status。与at的关键区别peek先做范围检查越界返回FAILURE绝不触发断言适合在不确定队列是否足够深时安全地探测void f(FifoQueueBaseU32 queue) { queue.clear(); U32 value 0; auto status queue.peek(value); ASSERT_EQ(status, Success::FAILURE); // 空队列 peek 失败 status queue.enqueue(3); status queue.peek(value); ASSERT_EQ(status, Success::SUCCESS); ASSERT_EQ(value, 3); // peek 到队首 3 status queue.peek(value, 1); ASSERT_EQ(status, Success::FAILURE); // 索引 1 尚不存在 status queue.enqueue(4); status queue.peek(value, 1); ASSERT_EQ(status, Success::SUCCESS); ASSERT_EQ(value, 4); // peek 到第二个元素 4 }6. 派生类实现环形缓冲区的底层原理FifoQueueBase的接口之美在于at(0)必须始终是最早的元素这要求存储层用环形circular缓冲区而非普通数组。两个派生类都复用了CircularIndex——一个按模数回绕的索引模板。6.1 ExternalFifoQueue外部存储实现ExternalFifoQueue.hpp 的私有成员揭示了核心数据结构ExternalArrayT m_items {}; // 存放队列元素的数组外部存储 CircularIndex m_enqueueIndex {}; // 入队游标 CircularIndex m_dequeueIndex {}; // 出队游标 FwSizeType m_size 0; // 当前元素个数入队在m_enqueueIndex处写入元素后递增游标、m_size出队读at(0)即从m_dequeueIndex开始偏移 0递增出队游标、m_size--清空两个游标归零、m_size 0见clear()。构造函数通过setStorage接收类型化存储T* itemscapacity或非类型化存储ByteArraycapacity并配套静态函数getByteArrayAlignment()/getByteArraySize(capacity)供调用方预先计算所需的字节数与对齐——这让队列可以被放到共享内存、内存池等场景中。6.2 FifoQueue内部存储实现FifoQueue.hpp 是final类模板FifoQueueT, C把ExternalFifoQueue与一块内嵌数组组合起来并做了两个static_assert编译期约束static_assert(std::is_default_constructibleT::value, T must be default constructible); static_assert(C 0, capacity must be greater than zero);即元素类型必须可默认构造因为内部数组T m_items[C]需要默认构造全部元素容量必须大于 0。其成员为名称类型用途默认值m_extQueueExternalFifoQueueT外部队列实现C 默认初始化m_itemsT[C]为m_extQueue提供后备内存的数组C 默认初始化零参数构造函数用ExternalFifoQueueT(m_items, C)把内嵌数组交给外部实现作后备存储拷贝构造与operator则全部转发给m_extQueue.copyDataFrom(queue)从而获得FifoQueueBase::copyDataFrom中先清空、再按容量截断拷贝的语义。所有虚接口clear/enqueue/dequeue/at/getSize/getCapacity都只是转发调用。6.3 类的整体接口速查成员类别说明at(index)纯虚返回索引处元素越界断言enqueue(e)纯虚右侧入队满返回FAILUREdequeue(e)纯虚左侧出队空返回FAILUREpeek(e, index0)非虚越界安全地查看元素copyDataFrom(q)非虚先清空后按容量拷贝clear()/getSize()/getCapacity()继承自SizedContainer容器通用操作isEmpty()/isFull()继承自SizedContainer便捷判定7. 测试验证规则化单元测试Fw/DataStructures的 FIFO 队列测试位于 Fw/DataStructures/test/ut/FifoQueueTest.cpp 与 Fw/DataStructures/test/ut/ExternalFifoQueueTest.cpp采用规则Rule与场景Scenario驱动的 STest 测试框架测试状态与规则定义在 Fw/DataStructures/test/ut/STest 目录下。测试覆盖了基类文档中所有核心行为ZeroArgConstructor构造后getCapacity() C、getSize() 0CopyConstructor/CopyAssignmentOperator拷贝后两个队列getSize()一致CopyDataFrom覆盖源队列元素数小于目标容量、等于目标容量、大于目标容量三种边界验证copyDataFrom的截断语义enqueue/dequeue/peek/at的成功与失败路径含空队列出队失败、满队列入队失败、越界at触发断言等。例如CopyDataFrom测试中特意构造size1 capacity2的情形验证拷贝后目标队列恰好填满见 FifoQueueTest.cpp这直接对应文档第 5.1 节size min(...)的算法步骤。8. 工程应用WasmSequencer 中的真实用法在 F´ 源码中Fw::FifoQueue有一个真实的使用案例Svc/WasmSequencer组件用其缓存等待中的 WebAssembly 命令见 Svc/WasmSequencer/WasmSequencer.hppFw::FifoQueueWaitingCmd, WasmSequencerConfig::MAX_CONCURRENT_WAIT_COMMANDS m_waiting;这里T是命令描述符WaitingCmd容量来自配置常量MAX_CONCURRENT_WAIT_COMMANDS充分体现了FifoQueueT, C编译期确定容量、零动态内存分配的嵌入式适用性。需要特别强调的是线程安全边界见 sdd.mdFw/DataStructures的数据结构都是顺序数据结构不支持多线程直接并发访问。若要在多线程上下文中使用必须自行提供并发控制最常见的做法是将其作为 active 或 queued 组件的成员借助组件队列守护对数据结构的访问。F´ 组件内部队列的调度语义可参考Fw/Comp目录下的组件基类文档与 Fw/Comp 源码。9. 从基类到实战一份完整的使用模板综合以上分析使用FifoQueueBase家族的标准姿势如下#include Fw/DataStructures/FifoQueue.hpp // 内部存储实现最常用 #include Fw/Types/BasicTypes.hpp // 1. 声明元素类型 U32容量 4 Fw::FifoQueueU32, 4 queue; // 2. 入队检查返回值满则失败 auto status queue.enqueue(10); FW_ASSERT(status Fw::Success::SUCCESS); status queue.enqueue(20); // 3. 通过基类引用进行多态访问 Fw::FifoQueueBaseU32 base queue; U32 front 0; FW_ASSERT(base.peek(front) Fw::Success::SUCCESS); // front 10不移除 FW_ASSERT(base.at(0) 10); // 越界才会断言 // 4. 出队先进先出 U32 e 0; FW_ASSERT(base.dequeue(e) Fw::Success::SUCCESS); // e 10 FW_ASSERT(base.dequeue(e) Fw::Success::SUCCESS); // e 20 FW_ASSERT(base.dequeue(e) Fw::Success::FAILURE); // 空队列 // 5. 容量相关查询来自 SizedContainer FW_ASSERT(queue.getCapacity() 4); FW_ASSERT(queue.isEmpty());选择哪个实现取决于存储来源需要对象内嵌存储、栈上分配、确定性容量→Fw::FifoQueueT, C需要外部/共享存储内存池、共享内存、非类型化字节缓冲→Fw::ExternalFifoQueueT配合getByteArraySize计算缓冲大小需要编写与具体实现无关的通用队列处理逻辑→ 只依赖Fw::FifoQueueBaseT接口结合 ExternalFifoQueue 文档 与 FifoQueue 文档 理解各实现细节。10. 小结FifoQueueBase是 F´ 数据抽象体系中一个小而精的基类它通过三个纯虚操作at/enqueue/dequeue 两个固化的算法copyDataFrom/peek 从SizedContainer继承的容量语义把 FIFO 队列的共性完全收敛到基类同时把存储布局的选择权留给派生类。配合CircularIndex实现的环形缓冲区F´ 得以在零动态内存分配、确定性执行的前提下提供健壮的先进先出队列这也正是其在 Svc/WasmSequencer 等飞行软件组件中被广泛采用的根本原因。【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表