C++20 中的范围基 for 循环改进与实践

C++20 对范围基 for 循环(range‑based for)做了重要改进,使得在处理容器和自定义视图时更加灵活和安全。本文将从语法、编译器实现、范围适配器和实际案例四个方面,系统阐述 C++20 版本的特点,并给出常见使用场景的代码示例,帮助你在项目中更好地运用这一特性。

1. 语法与基本思路

传统的范围基 for 循环语法为:

for (auto&& element : container) {
    // ...
}

其本质相当于:

auto&& __range = container;
for (auto __begin = std::begin(__range), __end = std::end(__range);
     __begin != __end; ++__begin) {
    auto&& element = *__begin;
    // ...
}

C++20 在此基础上引入了 范围适配器(range adapters)与 概念(concepts),让 std::beginstd::end 的选择变得更加灵活,并支持更丰富的自定义视图(如 std::ranges::view)。

2. 编译器实现优化

C++20 采用了 RVO(返回值优化)和 guaranteed copy elision,结合 range view 的延迟求值特性,编译器能够在循环内部实现惰性求值,避免不必要的拷贝与迭代器状态更新。

  • 惰性求值:视图(view)在真正需要时才会产生元素,例如 std::views::filter 仅在迭代时检查条件。
  • 短路:在 for 循环中,如果视图的前缀已满足停止条件,后续元素不再生成。

3. 主要范围适配器

适配器 作用 示例
std::views::filter 过滤 auto even = vec | std::views::filter([](int n){return n%2==0;});
std::views::transform 转换 auto square = vec | std::views::transform([](int n){return n*n;});
std::views::take 截取前 N 个 auto first3 = vec | std::views::take(3);
std::views::drop 跳过前 N 个 auto skip5 = vec | std::views::drop(5);
std::views::reverse 反向 auto rev = vec | std::views::reverse;
std::views::concat 连接 auto concat = vec1 | std::views::concat(vec2);

使用这些适配器可以以链式调用的方式构造复杂的视图,代码简洁且易于维护。

4. 自定义视图的实现

若需要自定义范围适配器,可通过实现 begin()end() 以及可选的 size() 等函数,满足 std::ranges::range 约束即可。示例:

template<class Iterator>
class my_view {
public:
    my_view(Iterator first, Iterator last) : first_(first), last_(last) {}
    Iterator begin() const { return first_; }
    Iterator end() const   { return last_; }

private:
    Iterator first_, last_;
};

auto vec = std::vector <int>{1,2,3,4,5};
my_view view(vec.begin(), vec.end());
for (int v : view) { std::cout << v << ' '; }

5. 实际案例

5.1 过滤并平方大于10的元素

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

int main() {
    std::vector <int> numbers{1, 3, 5, 7, 9, 11, 13};

    auto processed = numbers 
        | std::views::filter([](int n){ return n > 5; })
        | std::views::transform([](int n){ return n * n; });

    for (auto val : processed) {
        std::cout << val << ' ';
    }
    // 输出: 49 121 169 
}

5.2 在二维数组中寻找最大值

#include <iostream>
#include <ranges>
#include <array>

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

    auto rows = mat | std::views::transform([](auto& row){ return std::ranges::max(row); });

    auto max_val = std::ranges::max(rows);
    std::cout << "最大值: " << max_val << '\n';  // 输出 9
}

5.3 用 for 循环遍历自定义双向迭代器

#include <iostream>
#include <iterator>

class MyContainer {
public:
    struct Iterator {
        using iterator_category = std::bidirectional_iterator_tag;
        using value_type = int;
        using difference_type = std::ptrdiff_t;
        using pointer = int*;
        using reference = int&;

        Iterator(int* ptr) : ptr_(ptr) {}
        int& operator*() const { return *ptr_; }
        Iterator& operator++() { ++ptr_; return *this; }
        Iterator& operator--() { --ptr_; return *this; }
        bool operator!=(const Iterator& other) const { return ptr_ != other.ptr_; }

    private:
        int* ptr_;
    };

    Iterator begin() { return Iterator(data_); }
    Iterator end()   { return Iterator(data_ + size_); }

private:
    int data_[5] = {10, 20, 30, 40, 50};
    static constexpr std::size_t size_ = 5;
};

int main() {
    MyContainer c;
    for (auto v : c) {
        std::cout << v << ' ';
    }
    // 输出: 10 20 30 40 50
}

6. 性能考虑

  • 惰性求值:链式适配器不产生临时容器,降低内存占用。
  • 迭代器轻量:大多数视图的迭代器只保存基础容器的迭代器或偏移量,复制成本低。
  • 编译器内联:标准库对常见适配器进行了 noexceptconstexpr 标记,允许编译器内联展开。

7. 小结

C++20 对范围基 for 循环的改进,配合强大的范围适配器与概念体系,使得容器遍历与组合变得更直观、更安全。通过合理使用 std::views,可以在保持代码简洁的同时实现复杂的数据处理逻辑。希望本文能为你在项目中充分利用 C++20 这一强大特性提供实用参考。

发表评论