news 2026/9/15 19:23:44

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
F´ (F Prime) FifoQueueBase 抽象基类深度解析:FIFO 队列的接口设计与实现原理

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

【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime

FifoQueueBase是 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派生于它,而ExternalFifoQueueFifoQueue又均公开继承FifoQueueBase

基类本身不持有任何队列存储,而是把"队列"这一抽象形态的接口与公共算法固化下来,由派生类负责具体的内存布局与索引管理——这正是"抽象基类 + 内部/外部存储两种实现"这一设计模式的核心价值。

2. 模板参数与继承关系

2.1 模板参数

FifoQueueBase只有一个模板参数,即队列中元素的类型:

种类名称用途
typenameT队列中一个元素的类型

2.2 基类:SizedContainer

FifoQueueBase<T>公开派生于SizedContainer(见 FifoQueueBase.hpp)。SizedContainer是"有容量与大小的容器"抽象,它定义了三个纯虚函数和两个基于它们的公共便捷函数(见 SizedContainer.hpp):

函数类型语义
void clear()纯虚清空容器
FwSizeType getSize() const纯虚当前存储的元素个数(size)
FwSizeType getCapacity() const纯虚最大可存储元素个数(capacity)
bool isEmpty() const非虚返回getSize() == 0
bool isFull() const非虚返回getSize() >= getCapacity()

概念约定(源自 sdd.md):size是数据结构当前存储的元素数,capacity是最大可存储元素数。对固定大小数组二者相同,对 FIFO 队列等容器二者通常不同。这一对概念贯穿整个Fw/DataStructures库。

FifoQueueBase继承这些接口后,不新增自己的抽象容器操作(cleargetSizegetCapacity仍由SizedContainer提供),而是新增队列特有的操作:enqueuedequeueatpeekcopyDataFrom

3. 构造与析构策略:防拷贝、可多态

3.1 私有拷贝构造函数(已删除)

FifoQueueBase(const FifoQueueBase<T>& queue)

在基类中被定义为= delete。基类删除拷贝构造的目的是:禁止通过基类引用对未知派生类型执行"浅拷贝",从而避免派生类内部存储(如环形缓冲区索引、外部存储指针)被错误复制。真正的拷贝语义由派生类各自实现(见第 6 节的operator=)。

3.2 受保护的零参数构造函数

FifoQueueBase()

受保护(protected),使用成员默认初始化。它仅初始化基类SizedContainer(),不分配任何存储,保证只有派生类可以构造它(见 FifoQueueBase.hpp)。

3.3 虚析构函数

virtual ~FifoQueueBase()

定义为= default。将析构函数声明为virtual是删除拷贝构造后的多态基类的标准做法,确保通过FifoQueueBase<T>*/&删除派生类对象时能正确调用派生类析构函数。

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_cast<FwAssertArgType>(index), static_cast<FwAssertArgType>(this->m_size)); auto ci = this->m_dequeueIndex; const auto i = ci.increment(index); return this->m_items[i]; }

注意它内部通过CircularIndexm_dequeueIndex出发偏移index步,因此即使队列经历过多次出队(队首不在物理下标 0 处),at(0)也始终正确指向逻辑上最早的元素。

4.2 enqueue —— 入队(加到右端)

virtual Success enqueue(const T& e) = 0

FifoQueueBase规定的语义如下:

  1. 初始化status = Success::FAILURE
  2. 若队列有容纳新元素的空间,则把e添加到队列右端,置status = Success::SUCCESS
  3. 返回status

注意入队失败(队列已满)不触发断言,而是返回Success::FAILURE让调用方决策。这也解释了为什么文档示例中都要检查返回值:

void f(FifoQueueBase<U32>& queue) { queue.clear(); auto status = queue.enqueue(3); ASSERT_EQ(status, Success::SUCCESS); }

4.3 dequeue —— 出队(从最左端移除)

virtual Success dequeue(T& e) = 0
  1. 初始化status = Success::FAILURE
  2. size > 0,则移除队列最左端的元素并存入e,置status = Success::SUCCESS
  3. 返回status

空队列出队同样返回Success::FAILURE而非断言,符合"查询/操作失败通过返回值表达"的 F´ 风格:

void f(FifoQueueBase<U32>& 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/dequeueSuccess返回码报告"满/空"这种业务性失败;at用断言报告"越界"这种编程错误。这一分工是理解整个队列接口的关键。

5. 公共非虚函数:基类固化的算法

与纯虚接口不同,copyDataFrompeek在基类中给出了完整实现(模板方法模式),所有派生类直接复用:

5.1 copyDataFrom —— 拷贝另一队列的全部数据

void copyDataFrom(const FifoQueueBase<T>& queue)

算法步骤(与 FifoQueueBase.hpp 实现一一对应):

  1. &queue != this(自拷贝直接忽略):
    1. 调用clear()清空自身;
    2. size = min(queue.getSize(), getCapacity()),即只拷贝能容纳的部分;
    3. 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_cast<FwAssertArgType>(status));

示例(源队列元素多于目标容量时只拷贝容量上限个):

void f(FifoQueueBase<U32>& q1, FifoQueueBase<U32>& 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); }

copyDataFromFifoQueue::operator=与拷贝构造的底层支撑(见第 6.2 节),实现了"跨不同存储布局实现之间拷贝队列数据"的能力。

5.2 peek —— 偷看指定索引元素而不移除

Success peek(T& e, FwSizeType index = 0) const

