span的性能 Spans vs. Subranges

667 阅读3分钟

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_rangestd::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 天,点击查看活动详情