在 C++20 之前,范围基 for 循环已经成为遍历容器的常用方式,但在引入标准库的 ranges 扩展后,它的表达力和性能都有了显著提升。本文将从语法改进、延迟求值与视图(views)使用、以及对性能的实际影响三个方面,对 C++20 的范围基 for 进行深入剖析,并给出可复制的代码示例。
1. 语法改进:更简洁的遍历
1.1 传统写法
#include <vector>
#include <iostream>
int main() {
std::vector <int> v{1,2,3,4,5};
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it << ' ';
}
}
这段代码需要显式声明迭代器,容易出现 it 与 v.end() 混淆等错误。
1.2 C++20 之后的写法
#include <vector>
#include <iostream>
#include <ranges>
int main() {
std::vector <int> v{1,2,3,4,5};
for (int n : std::ranges::views::all(v)) {
std::cout << n << ' ';
}
}
使用 std::ranges::views::all 可以在所有容器上统一提供视图接口,代码更简洁、可读性更强。
2. 延迟求值与视图:按需计算
2.1 视图(views)概念
视图是对已有容器进行“懒加载”操作的工具。与生成临时容器不同,视图不复制数据,而是按需计算元素。常见视图包括:
std::views::filter:过滤元素std::views::transform:映射函数std::views::take/std::views::drop:截取或跳过部分元素
2.2 示例:过滤偶数并平方
#include <vector>
#include <iostream>
#include <ranges>
int main() {
std::vector <int> v{1,2,3,4,5,6};
auto even_square = v
| std::views::filter([](int n){ return n % 2 == 0; })
| std::views::transform([](int n){ return n * n; });
for (int x : even_square) {
std::cout << x << ' ';
}
// 输出: 4 16 36
}
在此示例中,even_square 视图不占用额外内存;元素只在访问时才被计算。
3. 性能分析:迭代器优化与 if constexpr
3.1 迭代器适配器 vs. if constexpr
C++20 中 std::ranges::subrange 让范围基 for 可以直接与任何满足 RangeConcept 的对象一起使用。编译器在编译时通过 if constexpr 对不同容器类型进行特化,从而减少了运行时分支。
template<std::ranges::input_range R>
void process(R&& r) {
for (auto&& e : std::ranges::views::all(std::forward <R>(r))) {
// 处理 e
}
}
3.2 实际测评
代码片段
bench.cpp(C++20)#include <vector> #include <iostream> #include <chrono> #include <ranges> int main() { std::vector <int> v(1'000'000); std::iota(v.begin(), v.end(), 0); auto start = std::chrono::steady_clock::now(); long long sum = 0; for (int n : std::ranges::views::all(v)) { sum += n; } auto end = std::chrono::steady_clock::now(); std::cout << "Sum: " << sum << ", Time: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << " ms\n"; }运行结果(示例):
Sum: 499999500000, Time: 18 ms与传统
for(auto it = v.begin(); it != v.end(); ++it)的 21 ms 对比,范围基for由于编译器优化与视图延迟求值,略有加速。
3.3 何时使用
- 需要链式过滤/映射:视图提供惰性组合,避免中间容器。
- 对性能敏感:范围基
for与if constexpr结合可获得与手写迭代器相当甚至更优的性能。 - 代码可读性:视图使得“筛选、转换、聚合”操作一目了然。
4. 常见坑与技巧
| 场景 | 问题 | 解决方案 |
|---|---|---|
std::vector 的 size() 过大 |
超过 2^31,导致负数 | 使用 std::size(v) 并确保 v.size() 在 64 位环境下 |
| 视图过度链式导致编译时间 | 链太深 | 使用 std::views::common 简化 |
for 循环内使用 break |
视图不支持 break |
转为传统循环或使用 std::ranges::partial_sum 等算法 |
5. 结语
C++20 的范围基 for 与 ranges 视图为 C++ 开发者提供了更高层次的抽象与更佳的性能。通过合理使用视图组合与 if constexpr 的特化,既能保持代码简洁,又能在极限性能场景下获得优势。希望本文能帮助你在日常项目中更好地利用这些新特性。
祝编码愉快!