在 C++20 之前,使用 STL 进行数据处理往往需要配合 std::vector、std::transform、std::for_each 等工具链,加上一层复杂的迭代器和谓词编写,代码长度可观,易出错。C++20 通过引入 ranges 库,将容器、视图、算法等功能模块化,极大简化了数据处理流程。下面通过具体示例来说明 ranges 的优势,并演示如何在实际项目中使用。
1. 传统 STL 写法
#include <vector>
#include <algorithm>
#include <numeric>
#include <iostream>
int main() {
std::vector <int> data{1,2,3,4,5,6,7,8,9,10};
// 1. 过滤出偶数
std::vector <int> evens;
std::copy_if(data.begin(), data.end(), std::back_inserter(evens),
[](int x){ return x % 2 == 0; });
// 2. 平方
std::transform(evens.begin(), evens.end(), evens.begin(),
[](int x){ return x * x; });
// 3. 求和
int sum = std::accumulate(evens.begin(), evens.end(), 0);
std::cout << "sum = " << sum << std::endl;
return 0;
}
上述代码涉及三步处理:过滤、变换、聚合。每一步都需要显式写出迭代器、谓词或 lambda,阅读起来不够直观。
2. ranges 写法
#include <vector>
#include <ranges>
#include <numeric>
#include <iostream>
int main() {
std::vector <int> data{1,2,3,4,5,6,7,8,9,10};
using namespace std::ranges;
// 直接链式调用
auto sum = data | views::filter([](int x){ return x % 2 == 0; })
| views::transform([](int x){ return x * x; })
| views::fold(0, std::plus<>{});
std::cout << "sum = " << sum << std::endl;
return 0;
}
此写法把数据流式化,阅读顺序与业务逻辑保持一致:先过滤偶数,再平方,最后求和。views::filter、views::transform 是惰性视图,实际运算在管道终点 fold 触发时一次性完成。
3. 进一步优化:复用视图
如果在多个地方需要相同的过滤和变换操作,可以预先定义一个视图:
auto evens_and_square = views::filter([](int x){ return x % 2 == 0; })
| views::transform([](int x){ return x * x; });
auto sum = data | evens_and_square | views::fold(0, std::plus<>{});
这样做可降低重复代码,并在未来更改处理逻辑时只需改动一次。
4. 与并行化的结合
ranges 还可以与并行算法配合,进一步提升性能。只需在管道前加上 execution::par:
#include <execution>
auto sum = data | views::filter(... ) | views::transform(...)
| views::fold(0, std::plus<>{}, execution::par);
这里的 fold 支持并行聚合,底层会把数据拆分为若干块并行处理。对于大数据量的场景,性能提升非常显著。
5. 常见 pitfalls 与注意事项
- 惰性求值:ranges 的视图是惰性的,只有终止算子(如
fold、for_each等)才会触发实际计算。如果你误将视图直接输出,可能得到一个空值或不确定行为。 - 迭代器失效:当原始容器被修改(如插入、删除)时,已有的视图将失效。使用前最好确保容器不再变更,或重新生成视图。
- 视图链太长:虽然代码简洁,但过多嵌套视图会导致编译时间膨胀,且错误信息难以定位。适度拆分为几个可复用视图更易维护。
6. 结论
C++20 的 ranges 为容器与算法提供了更自然、更接近业务流程的组合方式。通过惰性视图与管道化写法,代码不仅更简洁,且易于维护。对于现代 C++ 项目,推荐在合适的场景下使用 ranges,特别是当需要频繁对容器进行过滤、变换、聚合等操作时。随着 C++23 的进一步扩展,ranges 的生态将更加完善,值得持续关注与实践。