c++oding="utf-8" ?>
std::forward_list是为内存敏感场景定制的单向链表,无size()、不支持push_back(),erase_after()删除后继节点,splice_after()是唯一o(1)高性能操作。

std::forward_list 不是“轻量版 std::list”,它是为特定内存敏感场景定制的单向链表,用错地方反而比 vector 还慢;它没有 size()、不能 push_back()、删元素必须靠 erase_after() —— 这些不是缺陷,而是设计取舍。
为什么 std::forward_list 没有 size() 成员函数
标准明确禁止它缓存长度:因为每次插入/删除都要维护计数器,哪怕只是 +1/-1,也违背“零开销抽象”原则。它的节点结构里真的只有 next 指针和数据,没地方塞 size 字段。
常见错误:
- 在循环里反复调用
std::distance(fl.begin(), fl.end())判断长度 → O(n²) 性能雪崩 - 误以为 C++17 起
size()就变快了 → 标准只允许实现为 O(1),不强制;libstdc++ 和 libc++ 默认仍是 O(n) - 用
fl.empty()替代fl.size() == 0→ 正确,且是唯一 O(1) 的空判断方式
insert_after() 和 before_begin() 是头插的唯一安全路径
push_front() 在部分旧标准文档或编译器(如 GCC 4.8)中曾短暂存在,但 C++11 正式版已移除;现在合法且推荐的头部插入方式只有两种:
-
fl.insert_after(fl.before_begin(), x)—— 显式、可读,但多一次临时对象构造 -
fl.emplace_front(x)—— 更优,直接就地构造,避免拷贝;注意它仅接受一个参数(C++11 起),不支持 initializer_list 形式
别写 fl.insert_after(fl.begin(), x) 去头插:如果链表为空,fl.begin() == fl.end(),传给 insert_after() 是未定义行为。
erase_after() 的迭代逻辑陷阱
erase_after(it) 删的是 it 后面那个节点,不是 it 自身指向的节点 —— 这和 std::vector::erase() 完全相反。
典型翻车现场:
- 想删第 3 个元素?得先走到第 2 个位置:
auto it = std::next(fl.before_begin(), 2),再fl.erase_after(it) - 边遍历边删时,误用
++it继续循环 → 跳过被删节点的后继,导致漏删 - 正确写法是利用
erase_after()返回值:it = fl.erase_after(it),它返回被删节点的下一个有效迭代器
注意:fl.erase_after(fl.before_begin()) 等价于 pop_front();但 fl.erase_after(fl.end()) 是未定义行为,永远别这么干。
splice_after() 是唯一真正 O(1) 的高性能操作
它不拷贝、不构造、不析构,只改几个指针 —— 这是 std::forward_list 不可替代的核心价值。
使用前提很硬:
- 目标位置必须是有效
before_begin()或中间某节点的迭代器(不能是end()) -
src和dst必须同类型,且不能是同一个容器(自拼接未定义) -
dst.splice_after(pos, src)→ 整个src拼到pos后,src变空 -
dst.splice_after(pos, src, it)→ 把src中it指向的单个节点移到dst的pos后
如果你需要频繁重组链表段(比如 LRU 缓存淘汰、事件队列迁移),splice_after() 是唯一值得为 forward_list 留下的理由;其他所有操作,几乎都能被 std::vector 或 std::list 更直观地替代。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











