F´ (F Prime) 集合抽象基类 SetBase 全面解析:接口设计、迭代器模型与具体实现
【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime
SetBase是 F´ 飞行软件与嵌入式系统框架中集合数据结构的抽象基类模板,定义于Fw/DataStructures模块,为所有集合实现(如基于数组的ArraySet、基于红黑树的RedBlackTreeSet)提供统一的多态接口。本文将以 SetBase.md 为核心骨架,结合 SetBase.hpp 源码实现、SetConstIterator.hpp 迭代器机制以及 ArraySetTest.cpp 测试用例,完整讲解其模板参数、继承体系、六个纯虚/公共接口的语义与示例,并深入剖析copyDataFrom的拷贝算法与底层委托实现,帮助读者在嵌入式环境中正确使用和扩展 F´ 集合容器。
一、SetBase 在 F´ 数据结构体系中的定位
F´ 的Fw/DataStructures模块采用"接口抽象 + 具体实现"的两层架构:顶层是一组抽象基类(SizedContainer、SetBase、MapBase、StackBase等),底层则是持有实际存储的实现类。SetBase正是集合(set)这一支线的抽象层。
SetBase<T>公开继承自抽象容器基类SizedContainer,后者定义了所有"有容量限制的容器"共有的四个接口:
virtual void clear() = 0:清空容器;virtual FwSizeType getCapacity() const = 0:返回容器容量(最大可存储元素数);virtual FwSizeType getSize() const = 0:返回当前元素个数;isEmpty()/isFull():由getSize()与getCapacity()派生的便捷查询(非虚函数)。
这意味着任何SetBase的派生类都天然具备查询大小、容量、判断空/满的能力。集合的语义是元素唯一:同一元素最多存在一份,插入已存在的元素不会产生重复项。
在类族结构上,SetBase与具体实现类的关系可概括为:
SizedContainer(抽象基类:容量/大小/清空) ↑ 公开继承 SetBase<T>(抽象基类:集合语义,6 个成员接口) ↑ 公开继承 ┌─────────────┴──────────────┐ ArraySet<T, C> RedBlackTreeSet<T, C> (内部数组存储, (内部红黑树存储, 委托 ExternalArraySet) 委托 ExternalRedBlackTreeSet)二、模板参数与基类
2.1 模板参数
SetBase是一个类模板,定义如下:
| Kind | 名称 | 用途 |
|---|---|---|
typename | T | 集合中元素的类型 |
模板形参T在接口层面贯穿始终:find、insert、remove三个成员函数均以const T&作为参数,迭代器ConstIterator也以T为模板参数实例化。
2.2 基类
SetBase<T>公开派生自SizedContainer,因此在 SetBase.hpp 中可见:
template <typename T> class SetBase : public SizedContainer {所有派生类必须实现SizedContainer的纯虚函数(clear、getCapacity、getSize),才能实例化。
三、设计决策:禁用的拷贝操作
SetBase将拷贝构造函数和拷贝赋值运算符声明为private且= delete,从源头上禁止"基类拷贝":
private: //! Copy constructor deleted in the base class SetBase(const SetBase<T>&) = delete; //! operator= deleted in the base class //! Behavior depends on the implementation //! We avoid virtual user-defined operators SetBase<T>& operator=(const SetBase<T>&) = delete;源码注释揭示了两个关键设计原因:
- 行为取决于具体实现:不同集合实现(数组 vs 红黑树)的拷贝语义不同,基类无法给出统一实现;
- 避免虚赋值运算符:C++ 中定义
virtual operator=会导致语义混乱(参数类型协变问题),因此基类干脆禁止拷贝,将拷贝能力下放到具体派生类各自实现(如ArraySet提供自己的拷贝构造函数与operator=,见下文第六节)。
四、公共类型:ConstIterator
SetBase定义一个公共类型别名:
| 名称 | 定义 |
|---|---|
ConstIterator | SetConstIterator<T>的别名 |
using ConstIterator = SetConstIterator<T>;SetConstIterator是专为集合设计的只读(const)迭代器,迭代顺序未定义(The iteration order is not specified),这给了不同底层实现(数组、红黑树)充分的存储自由度。它支持operator=、operator==、operator!=、前缀/后缀operator++、解引用operator*、箭头operator->以及isInRange()范围检查。
从源码看,SetConstIterator.hpp 内部通过一个union Impl同时容纳数组迭代器(ArrayIterator)与红黑树迭代器(RedBlackTreeIterator),并借助SetOrMapImplConstIterator的implKind()区分当前实现类型——这是一种嵌入式友好的"类型擦除"手法:用一个统一类型包装两种底层迭代器,从而让上层SetBase接口可以返回单一类型ConstIterator。解引用时它最终调用getEntry().getKeyOrElement()返回元素本身(SetConstIterator.hpp)。
五、受保护的构造与析构
SetBase的构造函数为protected,因此它只能作为基类被继承,不能直接实例化:
protected: //! Zero-argument constructor SetBase() : SizedContainer() {} //! Destructor virtual ~SetBase() = default;- 零参数构造函数:使用成员默认初始化,内部仅转发到
SizedContainer(); - 虚析构函数:
= default,但声明为virtual——这是多态销毁的关键,保证通过SetBase*删除派生类对象时能正确调用派生类析构函数,避免内存泄漏。
六、核心公共成员函数详解
SetBase提供六个公共成员函数,其中五个为纯虚函数(begin、end、find、insert、remove),由派生类实现;copyDataFrom为基类内联提供的非虚通用算法。下面逐一解析其语义与用法示例。
6.1 begin:获取起始迭代器
virtual ConstIterator begin() const = 0返回指向集合第一个元素的ConstIterator。具体"第一个元素"是谁由实现决定(数组实现为下标 0 的元素,红黑树实现为最左节点)。
示例:
void f(SetBase<U32>& set) { set.clear(); // Insert an element in the set const auto status = set.insert(42); ASSERT_EQ(status, Fw::Success::SUCCESS); // Get a set const iterator object auto it = set.begin(); // Use the iterator to access the element ASSERT_EQ(*it, 42); }6.2 copyDataFrom:跨集合数据拷贝
这是SetBase中唯一在基类直接实现的非虚函数(见 SetBase.hpp),其算法步骤为:
- 若
&set != this(即目标不是自身),继续执行; - 调用
clear()清空目标集合; - 令
size = min(set.getSize(), this->getCapacity())——取源集合大小与目标容量中的较小值,防止目标集合溢出; - 令
it = set.begin(); - 对
i从 0 到size-1循环:insert(*it)插入当前元素,断言status == Success::SUCCESS(因为已按容量截断,插入必然成功),然后it++前进。
void copyDataFrom(const SetBase<T>& set) { if (&set != this) { this->clear(); const FwSizeType size = FW_MIN(set.getSize(), this->getCapacity()); auto it = set.begin(); for (FwSizeType i = 0; i < size; i++) { const auto status = this->insert(*it); FW_ASSERT(status == Success::SUCCESS, static_cast<FwAssertArgType>(status)); it++; } } }注意源码中的实现细节:FW_MIN宏与FW_ASSERT断言是 F´ 框架的惯用工具。容量截断语义是该方法的重要行为特征:当目标集合容量小于源集合大小时,只拷贝"放得下"的前缀部分,超出部分被丢弃——这也解释了为什么copyDataFrom必须先从set.begin()顺序遍历。
示例:
void f(SetBase<U32>& s1, SetBase<U32>& s2) { s1.clear(); // Insert an entry const auto status = s1.insert(42); ASSERT_EQ(status, Success::SUCCESS); ASSERT_EQ(s1.getSize(), 1); s2.clear(); ASSERT_EQ(s2.getSize(), 0); s2.copyDataFrom(s1); ASSERT_EQ(s2.getSize(), 1); }6.3 end:获取结束迭代器
virtual ConstIterator end() const = 0返回"越过末尾"(past-the-end)的哨兵迭代器,用于循环终止判断。
示例:
void f(SetBase<U32>& set) { set.clear(); // Insert an element in the set auto status = set.insert(42); ASSERT_EQ(status, Fw::Success::SUCCESS); // Get a set const iterator object auto iter = set.begin(); // Check that iter is not at the end ASSERT_NE(iter, set.end()); // Increment iter iter++; // Check that iter is at the end ASSERT_EQ(iter, set.end()); }6.4 find:查找元素
virtual Success find(const T& element) const = 0- 若集合中存在元素值为
element的条目e,返回SUCCESS; - 否则返回
FAILURE。
返回值类型Success是 F´ 的Fw/Types/SuccessEnumAc.hpp中定义的状态枚举,接口通过头文件包含引入(见 SetBase.hpp)。该函数为const,不会修改集合。
示例:
void f(SetBase<U32>& set) { set.clear(); auto status = set.find(42); ASSERT_EQ(status, Success::FAILURE); status = set.insert(42); ASSERT_EQ(status, Success::SUCCESS); status = set.find(42); ASSERT_EQ(status, Success::SUCCESS); }6.5 insert:插入元素
virtual Success insert(const T& element) = 0三条语义规则:
- 若已存在元素值相同的条目
e,返回SUCCESS(重复插入被幂等地接受,不产生重复项); - 否则若集合未满,新增条目并返回
SUCCESS; - 否则(集合已满)返回
FAILURE。
这条语义决定了集合的去重特性:insert不会抛出异常或断言失败,而是用返回值向调用方报告容量状态,非常适合无异常机制的嵌入式环境。
示例:
void f(SetBase<U32>& set) { set.clear(); auto size = set.getSize(); ASSERT_EQ(size, 0); const auto status = set.insert(42); ASSERT_EQ(status, Success::SUCCESS); size = set.getSize(); ASSERT_EQ(size, 1); }6.6 remove:移除元素
virtual Success remove(const T& element) = 0- 若集合中存在元素值为
element的条目e,移除该条目并返回SUCCESS; - 否则返回
FAILURE(元素不存在时移除是无害且明确告知的操作)。
示例:
void f(SetBase<U32>& set) { set.clear(); auto size = set.getSize(); ASSERT_EQ(size, 0); auto status = set.insert(0); ASSERT_EQ(status, Success::SUCCESS); size = set.getSize(); ASSERT_EQ(size, 1); // Element does not exist status = set.remove(42); ASSERT_EQ(status, Success::FAILURE); ASSERT_EQ(size, 1); // Key exists status = set.remove(0); ASSERT_EQ(status, Success::SUCCESS); ASSERT_EQ(size, 0); }七、具体实现类:ArraySet 与 RedBlackTreeSet
SetBase是抽象基类,实际使用需实例化具体实现。F´ 在Fw/DataStructures中提供了两个典型实现:
7.1 ArraySet:数组存储,固定容量
ArraySet<T, C>是final类模板,T为元素类型、C为编译期容量(静态断言C > 0)。其内部持有两个成员:
ExternalArraySet<T> m_extSet:外部数组集合实现;Entry[C] m_entries:提供底层内存的条目数组。
构造函数将m_extSet初始化为ExternalArraySet<T>(m_entries, C)——即把用户(在此为ArraySet)提供的静态数组作为后备存储,实现"内部存储但接口委托"的封装模式。所有begin/end/find/insert/remove/getCapacity/getSize/clear均一行转发给m_extSet,例如insert返回m_extSet.insert(element)。
using Set = ArraySet<U32, 10>; Set set; const auto status = set.insert(42); ASSERT_EQ(set.getSize(), 1); ASSERT_EQ(set.getCapacity(), 10);ArraySet还提供了自己的拷贝构造函数与operator=(不同于基类的删除策略):拷贝构造会先用本对象的m_entries初始化m_extSet再赋值;operator=返回m_extSet.copyDataFrom(set)的结果。
7.2 RedBlackTreeSet:红黑树存储,元素有序
RedBlackTreeSet<T, C>是另一final实现,结构上与ArraySet完全对称,但底层委托给ExternalRedBlackTreeSet<T>,元素按红黑树有序组织(查找、插入、删除均为对数复杂度)。由于SetBase的迭代顺序本就"未指定",上层代码无需关心遍历次序差异。
两个实现的接口签名与SetBase完全一致,因此面向SetBase接口编写的应用代码可以在两种实现之间无缝切换,这正是抽象基类的价值所在。
八、测试与验证:行为契约的落地
F´ 为集合族提供了详尽的单元测试,测试文件位于Fw/DataStructures/test/ut/,其中 ArraySetTest.cpp 覆盖了ArraySet的完整行为契约:
ZeroArgConstructor:验证容量等于State::capacity、初始大小为 0;CopyConstructor/CopyAssignmentOperator:验证拷贝后大小与元素可查找性;CopyDataFrom:覆盖三种容量关系——源小于目标容量、等于目标容量、大于目标容量(验证容量截断);Clear/Find/FindExisting/InsertExisting/InsertFull/InsertNotFull/Remove/RemoveExisting:以 STest 场景库逐项验证接口语义;Random:随机操作 1000 次,验证实现的鲁棒性。
对应的RedBlackTreeSetTest.cpp、ExternalArraySetTest.cpp等文件对红黑树版本与外部存储版本执行同样的验证。这些测试用例(尤其是CopyDataFrom的三种容量关系测试)直接印证了第六节所述copyDataFrom的FW_MIN截断语义。
九、使用建议与注意事项
- 通过基类接口编程:业务代码尽量以
SetBase<T>&或const SetBase<T>&作为参数(如文档示例所示),底层实现可自由切换ArraySet与RedBlackTreeSet。 - 容量与失败处理:嵌入式环境通常禁用异常,务必检查
insert的FAILURE返回值(集合已满)并预先用getCapacity()/isFull()判断容量。 - 迭代器的只读与失效:
ConstIterator只读且不可用于修改集合;文档明确建议不要在通过迭代器指向集合后更新集合再使用该迭代器(operator*与operator->在迭代器越界时会触发断言失败)。 - 拷贝语义:
SetBase禁止基类拷贝;如需拷贝请使用具体实现类(ArraySet/RedBlackTreeSet)自身的拷贝构造/赋值,或使用基类的copyDataFrom(注意其按目标容量截断的特性)。 - 未定义遍历顺序:集合迭代顺序未指定,遍历结果不应依赖元素插入次序;需要有序访问时考虑红黑树实现并自行排序输出。
十、延伸阅读
- SetConstIterator:集合只读迭代器的完整接口
- SizedContainer:抽象容器基类(
clear/getCapacity/getSize/isEmpty/isFull) - ArraySet 与 RedBlackTreeSet:两个具体实现
- ExternalArraySet 与 ExternalRedBlackTreeSet:外部存储实现层
- sdd.md:
Fw/DataStructures模块软件设计说明 - 测试代码:Fw/DataStructures/test/ut/ 目录下的
ArraySetTest.cpp、RedBlackTreeSetTest.cpp等
【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考