在 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
}
关键点
- 懒惰求值:
pipeline并不会立即执行。只有当我们遍历pipeline.begin()时,过滤和变换才会按需进行。 - 一次性链式:
|连接的视图在语义上是“先过滤再变换”。 - 类型安全:闭包自动推导类型,无需显式
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::filter、transform 的自定义版本。
6. 性能注意
- 视图本身不拷贝,仅在迭代时计算。
- 谓词和闭包应保持轻量;如果是昂贵操作,可考虑在视图前做一次预处理。
- 大容器:若数据量巨大,最好把视图链与
std::vector的reserve结合,避免频繁扩容。
7. 结语
std::ranges 为 C++20 引入的强大工具,让链式过滤与变换不再是繁琐的多行代码。通过视图与管道语法,既保持了 STL 的通用性,又提升了代码的可读性和可维护性。建议在日常开发中积极尝试,逐步迁移已有项目,以充分利用这套新特性。
祝编码愉快 🚀