应使用 empty() 判空而非 size(),因多数环境下 size() 非 O(1) 且可能未定义;erase_after() 不接受 end() 迭代器;需长度时应手动维护计数器或单次调用 distance。

别用 size() 判空,也别传 end() 给 erase_after() —— 这两类操作在多数真实项目环境(GCC + libstdc++、嵌入式裁剪 STL、C++14 旧项目)中会直接导致未定义行为或性能雪崩。
为什么 fl.size() == 0 是危险写法
libstdc++ 当前主流版本(包括 GCC 12/13 默认 STL)仍未实现 size() 的 O(1) 版本,每次调用都是 std::distance(fl.begin(), fl.end()) 全链表遍历。更糟的是:某些裁剪版 STL 根本不提供该函数,编译失败;部分旧实现返回未初始化值,if (fl.size() == 0) 可能恒为 false 或触发 SIGSEGV。
- 判空唯一可移植写法是
fl.empty(),它始终 O(1) 且 guaranteed defined - 若真需长度且只用一次:
auto len = std::distance(fl.begin(), fl.end()),结果存局部变量复用 - 高频查长度(如 LRU 缓存计数器):必须手动维护
size_t m_size,并在每次push_front()、insert_after()、erase_after()后同步增减 - 禁止在循环条件里写
for (size_t i = 0; i —— O(n²) 性能陷阱
insert_after() 和 erase_after() 的参数永远指“前驱”
这两个函数的操作对象不是你传进去的迭代器本身,而是它「后面那个节点」。这是单向链表无法反推前驱的硬约束,不是 API 设计疏漏。传错就删错、插错,且无编译期提示。
- 头插安全写法:
fl.push_front(x)或fl.insert_after(fl.before_begin(), x);绝不能用fl.insert_after(fl.begin(), x) - 删首节点必须用
fl.erase_after(fl.before_begin())或fl.pop_front();传fl.begin()实际删的是第二个元素 - 删第 n 个元素(n ≥ 1):
auto prev = std::next(fl.before_begin(), n - 1),再fl.erase_after(prev)—— 注意是n - 1,不是n - 遍历时删匹配节点:初始化
auto prev = fl.before_begin(),循环内用auto curr = std::next(prev)定位,匹配后执行prev = fl.erase_after(prev)(它返回被删节点之后的有效迭代器) - 绝对禁止传
fl.end()给任何insert_after()/erase_after()/std::next()—— 未定义行为,GCC/Clang 常静默崩溃
为什么 splice_after() 是唯一不可替代的核心操作
splice_after() 是 std::forward_list 中唯一真正 O(1) 的物理拼接操作:不调用构造/析构、不分配内存、不拷贝数据,只修改 next 指针。所有用 insert_after() 循环插入、或用 push_front() 模拟拼接的方式,都会彻底丧失其内存与性能优势。
立即学习“C++免费学习笔记(深入)”;
- 整链迁移:
dst.splice_after(pos, src),执行后src变为空容器,dst在pos后接入全部原src节点 - 单节点迁移:
dst.splice_after(pos, src, it),仅将src中it所指节点移至dst的pos之后;it在src中立即失效 - 范围迁移:
dst.splice_after(pos, src, first, last),拼接[first, last)左闭右开区间 - 典型场景:LRU 缓存命中时把节点提到头部 →
fl.splice_after(fl.before_begin(), fl, it);任务队列分片迁移时整段切走,避免逐个构造和析构
真正省内存的场景非常窄:64 位下每节点省 8 字节(少一个 prev 指针),只有当节点数量极大(>10⁵)、值类型极小(int、char*)、且操作模式严格限定为前向流式处理 + 高频头插/头删 + 完全不查长度时,才值得用。其他情况硬套,只会换来更难 debug 的未定义行为和维护成本。


















