C++20 中 std::ranges 的实战技巧

在 C++20 标准发布后,标准库中的 ranges 模块为容器的操作提供了更直观、更灵活的方式。相比传统的迭代器和算法组合,ranges 让代码更接近自然语言,且具备更好的可组合性和延迟求值特性。本文将从实战角度,演示如何在日常项目中应用 std::ranges,包括切片、视图、管道语法以及自定义适配器。

1. 什么是 ranges

ranges 主要由两部分构成:视图(view)适配器(adaptor)。视图是一种轻量级的“虚”容器,它不持有数据,而是通过组合已有容器和适配器实现惰性求值;适配器则是对容器或视图进行变换、筛选、分组等操作的工具。

2. 基础使用:切片与过滤

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

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

    // 取前四个元素
    auto first_four = vec | std::ranges::views::take(4);
    for (auto v : first_four) std::cout << v << ' ';   // 输出 1 2 3 4

    // 过滤偶数
    auto evens = vec | std::ranges::views::filter([](int x){ return x % 2 == 0; });
    for (auto v : evens) std::cout << v << ' ';        // 输出 2 4 6
}

这里的 | 操作符是管道语法(pipeline),它把前一个容器传递给后一个适配器,形成链式调用。

3. 自定义适配器:去重视图

标准库提供了 views::unique,但如果你想在自定义排序或比较时去重,可以写一个适配器。

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

struct case_insensitive_less {
    bool operator()(const std::string& a, const std::string& b) const {
        return std::lexicographical_compare(
            a.begin(), a.end(), b.begin(), b.end(),
            [](char x, char y){ return std::tolower(x) < std::tolower(y); });
    }
};

int main() {
    std::vector<std::string> words{"Apple","apple","Banana","banana","Cherry"};
    auto sorted = words | std::ranges::views::transform([](auto& s){ return s; })
                        | std::ranges::views::common; // 转成容器以便排序

    std::vector<std::string> result;
    std::ranges::inserter(result, result.end()) =
        sorted | std::ranges::views::unique(case_insensitive_less{});
    for(auto &s : result) std::cout << s << ' ';   // 输出 Apple Banana Cherry
}

此示例展示了如何在视图链中插入自定义比较器,实现大小写不敏感的去重。

4. 延迟求值的优势

传统算法在执行前会遍历整个容器,而 ranges 的惰性求值意味着只有真正需要元素时才会计算。

auto large = std::views::iota(1, 1000000) 
            | std::ranges::views::filter([](int x){ return x % 1000 == 0; });

auto sum = std::accumulate(large.begin(), large.end(), 0LL);
// 只会计算 1000、2000、... 1000000 共 1000 次

这在需要组合多个过滤、映射时尤其高效。

5. 与 STL 算法的兼容

多数 STL 算法都接受范围(range)而非迭代器对。使用 ranges,代码更简洁。

std::vector <int> data{1,2,3,4,5};
auto result = std::ranges::transform_reduce(
    data | std::ranges::views::filter([](int x){ return x > 2; }),
    0, std::plus{}, [](int x){ return x * 2; });
std::cout << result; // 18

以上示例把过滤、变换与归约合并为一行。

6. 小结

  • 视图:惰性、无副作用的容器包装。
  • 适配器:对视图进行变换、筛选、排序、分组。
  • 管道语法| 让链式调用更直观。
  • 延迟求值:提高性能,尤其在大数据流中。

掌握 std::ranges 后,你会发现 C++ 代码既简洁又强大。下一步可以尝试在自己的项目中替换部分传统循环,用 ranges 重新实现一次,体验它带来的代码可读性提升。祝编码愉快!

发表评论