std::forward_list比std::list更省内存,因其单向链表结构使每节点仅存1个指针(而非2个),64位系统下每节点节省8字节;但仅当节点数量大且值类型很小时优势显著。

std::forward_list 为什么比 std::list 更省内存?
因为 std::forward_list 是单向链表,每个节点只存一个指针(指向下一节点),而 std::list 是双向链表,每个节点存两个指针(前驱 + 后继)。在 64 位系统上,这意味着每个节点少用 8 字节 —— 对大量小元素(如 int、char*)来说,内存节省非常可观。
但注意:这个优势只在「节点数量大」且「值类型本身很小」时明显。如果存的是 1KB 的结构体,那多 8 字节几乎可以忽略。
- 典型省空间场景:
std::forward_list<int></int>存 100 万个元素 → 比std::list<int></int>少约 7.6MB 内存 - 不省空间的假象:如果你用
std::forward_list<:string></:string>,而每个std::string已经在堆上分配了几十字节,那节点指针差异就微不足道了 - 别指望它“自动压缩”——它不管理元素内容,只减少链表结构开销
insert_after 和 erase_after 是唯一合法的修改方式
std::forward_list 没有 push_front / pop_front 以外的随机插入/删除接口;所有中间操作必须通过 iterator 调用 insert_after 或 erase_after。这是因为它的迭代器不支持 “--it”,无法定位前一个节点。
常见错误是想当然地写 lst.erase(it) —— 这会编译失败,正确写法是 lst.erase_after(prev_it),其中 prev_it 必须是待删节点的前一个迭代器。
立即学习“C++免费学习笔记(深入)”;
- 要删第一个元素?用
lst.pop_front() - 要删第 n 个?得先用
std::next(begin, n-1)找到前一个位置,再erase_after - 插入同理:
insert_after总是在给定迭代器所指节点「之后」插入,不是「之前」
没有 size() 成员函数:遍历时务必小心性能陷阱
std::forward_list::size() 在 C++11/C++14 中是 O(n),C++17 起才要求为 O(1) —— 但很多老项目或嵌入式 STL 实现(如 libstdc++ 旧版、某些裁剪版)仍不保证。如果你依赖 lst.size() > 0 做空检查,不如直接用 !lst.empty(),它永远是 O(1)。
更危险的是循环里反复调用 size():
for (size_t i = 0; i < lst.size(); ++i) { ... } // 千万别这么写!
这会让本该 O(n) 的遍历变成 O(n²)。
- 遍历请用范围 for 或手动迭代:
for (auto it = lst.begin(); it != lst.end(); ) - 需要长度?显式计数一次,或改用
std::vector(如果不需要频繁中间插入) - 确认你的 STL 版本是否真支持 O(1)
size():查__GLIBCXX__宏或测试std::is_constant_evaluated()不适用,得实测
移动语义支持弱:emplace_front 是唯一高效构造方式
std::forward_list 的 emplace 系列只有 emplace_front 和 emplace_after,没有 emplace_back(因为没尾指针)。而且由于单向特性,emplace_after 必须提供前驱迭代器,无法像 vector::emplace_back 那样“无脑追加”。
想避免拷贝构造?别用 push_front(T{}),改用 emplace_front(args...) 直接在节点内存中构造对象。
-
lst.push_front(std::string("hello"))→ 先构造临时std::string,再移动或拷贝进节点 -
lst.emplace_front("hello")→ 直接在节点内存上调用std::string构造函数,零额外开销 - 注意:
emplace_after参数顺序是(pos, args...),不是(args..., pos)—— 容易和insert混淆
真正省内存的前提是你清楚自己在放弃什么:双向遍历能力、常数时间 size 查询、直观的中间操作接口。一旦选了 std::forward_list,就得顺着它的约束写代码,而不是硬套 std::vector 或 std::list 的习惯。








