F´ 框架 ExternalStack 完全指南:使用外部存储的 LIFO 栈模板
【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime
ExternalStack是 F´ 飞行软件与嵌入式系统框架中定义于Fw/DataStructures基础数据结构库的一个final类模板,它实现了一个使用外部存储的后进先出(LIFO)栈。本文以 ExternalStack.md 为骨架,结合其头文件实现与单元测试,系统讲解该类模板的模板参数、继承关系、全部构造函数、成员函数、静态辅助函数及测试验证,帮助你在 F´ 组件中正确地用它管理静态分配的内存与栈式数据。
1. 概览:什么是 ExternalStack
ExternalStack表示一个带有外部存储的栈:栈本身不持有元素数据的内存,而是通过内部维护的一个 ExternalArray 来引用调用者提供的后备存储(backing storage)。这种"外部存储"设计在嵌入式/飞行软件中非常实用——内存往往由上层系统静态分配或通过内存池统一管理,数据结构本身只负责组织与访问逻辑。
从 ExternalStack.hpp 的声明可以看出,它是一个模板类并被标记为final:
template <typename T> class ExternalStack final : public StackBase<T> {final关键字意味着它不能被继续继承,配合完整的接口实现,保证了栈语义的封闭与确定。
2. 类层次与依赖关系
ExternalStack<T>公开继承自StackBase,而StackBase又是 SizedContainer 的派生类(见 StackBase.hpp)。三者共同构成 F´ 栈数据结构家族的抽象骨架:
各层职责划分如下:
SizedContainer:最顶层的抽象容器接口,定义了clear()、getSize()、getCapacity()三个纯虚函数,并提供基于它们的isEmpty()与isFull()便捷方法(见 SizedContainer.hpp)。它删除了拷贝构造与赋值运算符,将"如何复制"交给具体实现决定。StackBase<T>:栈的抽象基类模板,将at、push、pop声明为纯虚函数,并基于这些原语实现了非虚的peek(带默认索引 0 的窥视)与copyDataFrom(跨栈数据拷贝)方法(见 StackBase.hpp)。ExternalStack<T>:栈接口的具体实现,内部组合一个ExternalArray<T>来持有元素。
这里有一个值得注意的细节:StackBase虽然删除了拷贝构造和operator=(见 StackBase.hpp),但ExternalStack通过浅拷贝语义自行实现了拷贝构造与赋值——复制的是对外部数组的引用(指针与容量),而非元素数据。这点将在第 5 节详细展开。
3. 模板参数
ExternalStack只有一个模板参数:
| Kind | Name | Purpose |
|---|---|---|
typename | T | 栈中元素(item)的类型 |
对T有一个静态约束:ExternalArray<T>的static_assert(std::is_assignable<T&, T>::value, ...)要求T可赋值(见 ExternalArray.hpp),因为栈操作(push、pop)依赖对T的赋值。
4. 私有成员变量
ExternalStack内部只有两个私有成员(见 ExternalStack.hpp):
| Name | Type | Purpose | Default Value |
|---|---|---|---|
m_items | ExternalArray<T> | 存储栈元素的数组 | C++ 默认初始化({},即空指针 + 0 容量) |
m_size | FwSizeType | 栈中当前元素数量 | 0 |
m_items是"外部存储"的载体:它自身只保存指向元素内存的指针m_elements与容量m_size(见 ExternalArray.hpp),并不拥有内存的所有权语义上的分配;m_size则记录当前栈顶的位置,与m_items的容量相互配合。FwSizeType是 F´ 框架定义的无符号大小类型。
5. 构造函数与析构函数
ExternalStack提供四个构造函数与一个默认析构函数,覆盖了"先构造后设存储""构造时绑定存储""无类型字节存储"与"拷贝"四种典型用法。
5.1 零参数构造函数
ExternalStack()将所有成员初始化为默认值(见 ExternalStack.hpp):此时栈为空、容量为 0,不能直接进行push操作,需要先调用setStorage绑定后备存储。
示例:
ExternalStack<U32> stack; // 容量为 0,大小为 05.2 提供类型化后备存储的构造函数
ExternalStack(T* items, FwSizeType capacity)items必须指向一个至少包含capacity个T类型元素的原生数组。该构造函数执行:
- 调用
setStorage(items, capacity); - 其余成员变量初始化为默认值。
示例:
constexpr FwSizeType capacity = 10; U32 items[capacity]; ExternalStack<U32> stack(items, capacity);5.3 提供无类型后备存储的构造函数
ExternalStack(ByteArray data, FwSizeType capacity)当内存以字节形式(如内存池中的缓冲)提供时使用。data必须满足两个约束:
- 按
getByteArrayAlignment()返回的对齐值对齐; - 至少包含
getByteArraySize(capacity)字节。
其实现为:调用setStorage(data, capacity),其余成员默认初始化。
示例:
constexpr FwSizeType capacity = 10; constexpr U8 alignment = ExternalStack<U32>::getByteArrayAlignment(); constexpr FwSizeType byteArraySize = ExternalStack<U32>::getByteArraySize(capacity); alignas(alignment) U8 bytes[byteArraySize]; ExternalStack<U32> stack(ByteArray(&bytes[0], sizeof bytes), capacity);5.4 拷贝构造函数
ExternalStack(const ExternalStack<T>& stack)执行*this = stack(见 ExternalStack.hpp)。与常规"深拷贝元素"不同,这里复制的是对外部数组的引用:目标栈将共享源栈的后备存储指针与容量,同时复制m_size。
示例:
constexpr FwSizeType capacity = 3; U32 items[capacity]; // 调用提供后备存储的构造函数 ExternalStack<U32> q1(items, capacity); // 压入一个元素 U32 value = 42; (void) q1.push(value); // 调用拷贝构造函数 ExternalStack<U32> q2(q1); ASSERT_EQ(q2.getSize(), 1);5.5 析构函数
~ExternalStack() override定义为= default(见 ExternalStack.hpp)。它不释放后备存储,因为存储不属于栈所有。
6. 核心成员函数
6.1 operator=(拷贝赋值)
ExternalStack<T>& operator=(const ExternalStack<T>& stack)语义与拷贝构造一致,算法为(见 ExternalStack.hpp):
- 若
&stack != this:- 令
m_items = stack.m_items(ExternalArray的赋值会重新指向源数组的指针与容量); - 令
m_size = stack.m_size;
- 令
- 返回
*this。
示例:
constexpr FwSizeType capacity = 3; U32 items[capacity]; // 调用提供后备存储的构造函数 ExternalStack<U32> q1(items, capacity); // 压入一个元素 U32 value = 42; (void) q1.push(value); // 调用默认构造函数 ExternalStack q2; ASSERT_EQ(q2.getSize(), 0); // 调用拷贝赋值运算符 q2 = q1; ASSERT_EQ(q2.getSize(), 1);6.2 clear
void clear() override将m_size置为 0(见 ExternalStack.hpp)。注意它只重置逻辑大小,不清空或释放后备存储中的元素内容。
示例:
constexpr FwSizeType capacity = 10; U32 items[capacity]; ExternalStack<U32> stack(items, capacity); const auto status = stack.push(3); ASSERT_EQ(stack.getSize(), 1); stack.clear(); ASSERT_EQ(stack.getSize(), 0);6.3 setStorage(类型化数据)
void setStorage(T* items, FwSizeType capacity)items必须指向至少capacity个T元素的原生数组。算法(见 ExternalStack.hpp):
- 调用
m_items.setStorage(items, capacity); - 调用
this->clear()。
在底层,ExternalArray::setStorage会先断言"非空数组时指针不能为空",并在传入指针与既有存储指针相同时避免释放存储(见 ExternalArray.hpp)。
示例:
constexpr FwSizeType capacity = 10; ExternalStack<U32> stack; // 先默认构造 U32 items[capacity]; stack.setStorage(items, capacity);6.4 setStorage(无类型数据)
void setStorage(ByteArray data, FwSizeType capacity)data需满足与 5.3 相同的对齐与字节数约束。算法为:调用m_items.setStorage(data, capacity),再调用this->clear()。
在底层,ExternalArray::setStorage的字节版本会依次断言:字节指针非空、指针按alignof(T)对齐、size <= data.size / sizeof(T)(采用除法形式避免size * sizeof(T)溢出FwSizeType),随后就地 placement-new 构造每个元素(见 ExternalArray.hpp),确保每个元素都是合法对象可供后续赋值。
示例:
constexpr FwSizeType capacity = 10; constexpr U8 alignment = ExternalStack<U32>::getByteArrayAlignment(); constexpr FwSizeType byteArraySize = ExternalStack<U32>::getByteArraySize(capacity); alignas(alignment) U8 bytes[byteArraySize]; ExternalStack<U32> stack; // 先默认构造 stack.setStorage(ByteArray(&bytes[0], sizeof bytes), capacity);6.5 push
Success push(const T& e) override将元素压入栈顶。算法(见 ExternalStack.hpp):
- 令
status = Success::FAILURE; - 若
m_size < getCapacity():- 令
m_items[m_size] = e; - 递增
m_size; - 令
status = Success::SUCCESS;
- 令
- 返回
status。
栈满时push返回Success::FAILURE而不是触发断言,这是嵌入式友好接口的典型设计——调用方可以安全地根据返回值处理溢出。
示例:
constexpr FwSizeType capacity = 3; U32 items[capacity]; ExternalStack<U32> stack(items, capacity); ASSERT_EQ(stack.getSize(), 0); auto status = stack.push(42); ASSERT_EQ(status, Success::SUCCESS); ASSERT_EQ(stack.getSize(), 1);6.6 pop
Success pop(T& e) override从栈顶弹出元素。算法(见 ExternalStack.hpp):
- 令
status = Success::FAILURE; - 若
m_size > 0:- 令
e = this->at(0)(取最右侧、即最新元素); - 递减
m_size; - 令
status = Success::SUCCESS;
- 令
- 返回
status。
对空栈调用pop同样返回FAILURE,不会崩溃。
示例:
constexpr FwSizeType capacity = 3; U32 items[capacity]; ExternalStack<U32> stack(items, capacity); U32 val; auto status = stack.pop(val); ASSERT_EQ(status, Success::FAILURE); // 空栈 status = stack.push(42); ASSERT_EQ(status, Success::SUCCESS); status = stack.pop(val); ASSERT_EQ(status, Success::SUCCESS); ASSERT_EQ(val, 42);6.7 at
const T& at(FwSizeType index) const override按索引访问元素。索引语义在 StackBase 中明确规定:索引 0 是栈最右侧(最新压入)的元素,索引递增方向从右往左(见 ExternalStack.hpp)。算法:
- 断言
index < m_size(越界直接触发FW_ASSERT); - 返回
m_items[m_size - 1 - index]。
示例:
constexpr FwSizeType capacity = 3; U32 items[capacity]; ExternalStack<U32> stack(items, capacity); const auto status = stack.push(3); ASSERT_EQ(status, Success::SUCCESS); ASSERT_EQ(stack.at(0), 3); ASSERT_DEATH(stack.at(1), "Assert"); // 越界断言6.8 getSize
FwSizeType getSize() const override返回当前栈中元素数量m_size。
示例:
constexpr FwSizeType capacity = 10; U32 items[capacity]; ExternalStack<U32> stack(items, capacity); auto size = stack.getSize(); ASSERT_EQ(size, 0); const auto status = stack.push(3); ASSERT_EQ(status, Success::SUCCESS); size = stack.getSize(); ASSERT_EQ(size, 1);6.9 getCapacity
FwSizeType getCapacity() const override返回栈的容量,即m_items.getSize()(见 ExternalStack.hpp)。
示例:
constexpr FwSizeType capacity = 10; U32 items[capacity]; ExternalStack<U32> stack(items, capacity); ASSERT_EQ(stack.getCapacity(), capacity);6.10 继承自基类的便捷方法
除上述覆写外,ExternalStack还直接继承了两个由StackBase实现的方法:
peek(T& e, FwSizeType index = 0) const:窥视指定索引元素而不弹出;索引越界时返回Success::FAILURE(见 StackBase.hpp)。copyDataFrom(const StackBase<T>& stack):将另一栈的元素拷贝到当前栈,取两者 size/capacity 的较小者,逐元素push并断言成功(见 StackBase.hpp)。
7. 公共静态函数
7.1 getByteArrayAlignment
static constexpr U8 getByteArrayAlignment()返回ExternalArray<T>::getByteArrayAlignment(),即alignof(T)(见 ExternalArray.hpp)。用于计算无类型后备存储所需的对齐。
7.2 getByteArraySize
static constexpr FwSizeType getByteArraySize(FwSizeType capacity)返回ExternalArray<T>::getByteArraySize(capacity),即capacity * sizeof(T)(见 ExternalArray.hpp)。用于计算无类型后备存储所需的字节数。两个函数均为constexpr,可在编译期用于静态数组声明(见 5.3 的示例)。
8. 测试验证:行为规格的机器可读化
ExternalStack的行为不仅由文档描述,还有完整的单元测试在 test/ut/ExternalStackTest.cpp 中固化为可执行断言:
- 构造测试:
ZeroArgConstructor验证默认构造容量/大小均为 0;TypedStorageConstructor与UntypedStorageConstructor分别验证两种后备存储构造函数正确接管了外部数组指针、容量正确且初始大小 0(见 ExternalStackTest.cpp)。 - 拷贝语义测试:
CopyConstructor与CopyAssignmentOperator验证拷贝后getSize() == 1且共享同一后备存储指针;CopyDataFrom则验证size1 < capacity2、size1 == capacity2、size1 > capacity2三种跨容量拷贝场景(见 ExternalStackTest.cpp)。 - 场景测试:基于 STest 规则/场景框架分别覆盖
at、clear、peek、popEmpty、popOK、pushFull、pushOK,并提供一个执行 1000 次随机操作的Random场景(见 ExternalStackTest.cpp)。
测试辅助类 ExternalStackTester.hpp 通过friend声明访问了ExternalStack的私有成员m_items(见 ExternalStack.hpp),从而可以断言内部ExternalArray的具体状态。此外,内部存储版本Stack<T, C>的测试 test/ut/StackTest.cpp 同样基于相同语义,可与ExternalStack的测试互为印证。
9. 使用场景与注意事项
典型使用场景:在 F´ 的 active/queued 组件内部用ExternalStack保存临时待处理项,后备存储来自静态数组或内存池缓冲区。F´ 官方文档 sdd.md 同时指出,Fw/DataStructures中的数据结构都是**顺序型(非线程安全)**的;在多线程上下文中使用时,必须借助外部并发控制——最常见做法是将数据结构作为 active/queued 组件的成员,用组件队列来串行化对其的访问。
关键注意事项:
- 存储所有权:
ExternalStack不拥有后备存储,析构、clear均不释放内存;释放内存的责任始终在调用方。 - 拷贝是引用共享:拷贝构造/赋值复制的是对外部数组的引用而非元素,两个栈会指向同一后备存储,修改一侧会影响另一侧;
StackBase中删除拷贝构造的注释也明确写着"行为取决于实现"。 - 越界即断言:
at越界会触发FW_ASSERT(在单元测试中对应ASSERT_DEATH);而push满栈、pop空栈则通过返回值Success::FAILURE优雅处理,使用时务必检查返回值。 - 字节存储的约束:无类型存储必须满足对齐与最小字节数两个硬约束,违反会在
setStorage阶段触发断言。
结合 ExternalArray 的完整实现,ExternalStack构成了 F´ 基础数据结构库中"栈 + 外部内存"的参考实现,是理解整个 Fw/DataStructures 库"内部存储/外部存储"双轨设计的最佳入口之一。
【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考