span的设计
设计一个引用元素序列的类型并不容易。很多方面和权衡都需要考虑和决定
- 性能与安全
- const正确性
- 可能的隐式和显式类型转换
- 支持类型的要求
- 支持的API
span不是容器。它可能有一些像容器一样的属性(例如,使用begin()和end()迭代元素的能力),但已经有几个问题了
- 如果span为const,则元素也应为const?
- 赋值的作用是:给引用的元素赋一个新序列或赋一个新值?
- 我们是否应该提供swap()以及它的作用?
span不是指针(有大小)。提供运算符是没有意义的
类型std::span是对元素序列的一个非常特定的引用。要正确使用它,理解这些特定的属性是很重要的。
span的迭代器不指向span本身。相反,它们指的是潜在的范围。因此,span是一个借来的范围。但是,请注意,当底层范围不再存在时,迭代器仍然可以摇摆。
请注意,我们还有其他方法来处理对(子)序列的引用,例如subranges,这也是在c++ 20中引入的。
span的性能
span的设计以最佳性能为目标。为此,它们在内部只使用一个指向元素序列的原始指针。然而,原始指针希望元素按顺序存储在一大块内存中(否则原始指针不能使用++来遍历元素)。因此,span要求元素位于连续的内存中。
有了这个需求,span可以提供alI类型视图的最佳性能。span不需要任何分配,也不附带任何间接。使用span的唯一开销是构造它的开销。
span检查引用序列是否在连续内存中通过概念在组合时具有其元素。当序列初始化或分配新序列时,迭代器必须满足概念 std::contiguous_iterator,容器/范围必须满足概念 std::ranges::contiguous_range 和 std::ranages::sized_range。
因为span内部只是一个指针和一个大小,所以复制它们的成本非常低。出于这个原因,您应该始终倾向于通过值传递span,而不是通过const引用传递它们。
span类型使用指向内存的原始指针执行元素访问,这意味着span类型会删除这些元素所在容器中的信息。向量元素的span与数组元素的span具有相同的类型(前提是它们具有相同的extent)。
std::array arr{1, 2, 3,4,5};
std::vector vec{1,2,3,4,5};
std::span<int> vecSpanDyn{vec};
std::span<int> arrSpanDyn{arr};
std::same_as<decltype(arrSpanDyn), decltype(vecSpanDyn)>
但是,请注意,span的类模板参数推导将从数组推导出一个固定的范围,从向量推导出一个动态的范围。这意味着
std::array arr{1,2, 3,4,5};
std::vector vec{1,2,3,4,5};
std::span<int,5> vecSpan5{vec};
std::same_as<decltype(arrSpan), decltype(vecSpan)>
//false std::same_as<decltype(arrSpan), decltype(vecSpan5)>
Spans vs. Subranges
元素的连续存储要求是子范围的主要区别,子范围也在c++ 20中引入。子范围内部仍然使用迭代器,因此可以引用所有类型的容器和范围。然而,这可能会导致显著的更多开销。
此外,span不需要它们所引用的类型的迭代器支持。您可以传递任何提供data()成员以访问元素序列的类型。
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 N 天,点击查看活动详情”