std::forward_list 是专为内存敏感、纯前向操作设计的单向链表,无 size() 以保零开销,仅支持 insert_after/erase_after 和 O(1) splice_after;需反向遍历、随机访问或频繁查长度时应选 std::list 或 std::vector。

std::forward_list 不适合拿来当“通用链表”用,它只在极少数内存敏感、纯前向操作的场景下才真正值得选。 你如果只是需要一个能随便插删、查长度、反向遍历的链表,直接用 std::list 或 std::vector 更省心。它的价值全在“省那一个指针”和“零开销 splice_after”上,其他全是妥协。
为什么 std::forward_list 没有 size()?不是忘了,是故意不存
标准要求 std::forward_list::size() 在 C++17 起必须是 O(1),但大量仍在使用的 STL 实现(如旧版 libstdc++、嵌入式裁剪版)仍按 C++11 行为走 —— 即调用一次就遍历一遍,O(n)。更关键的是,它压根没字段存计数:加一个 size_t _M_size 就意味着每次 push_front()、erase_after() 都得多执行一次整数增减,违背“零开销抽象”原则。
- 判空永远用
fl.empty(),它是 O(1) 且可移植 - 真要长度?只计一次:
auto len = std::distance(fl.begin(), fl.end());,别在循环条件里反复调 - 若逻辑频繁依赖长度(比如分页、索引访问),说明
std::forward_list不匹配你的需求
insert_after 和 erase_after 是唯一合法的中间操作入口
单向链表没有 prev 指针,所以无法从某个迭代器“往回找前驱”。所有中间插入/删除都必须基于“已知前一个节点”的前提。这意味着:
-
fl.insert_after(it, val)总是在it所指节点之后插入,it必须有效(不能是fl.end()) - 想插到开头?必须用
fl.insert_after(fl.before_begin(), val)——before_begin()是唯一安全的“逻辑头前”占位符 - 删第一个元素?
fl.erase_after(fl.before_begin());删第 n 个?先auto prev = std::next(fl.before_begin(), n-1),再fl.erase_after(prev) - 写
fl.erase(it)或fl.insert(it, val)会编译失败 —— 这些函数根本不存在
splice_after() 是它不可替代的性能核心,不是摆设
std::forward_list 的 splice_after() 是真正 O(1) 指针搬运,不构造、不析构、不拷贝元素,只改 next 字段。这在高频重组链表时(比如 LRU 缓存淘汰、任务队列迁移)是关键优势。
立即学习“C++免费学习笔记(深入)”;
-
dst.splice_after(pos, src):把整个src拼到dst的pos后,src变空 -
dst.splice_after(pos, src, it):把src中it指向的单个节点移到dst的pos后 -
src和dst必须同类型,且不能是同一个对象(自拼接未定义) - 被移动节点的迭代器在原容器中立即失效,但在新容器中仍可用(只要没被删)
什么时候该用,什么时候立刻放弃
它不是“链表的轻量版”,而是“单向链表的极致精简版”。适用边界非常窄:
- 适用:LRU 缓存(配合哈希表存前驱迭代器)、解析器 token 流、事件队列(只从头消费)、节点数量极大 + 值类型极小(如
std::forward_list<int></int>存百万级) - 放弃:需要
back()、operator[]、std::sort()(得用成员函数sort())、频繁size()、或任何可能需要反向遍历的逻辑 - 特别注意:用哈希表索引节点时,不能存
iterator(因为std::forward_list迭代器不支持--),必须存前驱迭代器,否则无法安全erase_after()


















