## C++23 中的 std::ranges 与管道式编程

1. 背景与动机

在 C++20 之前,集合操作(如 filter, map, sort 等)往往需要显式地写出迭代器、算法和临时容器。虽然 `

` 提供了强大的函数式接口,但缺乏直观的管道式链式调用语法,导致代码冗长且可读性不佳。C++23 在 “ 头文件中引入了 **管道运算符 `|`** 以及 **视图(view)**,使得我们可以用更接近自然语言的方式组合算法,极大提升代码的可读性与维护性。 ### 2. 核心概念 – **视图(View)**:只读的、懒执行的、对底层序列进行变换的轻量对象。它不存储数据,而是保持对原始容器的引用或迭代器。 – **管道运算符 `|`**:把左侧的可迭代对象传递给右侧的视图或算法,形成链式调用。 – **变换函数(Transformations)**:`std::views::filter`, `std::views::transform`, `std::views::take`, `std::views::reverse` 等。 – **消耗器(Consumers)**:如 `std::ranges::for_each`, `std::ranges::accumulate`, `std::ranges::sort` 等。 ### 3. 基础示例 “`cpp #include #include #include #include int main() { std::vector data = {1, 2, 3, 4, 5, 6}; // 先过滤偶数,再平方,最后求和 int sum = data | std::views::filter([](int x){ return x % 2 == 0; }) | std::views::transform([](int x){ return x * x; }) | std::ranges::fold(0, std::plus()); std::cout 说明: > – `std::views::filter` 只保留满足谓词的元素。 > – `std::views::transform` 对每个元素执行 lambda。 > – `std::ranges::fold` 是对视图进行累积(C++23 新增的消费者),等价于 `std::accumulate`。 ### 4. 进阶使用:组合自定义视图 有时标准视图不够用,可以自定义一个**投影视图**。下面实现一个 `std::views::select`,用于从 `std::pair` 或结构体中提取成员。 “`cpp #include #include #include #include namespace my_views { template struct select_view : std::ranges::view_base { using iterator_category = std::forward_iterator_tag; using value_type = std::invoke_result_t>::iterator>())>>; using reference = value_type; using difference_type = std::ptrdiff_t; select_view(std::ranges::subrange>::iterator> rng, MemberPtr mp) : rng_(rng), mp_(mp) {} struct iter { using iterator_type = std::vector>::iterator; iterator_type current_; MemberPtr mp_; using iterator_category = std::forward_iterator_tag; using value_type = std::invoke_result_t>; using reference = value_type; using difference_type = std::ptrdiff_t; iter(iterator_type it, MemberPtr mp) : current_(it), mp_(mp) {} reference operator*() const { return (*current_).*mp_; } iter& operator++() { ++current_; return *this; } bool operator==(const iter& other) const { return current_ == other.current_; } }; iter begin() const { return iter(rng_.begin(), mp_); } iter end() const { return iter(rng_.end(), mp_); } private: std::ranges::subrange>::iterator> rng_; MemberPtr mp_; }; template select_view> select(std::ranges::subrange>::iterator> rng, MemberPtr mp) { return select_view>(rng, mp); } } int main() { std::vector> v{{1,”a”},{2,”b”},{3,”c”}}; auto names = v | std::views::transform([](auto &p){ return &p.second; }) // 取地址 | my_views::select(v, &std::pair::second); // 提取成员 for (auto name : names) std::cout 注意:上例中自定义视图相对复杂,实际开发中通常使用 `std::views::transform` 或 `std::views::elements`(C++23)即可完成成员提取。 ### 5. 性能与懒执行 – **懒执行**:视图链式调用不会立即产生中间容器,只有在消费者触发遍历时才会执行。 – **避免拷贝**:只对需要的元素进行计算,减少不必要的数据复制。 – **单次遍历**:多个 `filter`、`transform` 组合在一次遍历中完成,提升 cache 命中率。 ### 6. 常见误区 1. **误以为 `std::ranges::for_each` 需要容器** `for_each` 接受的是可迭代对象,视图同样适用。 2. **忘记包含 ` `** ` ` 在 C++20 起已经存在,C++23 提供了更多消费者。 3. **对视图的生命周期管理不当** 视图内部持有对原始容器的引用,若容器已析构,视图将失效。 4. **使用 `std::views::common` 时忘记兼容不同视图** `common` 将非常规范围转换为普通范围,便于使用范围 for 循环。 ### 7. 结语 C++23 的 ` ` 与管道式编程为我们提供了更优雅、更直观的集合操作方式。它将算法、视图与消费者解耦,利用懒执行与链式调用实现了高效、可读性强的代码。掌握这些技巧后,日常的 STL 使用将更像写自然语言,而不再是堆砌函数模板。祝你编码愉快!

发表评论