c++11 之前 std::list::size() 是 o(n),因标准未强制缓存大小,实现需遍历链表计数;c++11 起要求 o(1),但旧库如 libstdc++ 4.8 前仍为 o(n)。

std::list::size() 在 C++11 之前为什么是 O(N)
因为老标准(C++98/03)没强制要求 std::list 缓存大小,实现可以只维护头尾指针,每次调用 size() 就得遍历整个链表数节点。GCC 的 libstdc++ 4.8 之前、MSVC 2013 及更早版本都这么干——你写 my_list.size() == 0,背后可能扫了上万节点。
常见错误现象:for (int i = 0; i 这种循环在大列表上直接卡死,实际复杂度变成 O(N²)。
- 别用
size()做循环条件,改用empty()判断是否为空 - 需要长度时,自己维护一个
size_t count变量,增删节点时同步更新 - 升级编译器或标准库前,用
std::chrono测一下size()耗时,尤其在性能敏感路径
C++11 起 size() 变成 O(1) 的前提是啥
C++11 标准把 size() 的时间复杂度明确改成 O(1),但这是“强约束”——所有合规实现必须缓存大小。不过,这不意味着你只要写 -std=c++11 就一定安全。
关键看标准库实现是否真正遵守了该要求:
立即学习“C++免费学习笔记(深入)”;
- libstdc++:从 GCC 4.9 开始(对应 libstdc++ 6.0+)才真正让
std::list::size()是 O(1) - libc++(Clang 默认):从一开始(LLVM 3.1+)就满足,且额外做了小优化,比如空列表的
size()直接返回字面量 - MSVC:从 Visual Studio 2015(_MSC_VER >= 1900)起,
std::list才带 size 缓存
如果你用的是 GCC 4.8.5(RHEL7 默认),即使加了 -std=c++11,size() 还是 O(N) —— 标准变了,但实现没跟上。
如何检测当前环境下的 list::size() 真实复杂度
不能只看编译选项或文档,得实测。最直接的办法是构造一个超大 std::list(比如 1e6 个元素),反复调用 size() 并计时,再和 empty() 对比。
auto start = std::chrono::steady_clock::now();
for (int i = 0; i < 10000; ++i) {
volatile auto s = my_list.size(); // 防止被优化掉
}
auto end = std::chrono::steady_clock::now();
如果耗时随 list 大小线性增长,说明仍是 O(N);如果基本恒定(几纳秒级),那就是 O(1)。
- 注意关掉编译器优化(
-O0)再测,否则size()可能被常量折叠 - 某些调试构建下,标准库会插入额外检查,导致
size()变慢,不代表发布版行为 - 别依赖
__cplusplus宏判断——它只告诉你编译标准,不反映实际库行为
替代方案:什么时候该彻底避开 size()
即使 size() 是 O(1),它仍涉及一次成员变量读取 + 返回,而 empty() 通常内联为单条比较指令(如 head == nullptr)。在极致性能场景(比如 lock-free 数据结构内部、高频回调),这点差异会被放大。
- 判断是否为空,永远优先用
empty(),而不是size() == 0 - 需要迭代全部元素时,用范围 for 或
begin()/end(),别用索引 +size() - 如果逻辑真依赖确切长度(比如分块处理),且列表生命周期可控,考虑换用
std::vector或带 size 缓存的自定义链表
最麻烦的情况是跨团队协作代码:你以为用了 C++17 和 libc++,结果 CI 用的是旧版 GCC 镜像——size() 的行为差异可能藏在某次发布后才暴露。这类隐性依赖,比语法错误更难定位。










