std::forward_list 的 empty() 是唯一安全的空容器判断方式,因 size() 在 libstdc++ 中为 O(n) 且旧版可能未定义或返回垃圾值;empty() 则是标准强制要求 O(1) 且保证可用。

std::forward_list 不是 std::list 的轻量替代品,硬套会出未定义行为;它只在「大量小节点 + 高频头插/头删 + 完全不查长度」三者同时成立时才真正省内存、真高效。
为什么 fl.empty() 是唯一安全的空容器判断方式
libstdc++(GCC 默认 STL)至今未维护计数器,fl.size() 就是 std::distance(fl.begin(), fl.end()),纯 O(n) 遍历。更危险的是:某些旧版实现甚至不定义 size(),调用直接编译失败;或返回垃圾值、抛异常。而 empty() 是标准强制要求 O(1) 且 guaranteed defined 的接口。
- 永远写
if (fl.empty()),别碰if (fl.size() == 0)或if (!fl.size()) - 若需长度且仅用一次:
auto len = std::distance(fl.begin(), fl.end()),结果缓存为局部变量复用 - 若高频访问长度(如 LRU 缓存计数),手动维护
size_t count,并在每次push_front()、insert_after()、erase_after()后同步增减 - 禁止在循环条件中写
for (size_t i = 0; i —— 这会让 O(n) 遍历退化成 O(n²)
insert_after() 和 erase_after() 的参数永远指“前驱”,不是目标节点
这是单向链表无法回溯的硬约束,不是 API 设计疏漏。传错迭代器(比如传 fl.begin() 给 erase_after())会删错位置,90% 以上导致未定义行为。
- 头插唯一安全写法:
fl.push_front(x)或fl.insert_after(fl.before_begin(), x);别用fl.insert_after(fl.begin(), x) - 删首节点必须用:
fl.pop_front()或fl.erase_after(fl.before_begin());传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()给任何函数 —— 未定义行为,GCC/Clang 可能静默崩溃
splice_after() 是唯一不可替代的 O(1) 物理拼接操作
splice_after() 不调用构造/析构、不分配内存、不拷贝数据,只改 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)左闭右开区间;注意last必须可从first到达,且不能是src.end() - 典型场景:LRU 缓存命中节点提到头部 →
fl.splice_after(fl.before_begin(), fl, it);任务队列分片迁移时整段切走
最容易被忽略的一点:std::forward_list 的内存节省只有在节点数量大、值类型小(如 int、char*)、且 allocator 对齐未放大结构体时才显著;若存的是 std::string 或 KB 级结构体,8 字节/节点的差异可忽略。用 sizeof 实测你的实际节点大小,别依赖文档或经验。


















