**如何在 C++20 中使用 std::ranges 实现链式过滤和变换**

在 C++20 里,std::ranges 为标准库提供了一套强大的管道式算法接口,使得链式操作既简洁又高效。本文将演示如何用 std::ranges::view 实现“先过滤再变换”这一常见需求,并讨论与旧式 std::transform / std::copy_if 的区别。


1. 背景

传统的 STL 用法通常需要多行代码完成一个链式操作,例如:

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

std::vector <int> result;
std::copy_if(input.begin(), input.end(),
             std::back_inserter(result),
             [](int n){ return n % 2 == 0; });

std::transform(result.begin(), result.end(),
               result.begin(),
               [](int n){ return n * n; });

这段代码先把偶数筛出来,再把每个数平方。实现起来冗长且易错。C++20 的 std::ranges 让我们能一次性表达整个流程,代码更直观。


2. 基本概念

  • 视图(view):对容器的懒惰、可迭代的“窗口”。
  • 管道(pipeline):使用 | 操作符将视图与算法串联。
  • 闭包(closure):由算法返回的可调用对象,用于进一步组合。

常用视图

视图 用途 典型语法
std::views::filter 过滤元素 input | std::views::filter(predicate)
std::views::transform 变换元素 input | std::views::transform(func)
std::views::take 取前 N 个 input | std::views::take(n)
std::views::drop 跳过前 N 个 input | std::views::drop(n)

3. 示例:筛选偶数并平方

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

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

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

    // 将结果收集到新的 vector
    std::vector <int> result(pipeline.begin(), pipeline.end());

    for (int v : result)
        std::cout << v << ' ';   // 输出: 4 16 36 64 100
}

关键点

  1. 懒惰求值pipeline 并不会立即执行。只有当我们遍历 pipeline.begin() 时,过滤和变换才会按需进行。
  2. 一次性链式| 连接的视图在语义上是“先过滤再变换”。
  3. 类型安全:闭包自动推导类型,无需显式 auto 或模板参数。

4. 与旧式 STL 的对比

场景 C++20 代码 传统 STL 代码
简洁性 1 行 auto pipeline = data | std::views::filter(... ) | std::views::transform(...); 多行 copy_if + transform
迭代器 pipeline.begin() data.begin() / result.begin()
性能 避免不必要拷贝,视图是“只读” 需要中间容器
可读性 直观表达“先过滤,再变换” 逻辑嵌套,易误读

5. 高级技巧

5.1 组合多个视图

auto processed = data | std::views::filter([](int n){ return n > 5; })
                     | std::views::transform([](int n){ return n + 1; })
                     | std::views::take(3);

5.2 用 std::ranges::for_each 替代手写循环

std::ranges::for_each(processed, [](int v){ std::cout << v << '\n'; });

5.3 自定义视图

如果需要更复杂的状态机,可继承自 std::ranges::view_interface 或使用 std::ranges::views::filtertransform 的自定义版本。


6. 性能注意

  • 视图本身不拷贝,仅在迭代时计算。
  • 谓词和闭包应保持轻量;如果是昂贵操作,可考虑在视图前做一次预处理。
  • 大容器:若数据量巨大,最好把视图链与 std::vectorreserve 结合,避免频繁扩容。

7. 结语

std::ranges 为 C++20 引入的强大工具,让链式过滤与变换不再是繁琐的多行代码。通过视图与管道语法,既保持了 STL 的通用性,又提升了代码的可读性和可维护性。建议在日常开发中积极尝试,逐步迁移已有项目,以充分利用这套新特性。

祝编码愉快 🚀

发表评论