C++20 ranges 与 view 的实战应用:提升代码可读性与性能

在 C++20 中,ranges 和 view 彻底改变了我们处理序列的方式。相比传统的 STL 容器与算法,ranges 通过惰性求值、链式组合与类型擦除,让代码既简洁又高效。本文将从基础概念入手,展示如何在实际项目中充分利用 ranges 与 view,提升代码可读性与性能。

1. 基础概念

1.1 范围(Range)

一个范围是一对迭代器(beginend)所定义的序列。C++20 引入了 std::ranges::range 范围概念,用来标识所有满足 begin()end() 接口的类型。

#include <ranges>
#include <vector>

std::vector <int> vec{1, 2, 3, 4, 5};
static_assert(std::ranges::range<std::vector<int>>); // true

1.2 View

View 是对范围的“视图”,它并不存储数据,而是通过惰性计算得到结果。常见的 View 有 std::ranges::filter_viewstd::ranges::transform_viewstd::ranges::reverse_view 等。

auto even_view = vec | std::views::filter([](int x){ return x % 2 == 0; });

2. 常用 View 示例

2.1 过滤(filter)

只保留满足条件的元素。

for (int x : vec | std::views::filter([](int n){ return n > 2; }))
    std::cout << x << ' '; // 3 4 5

2.2 转换(transform)

对每个元素做一次映射。

for (int x : vec | std::views::transform([](int n){ return n * n; }))
    std::cout << x << ' '; // 1 4 9 16 25

2.3 组合(concatenate)

将多个 View 合并为一个。

std::vector <int> vec2{6, 7, 8};
auto combined = vec | std::views::concat(vec2);
for (int x : combined)
    std::cout << x << ' '; // 1 2 3 4 5 6 7 8

2.4 递归(views::iota 与 views::take)

生成无穷序列并截断。

auto first10 = std::views::iota(0) | std::views::take(10);
for (int x : first10)
    std::cout << x << ' '; // 0 1 2 3 4 5 6 7 8 9

3. 性能优势

3.1 惰性求值

View 采用惰性求值策略,只有在真正访问元素时才执行计算,避免了不必要的拷贝或中间结果。

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

上面链式调用仅在 for 循环访问元素时执行一次过滤再一次映射。

3.2 编译期优化

与传统 std::transform 等算法相比,ranges 的 View 对象是轻量级的,编译器可以更容易地消除临时对象并进行函数内联。

3.3 并行支持

C++20 ranges 与 view 与并行执行器(如 std::execution::par)兼容,可直接使用 std::ranges::for_each 等算法实现并行处理。

std::ranges::for_each(vec | std::views::filter([](int n){ return n > 3; }),
                      std::execution::par,
                      [](int n){ std::cout << n << ' '; });

4. 与传统 STL 的对比

任务 STL 写法 ranges 写法
过滤偶数 std::copy_if std::views::filter
取平方 std::transform std::views::transform
链式操作 多次临时容器 单一惰性链
并行 std::for_each + execution::par std::ranges::for_each + execution::par

4.1 代码可读性提升

使用 ranges,代码更像自然语言,读者可以直观看到“过滤 -> 转换 -> 迭代”的流程,而不必关心中间容器。

4.2 内存占用减少

由于 View 不会产生中间容器,内存占用大幅下降,尤其适用于大数据量处理。

5. 真实案例

假设我们有一个包含日志条目的 `std::vector

`,想要提取最近一小时内所有错误级别的日志,并打印其简要摘要。 “`cpp struct LogEntry { std::chrono::system_clock::time_point timestamp; std::string level; // “INFO”, “WARN”, “ERROR” std::string message; }; std::vector logs = load_logs(); auto now = std::chrono::system_clock::now(); auto recent_errors = logs | std::views::filter([now](const LogEntry& e){ return e.level == “ERROR” && e.timestamp >= now – std::chrono::hours{1}; }) | std::views::transform([](const LogEntry& e){ return e.message; }); for (const auto& msg : recent_errors) std::cout

发表评论