std::forward_list没有size()是设计选择而非缺陷;判空必须用empty(),中间增删仅支持insert_after()和erase_after(),参数均指前驱节点。

std::forward_list 没有 size() 不是缺陷,是设计选择;想安全判空,必须用 empty();所有中间插入/删除都依赖 insert_after() 和 erase_after(),参数永远指“前驱节点”,不是目标节点本身。
为什么不能用 fl.size() == 0 判断是否为空
标准允许 size() 是 O(n),很多环境(如旧版 libstdc++、嵌入式裁剪 STL)仍实现为遍历全链表;更糟的是,某些旧实现甚至不定义该函数,调用直接编译失败或返回垃圾值。fl.empty() 才是唯一可移植、O(1)、 guaranteed defined 的方式。
- 别写
if (fl.size() == 0),哪怕它在你本地编译通过 - 别依赖
__size()等非标扩展——MSVC 可能有,libstdc++ 没 - 若封装类需频繁查长度,手动维护
size_t m_size,并在每次push_front()、insert_after()、erase_after()后同步增减
insert_after() 和 erase_after() 的参数到底指谁
这两个函数的操作对象永远是「参数迭代器所指节点的下一个节点」。这不是 API 设计疏漏,而是单向链表无法回溯的硬约束。
-
fl.insert_after(it, x):在it指向的节点之后插入x;it可以是fl.before_begin()(此时等价于头插),但绝不能是fl.end() -
fl.erase_after(it):删除it后面那个节点;传fl.before_begin()就是删首节点(等价于pop_front());传fl.end()是未定义行为 - 想删第 5 个元素?先
auto prev = std::next(fl.before_begin(), 4),再fl.erase_after(prev)—— 注意是 4,不是 5 - 遍历时边删边走:初始化
auto prev = fl.before_begin(),循环内用auto curr = std::next(prev)定位,匹配后执行prev = fl.erase_after(prev)跳过被删节点
什么时候必须用 splice_after() 而不是循环插入
splice_after() 是 std::forward_list 唯一真正 O(1) 的物理拼接操作:不调用构造/析构、不分配内存、只改 next 指针。任何用 insert_after() 循环模拟拼接的行为,都会触发 n 次内存分配和拷贝,彻底毁掉内存与性能优势。
-
dst.splice_after(pos, src):把整个src接到dst中pos后,src变为空;src和dst必须同类型且不能是同一对象(自拼接未定义) -
dst.splice_after(pos, src, it):只移动src中it所指单个节点 -
dst.splice_after(pos, src, first, last):拼接[first, last)范围(左闭右开) - 典型场景:LRU 缓存中把命中节点提到头部 →
fl.splice_after(fl.before_begin(), fl, it);任务队列分片迁移时整段切走
什么时候不该用 std::forward_list
它不是 std::list 的轻量替代品,而是一个有明确取舍的工具。一旦需求超出其能力边界,就该立刻换容器。
立即学习“C++免费学习笔记(深入)”;
- 需要频繁获取长度 → 改用
std::list或自己封装带计数器的forward_list - 需要尾部操作(
push_back()、pop_back())、反向遍历、随机访问(operator[]、at())→ 直接换std::vector或std::deque - 存的是大对象(如
std::string、1KB 结构体)→ 8 字节/节点的节省可忽略,别硬套 - 节点少(平均长度 ≤ 2)但操作频繁 → 指针操作开销可能反超缓存友好型连续结构


















