c++oding="utf-8" ?>
std::forward_list仅适用于头部高频增删且内存极度敏感的场景;其size()不可靠,应使用empty();插入删除必须通过insert_after/erase_after配合before_begin();splice_after()支持o(1)物理拼接;一旦需尾部操作、随机访问、丰富排序或缓存友好性,就该换用其他容器。

std::forward_list 不适合需要频繁查长度、尾部操作或反向遍历的场景;它只在“头部高频增删 + 内存极度敏感”时才真正值得用。
为什么 std::forward_list::size() 不能信
标准允许 size() 是 O(1) 或 O(n),而 libstdc++(GCC 默认)压根不维护计数器,每次调用都遍历全链表。MSVC 和 libc++ 多数实现为 O(1),但可移植代码里不能依赖。
- 别写
if (fl.size() == 0)—— 改用fl.empty()(O(1),始终安全) - 别在循环里反复调用
std::distance(fl.begin(), fl.end()),一次就是 O(n),N 次就是 O(n²) - 真要长度且高频,自己加一个
size_t count手动同步,或直接换std::list
insert_after 和 erase_after 的合法参数边界
所有中间插入/删除必须通过 insert_after() 和 erase_after(),且参数不是“位置”,而是“前驱节点的迭代器”。错传 fl.end() 或无效迭代器会触发未定义行为。
-
fl.insert_after(fl.before_begin(), x)是头插的唯一安全写法 -
fl.erase_after(fl.before_begin())删首元素;传fl.begin()是错的 —— 它指向第一个有效节点,不是前驱 - 想删第 n 个元素?得先
auto it = std::next(fl.before_begin(), n - 1),再fl.erase_after(it) -
fl.erase_after(it, fl.end())可清空从it后开始的所有节点(等价于fl.clear())
splice_after() 是唯一能绕过拷贝的拼接方式
splice_after() 不构造、不析构、不移动元素,只改 next 指针,是真正 O(1) 的物理拼接。但它不像 std::list::splice() 那样灵活 —— 必须指定“拼到哪之后”,且不能自拼接。
-
dst.splice_after(pos, src):把整个src拼到dst的pos后,src变空 -
dst.splice_after(pos, src, it):只把src中it指向的单个节点移到dst的pos后 -
src和dst必须是同一类型,且不能是同一个对象(fl.splice_after(fl.before_begin(), fl)是未定义行为) - 拼接后,被移动节点在原容器中的迭代器立即失效,但新容器中可用
什么时候该放弃 std::forward_list
它不是 std::list 的轻量替代品,而是一个有明确取舍的专用工具。一旦出现以下任一情况,就该换容器:
- 需要
push_back()或访问back()——std::forward_list没有尾指针,尾部操作是 O(n) - 需要按索引取值(比如
fl[5])或随机迭代 —— 它不支持operator[]、at(),也不提供random_access_iterator - 需要
merge()、sort()以外的排序相关操作 —— 它只有sort()和unique()(且unique()要求已排序) - 实际 profile 显示缓存命中率没提升,反而因遍历增多拖慢 —— 单向链表对 CPU 缓存并不总是友好,尤其节点分散时
最常被忽略的一点:它没有 cbegin()/cend()(C++14 才加入),老代码里用 auto it = fl.begin() 配合 const 容器容易编译失败 —— 这种细节在嵌入式或长期维护项目里特别伤。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











