C++20引入的ranges库绝非简单的语法糖,而是对STL算法和迭代器体系的一次彻底重构。传统STL算法要求开发者手动传递begin/end迭代器对,这种模式存在几个根本性问题:
ranges通过引入视图(view)和范围概念(range concept)解决了这些问题。一个典型例子是传统写法与ranges写法的对比:
cpp复制// 传统STL
std::vector<int> data{1,2,3,4,5};
auto it = std::find_if(data.begin(), data.end(), [](int x){ return x%2==0; });
// ranges写法
auto result = data | std::views::filter([](int x){ return x%2==0; })
| std::views::take(3);
这种管道式组合(pipe composition)不仅减少了代码量,更重要的是通过延迟计算(lazy evaluation)避免了不必要的中间存储。视图对象在组合时只记录操作逻辑,直到最终需要结果时才执行计算。
ranges库构建在复杂的概念体系上,这些概念通过C++20的concept特性实现编译时约束。主要概念包括:
概念检查示例:
cpp复制template<std::ranges::input_range R>
void process(R&& r) {
// 确保R满足input_range要求
static_assert(std::ranges::view<R>, "需要视图类型");
// ...
}
视图是ranges库的核心抽象,常见视图类型包括:
| 视图类型 | 功能描述 | 复杂度保证 |
|---|---|---|
| filter | 条件过滤 | O(n) |
| transform | 元素转换 | O(1) |
| take | 取前N个元素 | O(1) |
| reverse | 反向遍历 | O(1) |
| join | 展平嵌套range | O(1) |
视图组合时的注意事项:
传统STL算法在ranges命名空间下都有对应版本,主要改进包括:
示例:带投影的排序
cpp复制struct Person {
std::string name;
int age;
};
std::vector<Person> people;
std::ranges::sort(people, {}, &Person::age); // 按age排序
创建符合view概念的类型需要:
示例:stride_view(间隔取值)
cpp复制template<std::ranges::input_range V>
class stride_view : public std::ranges::view_interface<stride_view<V>> {
V base_;
std::ranges::range_difference_t<V> stride_;
public:
// 迭代器实现...
auto begin() { /* 返回自定义迭代器 */ }
auto end() { /* 返回哨位 */ }
};
// 视图适配器辅助函数
inline constexpr auto stride = []<std::ranges::range R>(R&& r, auto n) {
return stride_view<std::views::all_t<R>>(
std::forward<R>(r), n);
};
虽然ranges抽象带来便利,但在性能关键路径需注意:
性能对比测试示例:
cpp复制// ranges版
auto result = data | views::filter(pred)
| views::transform(fn);
// 手写版
std::vector<decltype(fn(*data.begin()))> temp;
for(auto& x : data) {
if(pred(x)) temp.push_back(fn(x));
}
C++20协程可以与ranges结合创建生成器模式:
cpp复制std::generator<int> fibonacci() {
int a=0, b=1;
while(true) {
co_yield a;
std::tie(a,b) = std::pair{b, a+b};
}
}
// 使用
for(int i : fibonacci() | std::views::take(10)) {
std::cout << i << " ";
}
视图不拥有底层数据,必须注意源数据的生命周期:
cpp复制auto make_filtered() {
std::vector<int> data{1,2,3,4,5};
return data | std::views::filter([](int x){ return x%2==0; }); // 危险!
} // data被销毁,返回的视图悬垂
解决方案:
视图组合可能导致复杂类型:
cpp复制auto v = data | filter(pred1) | transform(fn) | filter(pred2);
// v的类型可能非常复杂
处理建议:
调试视图组合时:
结合执行策略实现并行处理:
cpp复制std::vector<int> data(1'000'000);
auto view = data | std::views::transform([](int x){ return x*x; });
std::for_each(std::execution::par, view.begin(), view.end(), process);
C++23的pattern matching可与ranges结合:
cpp复制auto describe = [](auto&& r) -> std::string {
using T = std::ranges::range_value_t<decltype(r)>;
if constexpr(std::integral<T>) {
return "integral range";
} else if constexpr(std::ranges::sized_range<decltype(r)>) {
return "sized range";
}
// ...
};
在编译时处理range特性:
cpp复制template<std::ranges::range R>
constexpr bool is_reversible() {
return std::ranges::bidirectional_range<R> &&
std::ranges::common_range<R>;
}
static_assert(is_reversible<std::vector<int>>());
代码规范:
测试策略:
兼容性处理: