c++oding="utf-8" ?>
std::forward_list 无 size() 因标准要求 o(1) 复杂度,而维护计数器违背零开销设计;获取长度需 o(n) 的 std::distance;仅支持 insert_after/erase_after 操作;核心优势是 o(1) splice_after 拼接。

std::forward_list 为什么不能用 size()?
因为 std::forward_list 是为极致轻量设计的单向链表,标准明确要求其 size() 成员函数时间复杂度必须是 O(1),而维护一个实时更新的 size 计数器会增加每个插入/删除操作的开销(哪怕只是 +1/-1),违背其“零开销抽象”定位。所以它压根没存 size 字段。
实际中你要获取长度,只能用 std::distance(fl.begin(), fl.end()),但这是 O(n) —— 遍历一次链表。如果频繁需要长度,说明 std::forward_list 不适合你,该换 std::list 或 std::vector。
- 别在循环里反复调用
std::distance,性能雪崩 - 若只需判断是否为空,用
fl.empty()—— 这是 O(1) - 某些编译器(如 libstdc++)提供非标准扩展
__size(),但不可移植,别依赖
insert_after 和 erase_after:唯一合法的增删位置
std::forward_list 没有 insert()、erase()、push_front() 以外的“随机位置”操作接口。所有中间插入和删除都必须通过 insert_after() 和 erase_after(),且参数必须是一个有效的迭代器(指向某节点,而非 end)。
这是因为单向链表无法从后往前找前驱节点 —— 它没有 prev 指针。想在第 3 个元素后插入,你得先遍历到第 3 个,再传给 insert_after()。
-
fl.insert_after(fl.before_begin(), val)等价于push_front() -
fl.erase_after(fl.before_begin())删除首节点,等价于pop_front() - 传入
fl.end()给erase_after()是未定义行为 —— 它不是有效节点 - 想删第 n 个?先用
std::next(it, n-1)走到前一个,再erase_after()
移动语义支持弱,splice_after() 是核心优势
std::forward_list 不支持像 std::list::splice() 那样直接把另一容器的整段节点“摘下来”接过来 —— 它只有 splice_after(),且只能拼接另一个 forward_list 的一段(从某位置开始到结尾,或指定范围)。
但它拼接是真正 O(1) 的指针操作:不拷贝元素、不调用构造/析构,只改几个 next 指针。这在高频重组链表场景(比如 LRU 缓存淘汰、任务队列迁移)中是不可替代的性能优势。
-
dst.splice_after(pos, src):把整个src拼到dst中pos后面,src变空 -
dst.splice_after(pos, src, it):把src中it指向的节点移到dst的pos后 -
src和dst必须是同一类型,且不能是自身(自拼接未定义) - 注意:
splice_after()不影响被移动元素的值,但会使其迭代器失效(原属容器中)
和 std::list / std::vector 对比时的关键取舍点
选 std::forward_list 不是因为它“快”,而是它“最省”—— 内存占用最小(每个节点仅存一个 next 指针),插入/删除首部最快(O(1) 且无内存分配),且允许常数时间拼接。但它付出的代价很实在:不能反向遍历、不能随机访问、不能高效查长度、没有 begin() - 1 这种前驱能力。
- 如果你需要
operator[]或at(),直接排除它 - 如果你常做
find_if后立刻删,forward_list比list多一次遍历(先 find,再next找前驱),不如list - 如果容器生命周期短、节点少、且主要操作是头插/头删/拼接(如解析 token 流、临时构建链式结构),它就是最优解
- 别为了“听说链表快”就用它 —— 在缓存友好的场景下,
vector的 push_back + erase(remove_if) 往往更快
它的存在意义不是通用替代,而是精准解决一类低开销链式操作问题。用错地方,代价比想象中大。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











