std::ranges::filter是c++20范围库中的适配器对象,用于构造std::ranges::filter_view;后者才是真正持有源范围和谓词、支持惰性迭代的只读视图类型。

std::ranges::filter 是什么,它和 std::filter_view 有什么关系
std::ranges::filter 不是一个函数,而是一个范围适配器对象(range adaptor object),定义在 <ranges></ranges> 头文件中。它本身不执行过滤,而是用来构造一个 std::ranges::filter_view —— 后者才是真正持有原始范围和谓词、支持迭代的视图类型。
你几乎不会直接写 std::ranges::filter,而是用管道操作符 | 或显式构造 filter_view。它的作用等价于“延迟计算的、只读的、无拷贝的条件筛选”。
常见错误现象:std::ranges::filter(pred) 单独调用会编译失败,因为它是不可调用的适配器对象(没有 operator()),必须配合 | 或模板参数推导使用。
怎么正确写出 filter 管道表达式(C++20 推荐写法)
核心是用 | 将容器或范围与 std::ranges::filter 连接,谓词作为参数传入适配器闭包:
#include <ranges>
#include <vector>
#include <iostream><p>std::vector<int> v = {1, 2, 3, 4, 5, 6};
auto even_view = v | std::ranges::filter([](int x) { return x % 2 == 0; });</int></p>
<p>for (int x : even_view) {
std::cout </p></iostream></vector></ranges>
关键点:
-
v必须是可范围化的(满足std::ranges::range),std::vector、std::array、原生数组都行 - 谓词必须是可调用的,且接受范围元素类型的值或引用(注意
const int&和int的兼容性) -
even_view是一个filter_view,不是std::vector;不能直接取.size()(除非底层范围是sized_range) - 视图是惰性的:过滤逻辑只在迭代时触发,不额外分配内存
为什么 filter_view 迭代时可能崩溃或行为异常
最常踩的坑是悬挂引用(dangling reference):当被过滤的原始范围生命周期结束,但 filter_view 仍被使用。
典型错误场景:
- 在函数内构造
filter_view并返回它,而原始容器是局部变量 - 把
std::vector的临时对象直接用于管道:get_vec() | std::ranges::filter(...)→get_vec()析构后,视图迭代即未定义行为
安全做法:
- 确保原始范围的生命周期 ≥ 视图的生命周期
- 若需“物化”结果,显式转成容器:
std::vector<int>(even_view.begin(), even_view.end())</int> - 避免对纯右值范围使用视图,除非立即消费(如直接传给
std::ranges::for_each)
filter 和传统 for 循环 / std::copy_if 相比有什么实际影响
性能上,filter_view 几乎零开销:无内存分配、无元素拷贝,仅增加一次谓词调用/元素的间接跳过成本。但它不缓存结果,每次遍历都重新判断。
适用与不适用场景:
- 适合:一次性遍历、流式处理、组合多个视图(如
| std::ranges::filter(...) | std::ranges::take(5)) - 不适合:需要多次随机访问或反复查询长度的场景(
filter_view::size()可能为std::nullopt,且计算 size 需遍历全部) - 和
std::copy_if比:后者立即分配并填充新容器,内存开销大但可重复使用;前者轻量但“一次一算”
容易被忽略的一点:谓词捕获外部变量时,若该变量在视图生命周期内被修改,后续迭代会看到新值 —— 这是特性,不是 bug,但容易引发隐晦逻辑错误。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











