资讯动态

详解C++ STL模拟实现forward_list

发布时间:2026/10/22 8:05:15 来源:尧图企业网站定制
前言std::forward_list是 C11 引入的单向链表容器。它和std::list的关系类似于std::array和std::vector功能更少、约束更强换来更低的存储开销。每个节点只存一个后继指针不像std::list那样存前驱和后继两个指针代价是只能单向遍历很多操作必须用「前驱位置」来表达。这也解释了forward_list最让人不适应的接口没有push_back、没有insert、没有erase取而代之的是insert_after、erase_after、emplace_after以及一个特殊的before_begin()。初学者常问「为什么不能直接删掉当前节点」答案就在单向链表的物理结构里要删当前节点必须改前一个节点的next而单向链表没有往回走的指针。另一个常见误解是「forward_list没有size()是缺陷」。这是有意的设计维护一个 size 计数器会让每个节点或容器多一次读写而forward_list的定位就是「尽可能轻」。需要长度时用std::distance(begin(), end())复杂度 O(n)。本文从节点布局讲起逐步实现一个可用的ForwardList迭代器、before_begin、插入删除、remove/remove_if、reverse、sort并说明每处为什么这么写。代码以 C17 为基准只用标准库本机没有编译环境代码按标准语义与常见实现方式推演未经编译验证。一、整体结构哨兵节点与「前驱」语义要实现before_begin()最自然的做法是在链表头部放一个哨兵节点sentinel。它不存元素值只存指向第一个元素的指针。这样一来before_begin()返回指向哨兵的迭代器begin()返回哨兵.nextend()返回空指针insert_after(before_begin(), v)等价于头插空链表时begin() end()哨兵仍在。哨兵的类型不能直接是NodeT因为T未必可默认构造而且哨兵不该承担构造/析构T的开销。常见实现如 libstdc把节点拆成两层一个只含next的基类和一个含T value的派生类。迭代器持有基类指针解引用时再向下转型。设计选择好处代价独立哨兵节点before_begin天然合法插入删除统一多一个节点通常可忽略基类/派生节点分离哨兵不构造T空链表无额外要求解引用需要一次static_cast迭代器存基类指针before_begin与普通迭代器同类型不能对哨兵解引用二、迭代器前向迭代器的最小集合forward_list的迭代器是前向迭代器forward iterator只支持*、-、、、!。它不能--也不能做it n。为了让std::distance、范围 for、算法库能识别它必须提供五个类型别名iterator_category、value_type、difference_type、pointer、reference。为了同时支持iterator和const_iterator可以用一个带bool IsConst的类模板再用std::conditional_t切换指针与引用类型。const_iterator需要能从iterator隐式转换这通过一个受enable_if约束的转换构造函数实现。三、完整实现下面这份实现覆盖了forward_list的核心子集。为了控制篇幅省略了splice_after、merge、unique和分配器支持这些操作的实现思路与文中给出的相同只是边界更多。// 适用于 C17#include cstddef#include iterator#include memory#include type_traits#include utilitytemplate class Tclass ForwardList {private:struct NodeBase {NodeBase* next;explicit NodeBase(NodeBase* p nullptr) : next(p) {}};struct Node : NodeBase {T value;template class... Argsexplicit Node(Args... args): NodeBase(nullptr), value(std::forwardArgs(args)...) {}};template bool IsConstclass basic_iterator {public:using iterator_category std::forward_iterator_tag;using value_type T;using difference_type std::ptrdiff_t;using pointer std::conditional_tIsConst, const T*, T*;using reference std::conditional_tIsConst, const T, T;basic_iterator() default;explicit basic_iterator(std::conditional_tIsConst, const NodeBase*, NodeBase* p): m_node(p) {}template bool Other, class std::enable_if_tIsConst !Otherbasic_iterator(const basic_iteratorOther other): m_node(other.m_node) {}reference operator*() const { return node()-value; }pointer operator-() const { return std::addressof(node()-value); }basic_iterator operator(){m_node m_node-next;return *this;}basic_iterator operator(int){basic_iterator old *this;*this;return old;}friend bool operator(const basic_iterator a, const basic_iterator b){return a.m_node b.m_node;}friend bool operator!(const basic_iterator a, const basic_iterator b){return a.m_node ! b.m_node;}private:using node_pointer std::conditional_tIsConst, const Node*, Node*;node_pointer node() const{return static_castnode_pointer(m_node);}std::conditional_tIsConst, const NodeBase*, NodeBase* m_node nullptr;template bool friend class basic_iterator;friend class ForwardList;};public:using iterator basic_iteratorfalse;using const_iterator basic_iteratortrue;ForwardList() default;ForwardList(const ForwardList other){copy_from(other);}ForwardList(ForwardList other) noexcept{m_before.next other.m_before.next;other.m_before.next nullptr;}ForwardList operator(ForwardList other){swap(other);return *this;}~ForwardList(){clear();}void swap(ForwardList other) noexcept{NodeBase* tmp m_before.next;m_before.next other.m_before.next;other.m_before.next tmp;}iterator before_begin() { return iterator(m_before); }const_iterator before_begin() const { return const_iterator(m_before); }const_iterator cbefore_begin() const { return const_iterator(m_before); }iterator begin() { return iterator(m_before.next); }const_iterator begin() const { return const_iterator(m_before.next); }const_iterator cbegin() const { return const_iterator(m_before.next); }iterator end() { return iterator(nullptr); }const_iterator end() const { return const_iterator(nullptr); }const_iterator cend() const { return const_iterator(nullptr); }bool empty() const { return m_before.next nullptr; }T front() { return static_castNode*(m_before.next)-value; }const T front() const{return static_castconst Node*(m_before.next)-value;}template class... Argsiterator emplace_after(const_iterator pos, Args... args){NodeBase* p const_castNodeBase*(pos.m_node);Node* n new Node(std::forwardArgs(args)...);n-next p-next;p-next n;return iterator(n);}iterator insert_after(const_iterator pos, const T value){return emplace_after(pos, value);}iterator insert_after(const_iterator pos, T value){return emplace_after(pos, std::move(value));}iterator erase_after(const_iterator pos){NodeBase* p const_castNodeBase*(pos.m_node);NodeBase* victim p-next;if (victim nullptr) {return end();}p-next victim-next;delete static_castNode*(victim);return iterator(p-next);}void push_front(const T value) { insert_after(before_begin(), value); }void push_front(T value) { insert_after(before_begin(), std::move(value)); }void pop_front(){erase_after(before_begin());}void clear(){NodeBase* p m_before.next;while (p ! nullptr) {NodeBase* next p-next;delete static_castNode*(p);p next;}m_before.next nullptr;}void remove(const T value){NodeBase* p m_before;while (p-next ! nullptr) {if (static_castNode*(p-next)-value value) {NodeBase* victim p-next;p-next victim-next;delete static_castNode*(victim);} else {p p-next;}}}template class Predvoid remove_if(Pred pred){NodeBase* p m_before;while (p-next ! nullptr) {if (pred(static_castNode*(p-next)-value)) {NodeBase* victim p-next;p-next victim-next;delete static_castNode*(victim);} else {p p-next;}}}void reverse(){NodeBase* prev nullptr;NodeBase* cur m_before.next;while (cur ! nullptr) {NodeBase* next cur-next;cur-next prev;prev cur;cur next;}m_before.next prev;}void sort(){if (m_before.next nullptr || m_before.next-next nullptr) {return;}NodeBase sorted; // 局部的有序链哨兵NodeBase* cur m_before.next;while (cur ! nullptr) {NodeBase* next cur-next;NodeBase* p sorted;while (p-next ! nullptr static_castNode*(p-next)-value static_castNode*(cur)-value) {p p-next;}cur-next p-next;p-next cur;cur next;}m_before.next sorted.next;}private:void copy_from(const ForwardList other){NodeBase* tail m_before;for (NodeBase* p other.m_before.next; p ! nullptr; p p-next) {tail-next new Node(static_castNode*(p)-value);tail tail-next;}}NodeBase m_before; // 哨兵next 指向首元素};3.1 插入与删除为什么都带 afterinsert_after(pos, v)的逻辑只有三步创建节点、让它指向pos的后继、让pos指向它。没有哨兵时头插需要特判有了哨兵before_begin()让头插和中间插入走同一条路径。erase_after(pos)同理拿到pos的后继把pos-next改成后继的后继再删除后继。它返回被删元素之后的迭代器方便循环删除for (auto it xs.before_begin(); it ! xs.end(); ) {auto next std::next(it);if (next xs.end()) {break;}if (*next 0) {it xs.erase_after(it); // 返回值已指向下一个元素} else {it;}}注意循环里不要写成xs.erase_after(it); it;it指向的节点没变但再会跳过元素应当使用返回值。3.2 拷贝、移动与赋值拷贝构造遍历源链表逐个new Node尾插到哨兵后面。移动构造只偷走next指针并把源哨兵的next置空保证源对象析构时不会删除已被接管节点。赋值采用 copy-and-swap参数按值传递左值走拷贝、右值走移动函数体内swap后由参数析构释放旧内容。这套写法自动满足自赋值安全。3.3 sort 的取舍上面的sort是插入排序对每个节点在已排序部分从头找第一个不小于它的位置插入。它稳定相等元素保持相对顺序实现短但比较次数是 O(n²)。标准库的std::forward_list::sort通常用自底向上的归并排序比较次数 O(n log n)且不需要额外节点。真实实现应当用归并本文选插入排序是为了让代码在有限篇幅内保持可读读者可以把它当作「正确但非最优」的版本。四、使用示例// 适用于 C17#include iostreamint main(){ForwardListint xs;xs.push_front(3);xs.push_front(1);xs.push_front(2);for (int v : xs) {std::cout v ; // 2 1 3}std::cout \n;xs.sort(); // 1 2 3xs.reverse(); // 3 2 1xs.insert_after(xs.before_begin(), 42);xs.remove(2);const ForwardListint cxs xs;for (auto it cxs.cbegin(); it ! cxs.cend(); it) {std::cout *it ;}std::cout \n;return 0;}范围 for 能工作是因为begin()/end()返回的迭代器满足前向迭代器的要求const ForwardListint的遍历走const_iterator*it返回const int不能通过它修改元素。常见坑点❌ 对forward_list调用push_back或size()。✅ 标准std::forward_list没有这两个成员尾部插入需要先遍历到最后一个节点长度用std::distance(begin(), end())。❌ 认为before_begin()返回的迭代器可以解引用。✅ 它指向哨兵不是元素只能用于insert_after、erase_after或作为遍历起点。❌ 在erase_after(it)之后继续用原来的it自增。✅ 用返回值it xs.erase_after(it);或先保存std::next(it)。❌ 删除节点时只改指针不delete。✅ 单向链表删除必须同时释放节点否则泄漏本实现的remove/erase_after都在改链后delete。❌ 用delete删除NodeBase*而不转回Node*。✅NodeBase没有虚析构按基类指针删除是 UB必须先static_castNode*再delete。❌ 移动构造后不把源对象的next置空。✅ 源对象析构时会重复删除已被接管的节点造成重复释放UB。❌ 对空链表调用front()。✅ 先empty()判断标准对空容器调用front()是 UB。❌ 在sort的比较里用。✅ 必须用严格弱序否则排序算法行为未定义。总结操作实现要点复杂度push_frontinsert_after(before_begin())O(1)insert_after改两个指针O(1)erase_after改指针并deleteO(1)remove/remove_if单次遍历边删边走O(n)reverse三指针就地反转O(n)sort本文版插入排序O(n²)sort标准库归并排序O(n log n)模拟实现forward_list的价值不在于复刻每一个成员函数而在于理解「单向」这个约束如何塑造整套接口为什么有before_begin为什么插入删除都带after为什么没有size。把哨兵、节点分层、迭代器类型这几个点想清楚再去看标准库的实现或自己扩展splice_after就不会觉得接口古怪了。实际项目中应优先使用std::forward_list它经过充分测试且带有分配器支持本文的实现适合作为学习与定制的起点。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