C++20 视图(Views)与管道式编程:让集合操作更直观

在 C++20 中,std::ranges::views 为我们提供了一套强大的工具,让集合操作更加像函数式编程。通过视图(views)可以懒惰地处理数据,而不必立即产生中间容器,既节省内存,又提升性能。下面通过一个完整示例,演示如何使用视图链实现“从整数数组中筛选偶数,去除重复值,取平方后按升序排序”这一常见需求。

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

int main() {
    std::vector <int> data{ 5, 12, 12, 7, 8, 3, 8, 4, 12, 5 };

    // 1. 先把容器包装成视图
    auto view = data
        | std::ranges::views::filter([](int n){ return n % 2 == 0; })   // 只保留偶数
        | std::ranges::views::unique                                      // 去重
        | std::ranges::views::transform([](int n){ return n * n; })      // 平方
        | std::ranges::views::sort;                                      // 升序

    // 2. 由于视图是惰性求值,遍历一次即可得到结果
    std::cout << "Result: ";
    for (int x : view) {
        std::cout << x << ' ';
    }
    std::cout << '\n';

    // 3. 也可以直接拷贝到新的容器(如果需要实际存储)
    std::vector <int> result(view.begin(), view.end());
    std::cout << "Result vector: ";
    for (int x : result) std::cout << x << ' ';
    std::cout << '\n';

    return 0;
}

关键点拆解

  1. 懒惰求值
    views::filter, views::unique, views::transform, views::sort 都是懒加载,直到真正迭代时才执行。这样我们避免了多余的临时容器。

  2. 管道式写法
    通过 | 管道符将操作串联,使代码更简洁、易读。可以想象成数据流经过一系列变换节点。

  3. 去重视图
    views::uniquestd::unique 的区别在于它是视图级别的去重,要求输入必须是已排序的(或在视图链中使用 sort 前置)。如果不满足,可以在链前先调用 views::sort

  4. 视图与容器无关
    view 可以用于任何支持范围的算法,例如 std::ranges::for_each, std::ranges::accumulate 等,或者直接作为 `std::vector

    result(view.begin(), view.end());` 的源。
  5. 性能与内存
    传统做法往往需要先过滤到一个临时 vector,再去重,再平方,最后排序。每一步都产生新的容器,耗费时间和空间。使用视图链,所有操作在一次遍历中完成,显著降低开销。

扩展思考

  • 组合更多视图
    views::take, views::drop, views::reverse, views::filter 等都可以自由组合,构建更复杂的数据处理流水线。

  • 自定义视图
    C++20 允许自定义视图(custom view),只需实现 begin()end(),即可将自定义逻辑与标准视图无缝融合。

  • 与并行算法结合
    std::ranges::sort 默认使用内部的并行实现(若编译器支持),结合视图可在不改写代码的情况下获得并行性能提升。

结语

C++20 的 views 为集合处理带来了新的范式:声明式 + 懒惰 + 链式。它们让 C++ 既保留了语言本身的高性能,又兼具函数式语言的可读性。下次面对复杂的集合处理逻辑,试着把它拆分成一连串视图,再通过管道式编程把它们串起来,你会发现代码既简洁又高效。

发表评论