使用C++20 Range Views 简化容器遍历与变换

在 C++20 中引入的 Range Views 为容器遍历和变换提供了一套无缝、懒执行的工具链。相比传统的迭代器循环,Views 让代码更具可读性、可组合性,并在内部保持了高效的延迟求值。本文将从基础概念入手,示例演示常用 Views 的使用方式,并给出实际场景中的最佳实践建议。


1. 视图(View)是什么?

视图是对容器或范围的非所有者包装,它不存储数据,而是对底层元素进行“视图化”处理。典型的操作包括:过滤 (filter)、映射 (transform)、切片 (slice)、反向 (reverse)、组合 (join) 等。所有这些操作都是 懒惰 的,只有在真正需要遍历时才会触发计算。

视图 功能 延迟求值
std::views::filter 过滤元素
std::views::transform 映射元素
std::views::take 截取前 N 个元素
std::views::drop 跳过前 N 个元素
std::views::reverse 逆序遍历
std::views::stride 步长访问

注意:Views 的定义不需要包含头文件 `

`;但要使用 `std::views` 命名空间,需要 `#include `。

2. 基础示例

2.1 过滤奇数

#include <iostream>
#include <vector>
#include <ranges>

int main() {
    std::vector <int> data{1, 2, 3, 4, 5, 6};

    auto odd = data | std::views::filter([](int x){ return x % 2 == 1; });

    for (int n : odd)
        std::cout << n << ' ';
    // 输出: 1 3 5
}

2.2 映射平方

auto squares = data | std::views::transform([](int x){ return x * x; });

for (int n : squares)
    std::cout << n << ' ';

2.3 组合过滤与映射

auto even_squares = data
    | std::views::filter([](int x){ return x % 2 == 0; })
    | std::views::transform([](int x){ return x * x; });

for (int n : even_squares)
    std::cout << n << ' ';   // 输出: 4 16 36

3. 延迟求值的好处

  • 性能:仅在遍历时执行操作,避免不必要的临时对象创建。
  • 内存占用:不产生额外容器,减少内存使用。
  • 组合灵活:通过管道符 | 可以自由组合,生成新的视图链。

实践:如果你需要对同一容器多次做不同的筛选/变换,最好使用 Views 生成一个临时范围再遍历,而不是每次都复制或构造临时容器。


4. 与传统算法的对比

传统方式 Views 方式
std::copy_if + std::transform | std::views::filter | std::views::transform
需要临时容器 只需一次遍历
代码可读性低 代码直观、链式写法

5. 常见错误与调试技巧

  1. **忘记 `#include

    `** 编译器报错:`’views’ is not a member of ‘std’`。 解决:在文件顶部加 `#include `。
  2. 闭包捕获过大导致不必要的复制
    例如 auto big_view = data | std::views::transform([](const std::string& s){ return s; });
    这里闭包捕获的是 data 的引用,导致每次访问都获取 data。若闭包捕获了大对象,建议使用 auto&&std::move 传递。

  3. 对非随机访问容器使用 reverse 需要注意
    std::vector 支持 reverse,但 std::list 不支持。确保容器满足 RandomAccessRange


6. 进阶技巧

6.1 自定义 View

template<std::ranges::input_range R>
auto my_view(R&& r) {
    auto it = std::ranges::begin(r);
    auto end = std::ranges::end(r);
    return std::views::filter([&](auto x){ return *it++ == x; });
}

6.2 使用 std::views::join 合并多容器

std::vector<std::vector<int>> vv{{1,2},{3,4,5},{6}};
auto joined = vv | std::views::join;
for (int n : joined)
    std::cout << n << ' ';   // 输出: 1 2 3 4 5 6

7. 总结

C++20 的 Range Views 提供了一种极具表达力且高效的方式来处理容器数据。它们:

  • 无所有权:不拷贝、不占用额外内存。
  • 懒求值:仅在需要时才执行。
  • 链式组合:代码简洁、可读性强。

在日常项目中,建议:

  1. 优先使用 Views 进行过滤、映射、切片等操作。
  2. 对于需要多次访问同一视图的情况,缓存 生成的视图。
  3. 结合 C++20 三点改进std::ranges::views::iota, std::views::take_while, std::views::drop_while 等,进一步简化逻辑。

希望本文能帮助你在 C++20 项目中更好地利用 Range Views,实现代码既优雅又高效。祝编码愉快!

发表评论