C++20 范围视图(Ranges)实用技巧

C++20 中引入的范围(Ranges)库为我们提供了一种更优雅、更安全的方式来处理序列数据。与传统的迭代器和算法组合相比,范围视图(views)能够以懒加载、链式组合的形式表达复杂的数据转换,极大地提升代码可读性与可维护性。下面从基础用法到高级技巧,逐步展开 C++20 范围视图的实战指南。


1. 范围视图的基本概念

  • View:对序列的只读、懒惰的视图。不会产生副本,而是延迟计算结果。
  • ViewAdaptor:一种包装器,将已有容器或视图转化为另一种视图。
  • Range:任何支持 begin()end() 的对象。
#include <ranges>
#include <vector>
#include <iostream>

std::vector <int> vec{1,2,3,4,5,6,7,8,9,10};

auto even = vec | std::views::filter([](int x){ return x % 2 == 0; });
for (int n : even) std::cout << n << ' ';

输出:2 4 6 8 10


2. 常用视图适配器

适配器 说明 示例
std::views::filter 根据谓词过滤元素 vec | std::views::filter([](int n){ return n>5; })
std::views::transform 对每个元素做变换 vec | std::views::transform([](int n){ return n*n; })
std::views::take 取前 N 个元素 vec | std::views::take(3)
std::views::drop 跳过前 N 个元素 vec | std::views::drop(2)
std::views::reverse 逆序遍历 vec | std::views::reverse
std::views::unique 去重 sorted_vec | std::views::unique
std::views::zip 并行遍历多个序列 std::views::zip(a,b)

2.1 组合使用

auto result = vec
    | std::views::filter([](int n){ return n % 3 == 0; })
    | std::views::transform([](int n){ return n/3; })
    | std::views::take(4);

for (int x : result) std::cout << x << ' ';

3. 创建自定义视图

如果标准视图不满足需求,可以自定义一个视图。关键步骤:

  1. 定义视图结构:继承 std::ranges::view_interface,实现 begin(), end().
  2. 提供 size():可选,但有利于 std::ranges::sized_range 特性。
  3. 使用 std::ranges::subrange:简化迭代器包装。
#include <ranges>
#include <vector>

template<std::ranges::input_range R>
class square_view : public std::ranges::view_interface<square_view<R>> {
    R base_;
public:
    explicit square_view(R r) : base_(std::move(r)) {}

    auto begin() { return std::views::transform(base_, [](auto x){ return x*x; }).begin(); }
    auto end()   { return std::views::transform(base_, [](auto x){ return x*x; }).end(); }
};

template<std::ranges::input_range R>
auto operator|(R&& r, std::type_identity_t<square_view<R>> v)
{
    return square_view <R>(std::forward<R>(r));
}

用法:

std::vector <int> v{1,2,3,4};
for (int n : v | square_view{}) std::cout << n << ' ';  // 1 4 9 16

4. 视图与算法的协同

虽然范围视图是懒加载的,但与标准算法配合使用时,它们会自动展开为高效的迭代器。

#include <algorithm>

auto sum = std::accumulate(
    vec | std::views::filter([](int n){ return n%2==0; }),
    0
);

如果你需要在视图链中加入 std::views::common,可确保在算法需要 begin()end() 的时候得到可用的迭代器。


5. 性能注意事项

  • 懒计算:每次迭代都会重新计算所有适配器,可能导致多余计算。若需要多次访问,可先 to_vector()to_array().
  • 链式视图:过深的链式调用会导致额外的迭代器包装。若性能敏感,可将中间结果缓存为 std::vector
  • 并行算法:从 C++20 起,std::ranges::parallel 适配器可与视图一起使用。
auto max = std::ranges::max(
    vec | std::views::transform([](int n){ return n*n; }),
    std::execution::par
);

6. 小技巧汇总

技巧 说明
链式 takedrop vec | std::views::drop(3) | std::views::take(5) 等价于 vec[3:8]
多维数据 flatten std::views::join 可将二维容器变为一维
索引访问 std::views::iota 生成可迭代索引序列
组合 ziptransform std::views::zip(a,b) | std::views::transform([](auto&& pair){ return pair.first + pair.second; })

7. 结语

C++20 的范围视图为我们提供了强大且简洁的方式来处理序列数据。掌握视图适配器的组合与自定义,将使代码更加优雅、易读。实践中,记得关注性能开销,必要时缓存中间结果。希望本篇文章能帮助你在 C++20 之旅中更上一层楼。祝编码愉快!


发表评论