<ranges>
视图类
“视图”是一个轻量级范围,它引用它不拥有的元素(owning_view
除外)。 视图通常基于另一个范围,并且通过转换或筛选提供不同的查看方式。 例如,,std::views::filter
是一个视图,它使用指定的条件从另一个范围中选择元素。
访问视图中的元素时,它是“惰性”完成的,因此只有当你获取元素时才完成工作。 这样就可合并或组合视图,而不会造成性能损失。
例如,可以创建一个视图,该视图仅提供区域中的偶数元素,然后通过对它们进行平方来转换它们。 仅对你访问的元素,并且仅在你访问这些元素时执行筛选和转换。
无论视图包含多少个元素,都可以在固定时间内复制、分配和销毁视图。 这是因为视图不拥有它引用的元素,因此不需要创建副本。 这就是为什么你可以在没有性能损失的情况下组合视图。
通常使用范围适配器来创建视图。 范围适配器是创建视图的预期方法,比直接实例化视图类更容易使用,有时比直接实例化视图类更有效。 如果需要基于现有视图类型创建自己的自定义视图类型,则直接公开视图类。
下面的简短示例演示了如何创建一个矢量中可被 3 整除的元素的平分的视图:
// requires /std:c++20 or later
#include <ranges>
#include <vector>
#include <iostream>
int main()
{
std::vector<int> input = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto divisible_by_three = [](const int n) {return n % 3 == 0;};
auto square = [](const int n) {return n * n;};
auto x = input
| std::views::filter(divisible_by_three)
| std::views::transform(square);
for (int i : x)
{
std::cout << i << ' '; // 0 9 36 81
}
}
0 9 36 81
在视图基于的范围被修改后使用视图可能会导致未定义的行为。 例如,如果添加或移除基础矢量中的元素,不应重复使用基于矢量的 reverse_view
。 修改基础矢量会使容器的 end
迭代器失效,包括使视图可能已创建的迭代器的副本失效。
视图创建成本低,因此如果修改基础范围,通常应重新创建视图。 以下示例演示如何将视图管道存储在变量中,以便可重用它。
// requires /std:c++20, or later
#include <iostream>
#include <ranges>
#include <vector>
#include <list>
#include <string_view>
#include <algorithm>
template<typename rangeType>
void show(std::string_view msg, rangeType r)
{
std::cout << msg;
std::ranges::for_each(r,
[](auto e)
{
std::cout << e << ' ';
});
std::cout << '\n';
}
int main()
{
std::vector v{ 1, 2, 3, 4 };
show("v: ", v);
// You can save a view pipeline
auto rev3 = std::views::take(3) | std::views::reverse;
show("v | rev3: ", v | rev3); // 3 2 1
v.insert(v.begin(), 0); // v = 0 1 2 3 4
show("v: ", v);
// Because modifying the vector invalidates its iterators, rebuild the view.
// We are reusing the view pipeline we saved earlier
show("v | rev3(v): ", rev3(v));
}
v: 1 2 3 4
v | rev3: 3 2 1
v: 0 1 2 3 4
v | rev3(v): 2 1 0
以下视图类在 std::ranges
命名空间中定义。
查看 | 说明 |
---|---|
basic_istream_view C++20 |
输入流中连续元素的视图。 专用化包括 istream_view 和 wistream_view 。 |
common_view C++20 |
将具有不同迭代器/sentinel 类型的视图调整为具有相同迭代器/sentinel 类型的视图。 |
drop_view C++20 |
从另一个视图创建,跳过第一个 count 元素。 |
drop_while_view C++20 |
从另一个视图创建,只要保留谓词,就跳过前导元素。 |
elements_view C++20 |
集合中每个类似元组的值的所选索引的视图。 例如,给定一系列 std::tuple<string, int> 值后,创建一个视图,其中包含每个元组中的所有 string 元素。 |
empty_view C++20 |
没有元素的视图。 |
filter_view C++20 |
筛选掉范围中与谓词不匹配的元素。 |
iota_view C++20 |
生成的包含一些列递增值的视图。 |
join_view C++20 |
将多个范围的所有元素合并为一个视图。 |
keys_view C++20 |
集合中每个类似元组的值的第一个索引的视图。 例如,给定一系列 std::tuple<string, int> 值后,创建一个视图,其中包含每个元组中的 string 元素。 |
lazy_split_view C++20 |
根据分隔符将视图拆分为子范围。 |
owning_view C++20 |
从另一个范围获取元素的所有权。 |
ref_view C++20 |
引用属于其他范围的元素的视图。 |
reverse_view C++20 |
以倒序呈现范围的元素。 |
single_view C++20 |
仅包含一个元素的视图。 |
split_view C++20 |
根据分隔符将视图拆分为子范围。 |
subrange C++20 |
范围的部分元素的视图,由开始迭代器和 sentinel 定义。 |
take_view C++20 |
包含从范围前面获取的指定数量的元素。 |
take_while_view C++20 |
包含范围中与给定谓词匹配的前导元素。 |
transform_view C++20 |
将转换函数应用于每个元素后的基础序列的视图。 |
values_view C++20 |
集合中每个类似元组的值的第二个索引的视图。 例如,给定一系列 std::tuple<string, int> 值后,创建一个视图,其中包含每个元组中的 int 元素。 |
其中很多类在创建其实例的 std:views
命名空间具有相应的范围适配器。 首选使用适配器来创建视图,而不是直接创建视图类。 范围适配器是创建视图的预期方法,它们更易于使用,而且在某些情况下更加高效。
视图类特征
每个视图类主题在语法节之后都有一个“特征”部分。 “特征”部分包含以下条目:
范围适配器:指向创建视图的范围适配器的链接。 通常使用范围适配器来创建视图,而不是直接创建视图类,因此为了方便起见,这里列出了它。
基础范围:视图对其可使用的基础范围类型有不同的迭代器要求。 有关迭代器类型的详细信息,请参阅范围迭代器层次结构。
查看迭代器类别:视图的迭代器类别。 当视图调整范围时,视图的迭代器类型通常与基础范围的迭代器类型相同。 但是,对于某些视图,它可能有所不同。 例如,
reverse_view
有一个bidirectional_iterator
,即使基础范围有一个random_access_iterator
也是如此。元素类型:视图迭代器返回的元素的类型。
已设置大小:视图是否可返回它引用的元素数。 并非所有视图都可以。
常见范围:指定视图是否为
common_range
,这意味着开始迭代器和 sentinel 类型是相同的。 对于适用于迭代器对的预范围代码,常见范围非常有用。 一个例子是序列容器的迭代器对构造函数,例如vector(ranges::begin(x), ranges::end(x))
。借用范围:指定视图是否为借用范围。
borrowed_range<T>
意味着你可在T
销毁后使用T
的迭代器。没有标准容器就是一个借用范围,因为销毁容器会释放元素并使任何迭代器失效。 在这种情况下,我们说迭代器在销毁后被“悬空”。
例如,
std::ranges::find()
通常向 range 参数中的 found 元素返回迭代器。 如果 range 参数是临时 (rvalue) 容器,则存储返回的迭代器并稍后使用它是错误的,因为它是“悬空”的。返回迭代器(或子范围)的范围算法仅在其参数是 lvalue(非临时)或借用范围时才这样做。 否则,它们返回一个
std::dangling
对象,如果你尝试像使用迭代器一样使用该对象,它会在错误消息中提示什么出现了错误。是
const
可迭代:指示是否可循环访问视图的const
实例。 并非所有const
视图都可以迭代。 如果视图不是const
可迭代的,你无法迭代for (const auto& element : as_const(theView))
,也无法将其传递给函数,其中该函数接受对视图的const
引用,然后尝试循环访问它。
范围迭代器层次结构
在每个视图类主题的“特征”部分中,基础范围和视图迭代器类别中的迭代器类别是指范围/视图支持的迭代器类型。 有 6 类范围迭代器,它们由 C++20 概念标识。 按功能的增加顺序,范围迭代器的层次结构如下:
范围迭代器概念 | 说明 |
---|---|
output_range |
只写,仅向前移动;单传递。 |
input_range |
只读,仅向前移动;单传递。 |
forward_range |
仅向前移动;多传递。 |
bidirectional_range |
可向前和向后移动;多传递。 |
random_access_range |
可使用索引访问集合;多传递。 |
contiguous_range |
可使用索引访问集合,元素在内存中连续存储。 |
一般来说,迭代器具有表中它前面的迭代器的功能。 例如,bidirectional_range
具有 forward_range
功能,但反之并非是这样。 input_range
除外,它没有 output_range
的功能,因为你无法写入 input_range
。
“需要 input_range
或更高范围”表述意味着视图可以与 input_range
、forward_range
、bidirectional_range
、random_access_range
或 contiguous_range
迭代器一起使用,因为它们在功能上与 input_range
相同。
范围迭代器层次结构与迭代器层次结构直接相关。 有关详细信息,请参阅迭代器概念。