参数index默认值为 0,即默认查看队首元素。算法:

  1. 初始化status = Success::FAILURE
  2. index < getSize(),则令e = at(index),置status = Success::SUCCESS
  3. 返回status

at的关键区别:peek先做范围检查,越界返回FAILURE,绝不触发断言,适合在"不确定队列是否足够深"时安全地探测:

void f(FifoQueueBase<U32>& 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 的私有成员揭示了核心数据结构:

ExternalArray<T> 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* items+capacity)或非类型化存储ByteArray+capacity),并配套静态函数getByteArrayAlignment()/getByteArraySize(capacity)供调用方预先计算所需的字节数与对齐——这让队列可以被放到共享内存、内存池等场景中。

6.2 FifoQueue:内部存储实现

FifoQueue.hpp 是final类模板FifoQueue<T, C>,把ExternalFifoQueue与一块内嵌数组组合起来,并做了两个static_assert编译期约束:

static_assert(std::is_default_constructible<T>::value, "T must be default constructible"); static_assert(C > 0, "capacity must be greater than zero");

即:元素类型必须可默认构造(因为内部数组T m_items[C]需要默认构造全部元素),容量必须大于 0。其成员为:

名称类型用途默认值
m_extQueueExternalFifoQueue<T>外部队列实现C++ 默认初始化
m_itemsT[C]m_extQueue提供后备内存的数组C++ 默认初始化

零参数构造函数用ExternalFifoQueue<T>(m_items, C)把内嵌数组交给外部实现作后备存储;拷贝构造与operator=则全部转发给m_extQueue.copyDataFrom(queue),从而获得FifoQueueBase::copyDataFrom中"先清空、再按容量截断拷贝"的语义。所有虚接口(clear/enqueue/dequeue/at/getSize/getCapacity)都只是转发调用。

6.3 类的整体接口速查

成员类别说明
at(index)纯虚返回索引处元素,越界断言
enqueue(e)纯虚右侧入队,满返回FAILURE
dequeue(e)纯虚左侧出队,空返回FAILURE
peek(e, index=0)非虚越界安全地查看元素
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() == CgetSize() == 0
  • CopyConstructor/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.hpp):

Fw::FifoQueue<WaitingCmd, WasmSequencerConfig::MAX_CONCURRENT_WAIT_COMMANDS> m_waiting;

这里T是命令描述符WaitingCmd,容量来自配置常量MAX_CONCURRENT_WAIT_COMMANDS,充分体现了FifoQueue<T, C>"编译期确定容量、零动态内存分配"的嵌入式适用性。

需要特别强调的是线程安全边界(见 sdd.md):Fw/DataStructures的数据结构都是顺序数据结构不支持多线程直接并发访问。若要在多线程上下文中使用,必须自行提供并发控制;最常见的做法是将其作为 active 或 queued 组件的成员,借助组件队列守护对数据结构的访问。F´ 组件内部队列的调度语义可参考Fw/Comp目录下的组件基类文档与 Fw/Comp 源码。

9. 从基类到实战:一份完整的使用模板

综合以上分析,使用FifoQueueBase家族的标准姿势如下:

#include "Fw/DataStructures/FifoQueue.hpp" // 内部存储实现(最常用) #include "Fw/Types/BasicTypes.hpp" // 1. 声明:元素类型 U32,容量 4 Fw::FifoQueue<U32, 4> queue; // 2. 入队:检查返回值,满则失败 auto status = queue.enqueue(10); FW_ASSERT(status == Fw::Success::SUCCESS); status = queue.enqueue(20); // 3. 通过基类引用进行多态访问 Fw::FifoQueueBase<U32>& 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::FifoQueue<T, C>
  • 需要外部/共享存储(内存池、共享内存、非类型化字节缓冲)Fw::ExternalFifoQueue<T>(配合getByteArraySize计算缓冲大小);
  • 需要编写与具体实现无关的通用队列处理逻辑→ 只依赖Fw::FifoQueueBase<T>接口,结合 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 19:23:13

MCP自定义服务器进阶:错误处理、流式输出与TypeScript部署实践

MCP 自定义服务器开发进阶指南 —— 错误处理、流式输出、TypeScript 与部署MCP&#xff08;Model Context Protocol&#xff09;最近在开发者圈子里热度一直居高不下&#xff0c;从蓝湖 MCP 到 Figma MCP&#xff0c;再到 Codex MCP 这类工具链集成&#xff0c;本质上都是把“…

作者头像 李华
网站建设 2026/9/15 19:21:27

腾讯混元3D世界模型2.0实战:AI生成资产到Unity/UE5全流程

近几年AI生成3D资产已经不算新鲜事&#xff0c;可大部分模型生成完也就是“看一眼觉得厉害”&#xff0c;真要拖进Unity或UE里当生产资产&#xff0c;各种问题就全冒出来了。腾讯混元3D世界模型2.0算是这批工具里少数让我愿意反复用的一个——它不光是能出模型&#xff0c;还把…

作者头像 李华
网站建设 2026/9/15 19:21:16

杰理之长文件名录音支持【篇】

void filename_test(void){ u8 test_data “filename test123”; //写入文件数据 u8 read_buf[32];//读取数据buf int len;//文件大小 char path[256] “storage/sd0/C”;//路径 FILE fd NULL;//文件句柄指针//文件名&#xff1a;longfilename.txt 通过字符转UTF16LE编码写…

作者头像 李华