std::distance在非随机访问迭代器上为O(n)导致性能反常,应优先用容器的O(1) size()成员函数;仅当仅有迭代器而无容器时才必须使用。

std::distance 为什么有时慢得反常
std::distance 的行为取决于迭代器类别:对 RandomAccessIterator(如 std::vector::iterator)是 O(1),直接用减法;对 ForwardIterator 或更低(如 std::list::iterator、std::unordered_map::iterator)则是 O(n),靠反复 ++ 计数。这意味着在 std::list 上调用 std::distance(begin(), end()) 实际会遍历整个链表——不是“查长度”,而是“数一遍”。
常见误用场景:
- 在循环里反复调用
std::distance(it, end())判断剩余元素个数(性能雪崩) - 把
std::distance当作通用“容器大小”替代品,却没注意底层迭代器类型 - 对输入迭代器(如
std::istream_iterator)使用,导致未定义行为(它不支持多遍遍历)
比 std::distance 更快的容器元素个数获取方式
绝大多数标准容器提供 size() 成员函数,时间复杂度为 O(1),且语义明确——返回当前元素个数,不依赖迭代器类型。
适用情况:
立即学习“C++免费学习笔记(深入)”;
-
std::vector、std::deque、std::string:始终有size(),且稳定可靠 -
std::list、std::forward_list(C++11 起)、std::set、std::map等:也提供size(),尽管内部结构非随机访问 -
std::array:size()是 constexpr,编译期可知
例外:std::forward_list 在 C++11 中曾不保证 size() 为 O(1),但所有主流实现(libstdc++、libc++、MSVC STL)都已支持常数时间 size();C++17 起标准强制要求其为 O(1)。
什么时候非用 std::distance 不可
当只有两个迭代器、且无法访问容器本身时,std::distance 是唯一选择。典型场景包括:
- 泛型算法中只接收迭代器对(如自定义
my_find_if(first, last, pred)),需计算子范围长度 - 处理
std::string_view或 C 风格字符串视图,用begin()/end()构造后求长度 - 与
std::istream_iterator配合做有限读取(需确保输入迭代器可步进,且只走一次)
注意:对 std::basic_string 迭代器调用 std::distance 没问题,但若已有 string 对象,直接用 s.size() 更直白、无歧义。
std::distance 的 SFINAE 安全性与 C++20 替代方案
C++17 前,std::distance 对不满足 InputIterator 要求的类型(如裸指针传错类型)可能触发硬错误;C++17 起多数实现改用约束,使其参与 SFINAE,便于写泛型代码。
C++20 引入 std::ranges::distance,行为更一致:
- 对所有 range(包括
std::views::filter等惰性 view)可用 - 若 range 满足
sized_range(如大多数容器),则直接调size(),不碰迭代器 - 否则退化为迭代计数,但接口统一
不过,若你只是想获取容器元素个数,仍应优先写 c.size()——它不引入额外依赖,不触发 ADL,也不受迭代器类别拖累。
真正容易被忽略的是:std::distance 测量的是“迭代器可到达的步数”,不是“容器逻辑大小”。比如对 std::vector 的子范围 [begin()+2, begin()+5),std::distance 返回 3,这没错;但若误以为它能代替 vec.size(),就混淆了“范围长度”和“容器容量”两个概念。










