掌握 C++20 中的 ranges 与管道式操作

在 C++20 之前,对容器进行一系列转化、过滤、排序等操作时,往往需要写一堆嵌套的算法调用,或者手动写循环。C++20 引入了 ranges 库,让这些操作变得既简洁又高效。更进一步,ranges 还支持“管道式”语法,使代码像 Unix 管道一样清晰。本文将从概念、语法到实际案例,逐步剖析如何在 C++20 中使用 ranges 与管道式操作。

1. ranges 基础概念

1.1 什么是 range

在 C++20 之前,STL 的算法接受的是 迭代器区间(begin/end)。ranges 通过 range 抽象,把 容器、迭代器区间、生成器 等统一起来。一个 range 对象必须满足 begin()end() 成员函数,返回可比较、可解引用的迭代器。

1.2 views

views 是对原始 range 的一种“懒惰”视图。常见的 views 包括 std::views::filter, std::views::transform, std::views::take, std::views::drop, std::views::reverse, std::views::split, std::views::common 等。每个 view 都是一个高阶函数,返回一个新的 range。

2. 经典范例:筛选偶数并平方

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

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

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

    for (int v : result) {
        std::cout << v << ' ';
    }
    std::cout << '\n';
}

输出: 4 16 36 64 100
说明:filter 先筛选偶数,transform 再做平方。整个过程 懒执行,只有在遍历 result 时才会产生值。

3. 管道式语法(pipe)

C++20 引入了 | 运算符,使 range 的链式调用更自然。std::ranges::pipe 把一个视图函数与前一个 range 连接起来。上述例子正是使用了管道式语法。

小技巧:如果你不想每次都写 std::views::, 可以用 namespace rv = std::ranges::views; 进行别名。

4. 实际应用:从文件读取行并去重

#include <fstream>
#include <string>
#include <unordered_set>
#include <vector>
#include <ranges>
#include <algorithm>

int main() {
    std::ifstream fin("input.txt");
    if (!fin) return 1;

    std::unordered_set<std::string> seen;
    std::vector<std::string> uniq;

    auto lines = std::ranges::istream_view<std::string>(fin)
        | std::views::filter([&seen](auto const& s){ return seen.insert(s).second; });

    std::ranges::copy(lines, std::back_inserter(uniq));

    // 打印结果
    for (const auto& line : uniq) {
        std::cout << line << '\n';
    }
}

此代码从 input.txt 读取每一行,利用 unordered_set 进行去重,并在同一行实现 filter。

5. 使用 ranges 与管道式操作的优势

传统方式 C++20 ranges + 管道
代码冗长,易出错 简洁明了,易读易维护
需要多次遍历 懒执行,必要时才遍历
受限于已有算法 通过组合 views 可构建复杂流程
性能受迭代器解引用成本影响 通过 views::commonviews::all 控制迭代器类型

6. 注意事项

  1. 性能:虽然 ranges 设计为懒惰,但在极端性能敏感场景下,最好先测量与传统方法的差异。
  2. 迭代器失效:如果你对原始容器做了修改(插入/删除),需要重新创建 views。
  3. 自定义 view:如果需要更复杂的转换,可以继承 std::ranges::view_interface 并实现 beginend

7. 结语

C++20 的 ranges 与管道式操作为容器算法带来了新的表达方式。它们让代码更接近自然语言表达,减少了样板代码,让程序员把注意力集中在业务逻辑上。熟练掌握后,你会发现写 STL 代码从未如此优雅。祝你在 C++20 的世界里玩得开心!

发表评论