C++20 中的 ranges 与 views:让集合操作更优雅

C++20 为标准库引入了 rangesviews 的概念,使得对容器、迭代器等序列数据的操作变得更像函数式编程。相比传统的 std::copy_ifstd::transform 等算法,ranges 的语义更直观,同时可以在编译期优化,提升性能。下面从基本概念、常用视图、以及实际编码技巧四个角度,来系统地介绍这部分内容。

1. 基础概念

1.1 Range

在 C++20 中,Range 是一个对象,能够提供 begin()end() 迭代器,或者直接满足 std::ranges::range 语义。几乎所有容器(std::vectorstd::list 等)以及数组都已经是 Range。

1.2 View

View 是对 Range 的“视图”,它不拥有数据,而是对底层 Range 进行惰性变换。典型的 View 有 std::views::filterstd::views::transformstd::views::take 等。由于 View 本身不存储元素,它可以链式组合,产生一个新的 Range,直到最终消费。

1.3 容器适配器

与 C++17 的 std::ranges::subrange 相比,C++20 更倾向于使用 std::views::all 对任何 Range 进行适配,保证后续操作能统一处理。

2. 常用 View 详解

View 用途 示例
filter 过滤元素 auto evens = std::views::filter([](int n){return n%2==0;});
transform 变换元素 auto squared = std::views::transform([](int n){return n*n;});
take 取前 N 个 auto first3 = std::views::take(3);
drop 跳过前 N 个 auto skip2 = std::views::drop(2);
reverse 反转 auto rev = std::views::reverse;
concat 合并 auto merged = std::views::concat(vec1, vec2);
zip 并行遍历 auto zipped = std::views::zip(vec1, vec2);

注意zip 在 C++20 标准库中并不存在,需自行实现或使用第三方库。

3. 代码实战

3.1 过滤奇数并平方

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

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

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

    std::for_each(processed.begin(), processed.end(), 
                  [](int n){ std::cout << n << ' '; });

    // 输出: 4 16 36 64 100 
}

这里 processed 是一个惰性视图,只有在 for_each 访问时才真正执行过滤与变换。

3.2 取前 5 个平方数

auto top5 = processed | std::views::take(5);
std::ranges::copy(top5, std::ostream_iterator <int>(std::cout, " "));
// 输出: 4 16 36 64 100

3.3 反转序列

auto reversed = nums | std::views::reverse;
std::ranges::copy(reversed, std::ostream_iterator <int>(std::cout, " "));

4. 性能与编译期优化

  • 惰性求值:除非对视图进行终止操作(如 copyfor_each),否则不会产生任何运行时开销。
  • 编译期常量:如果视图参数是常量表达式,编译器可以在编译期展开,进一步减少运行时成本。
  • SFINAE 与 constexpr:C++20 的 std::ranges::enable_view 让 View 成为 constexpr 友好,适用于 constexpr std::vector 等场景。

5. 常见坑点

  1. 不支持所有容器:例如 std::forward_listbegin()bidirectional_iterator,不支持 reverse
  2. 多次复制std::views::all 在某些情况下会产生额外的包装对象,导致链式调用中不必要的复制。
  3. 递归视图:链式深度过大时,编译器可能会生成巨大的模板实例化,导致编译时间拉长。

6. 结语

C++20 的 ranges 与 views 为集合操作注入了“函数式”的语法糖,既保持了 C++ 传统的性能优势,又极大提升了代码可读性与维护性。随着标准库的完善,未来的 C++ 程序员将不再需要手写繁琐的循环和算法,而是能用更简洁、直观的方式表达复杂的逻辑。


如果你在实际项目中遇到对 View 组合或性能瓶颈的问题,欢迎继续提问。祝编码愉快!

发表评论