什么是 "span" 以及我什么时候应该使用它?

What is a "span" and when should I use one?

最近我收到了在我的代码中使用 span<T> 的建议,或者在网站上看到了一些使用 span 的答案——据说是某种容器。但是——我在 C++17 标准库中找不到类似的东西。

那么这个神秘的东西是什么 span<T>,如果它不是标准的,为什么(或什么时候)使用它是个好主意?

这是什么?

一个span<T>是:

  • 内存中某处 T 类型的连续值序列的非常轻量级抽象。
  • 基本上是一个 struct { T * ptr; std::size_t length; } 和一堆方便的方法。
  • A non-owning 类型(即 "reference-type" 而不是“值类型”):它从不分配或取消分配任何东西,也不会使智能指针保持活动状态。

它以前被称为 array_view and even earlier as array_ref

我应该什么时候使用它?

首先,当使用它时:

  • 不要在可以接受任何一对开始和结束迭代器的代码中使用它,例如 std::sortstd::find_ifstd::copy 和所有这些 super-generic 模板函数。
  • 如果您有一个您知道适合您的代码的标准库容器(或 Boost 容器等),请不要使用它。它无意取代其中任何一个。

现在实际使用它的时间:

Use span<T> (respectively, span<const T>) instead of a free-standing T* (respectively const T*) when the allocated length or size also matter. So, replace functions like:

void read_into(int* buffer, size_t buffer_size);

with:

void read_into(span<int> buffer);

我为什么要使用它?为什么是好事?

哦,跨度太棒了!使用 span...

  • 意味着您可以使用该指针+长度/开始+结束指针组合,就像您使用花哨的 pimped-out 标准库容器一样,例如:

    • for (auto& x : my_span) { /* do stuff */ }
    • std::find_if(my_span.cbegin(), my_span.cend(), some_predicate);
    • std::ranges::find_if(my_span, some_predicate);(在 C++20 中)

    ...但是绝对 none 的开销大多数容器 类 招致。

  • 有时让编译器为您做更多的工作。例如,这个:

    int buffer[BUFFER_SIZE];
    read_into(buffer, BUFFER_SIZE);
    

    变成这样:

    int buffer[BUFFER_SIZE];
    read_into(buffer);
    

    ... 这将执行您希望它执行的操作。另见 Guideline P.5.

  • 当您希望数据在内存中连续时,
  • 是将 const vector<T>& 传递给函数的合理替代方法。不再被 high-and-mighty C++ 大师骂!

  • 促进静态分析,因此编译器可能能够帮助您捕获愚蠢的错误。

  • 允许 debug-compilation 运行时检测 bounds-checking(即 span 的方法将在 #ifndef NDEBUG 中包含一些 bounds-checking 代码... #endif)

  • 表示您的代码(使用跨度)不拥有 pointed-to 内存。

使用 span 的动力更大,您可以在 C++ core guidelines 中找到它 - 但您会发现其中的不足。

但是它在标准库中吗?

编辑: 是的,std::span 已通过 C++20 版本的语言添加到 C++ 中!

为什么只在 C++20 中?好吧,虽然这个想法并不新鲜 - 它目前的形式是与 C++ core guidelines 项目一起构思的,该项目在 2015 年才开始成形。所以花了一段时间。

如果我正在编写 C++17 或更早版本,我该如何使用它?

它是 Core Guidelines 支持库 (GSL) 的一部分。实现:

  • Microsoft / Neil Macintosh 的 GSL contains a standalone implementation: gsl/span
  • GSL-Lite 是整个 GSL 的 single-header 实现(没那么大,不用担心),包括 span<T>.

GSL 实现通常假设平台实现了 C++14 支持 [11]。这些替代 single-header 实现不依赖于 GSL 设施:

请注意,这些不同的 span 实现在 methods/support 附带的功能方面存在一些差异;它们也可能与 C++20 中标准库采用的版本有所不同。


进一步阅读:您可以在C++17之前的最终官方提案中找到所有细节和设计注意事项,P0122R7:span: bounds-safe views for sequences of objects by Neal Macintosh and Stephan J. Lavavej. It's a bit long though. Also, in C++20, the span comparison semantics changed (following this short paper by Tony van Eerd) .

一个span<T>是这样的:

template <typename T>
struct span
{
    T * ptr_to_array;   // pointer to a contiguous C-style array of data
                        // (which memory is NOT allocated or deallocated 
                        // by the span)
    std::size_t length; // number of elements of type `T` in the array

    // Plus a bunch of constructors and convenience accessor methods here
}

它是 light-weight 对 C-style 数组的包装器,C++ 开发人员在使用 C 库并希望使用 C++ 样式的数据容器包装它们以实现“类型安全”时首选和“C++-ishness”和“feelgoodery”。 :)

注意:我将上面定义的结构容器称为 span,一个“light-weight 对 C-style 数组的包装器”,因为它指向一个连续的片段内存,例如 C-style 数组,并用访问器方法和数组大小包装它。这就是我所说的“light-weight 包装器”的意思:它是一个指针和长度变量以及函数的包装器。


更进一步:

@einpoklum 很好地介绍了 span 是什么 in his answer here。但是,即使在阅读了他的回答后, 新手也很容易遇到一系列 stream-of-thought 未完全回答的问题,例如:

  1. span 与 C 数组有何不同?为什么不只使用其中之一呢?好像只是其中一个尺寸也已知的...
  2. 等等,这听起来像 std::arrayspan 和那个有什么不同?
  3. 哦,这让我想起了,std::vector 不也和 std::array 一样吗?
  4. 我很困惑。 :( 什么是 span

因此,这里有一些额外的说明:

直接引用他的回答——我的补充和加粗的括号注释我的强调用斜体:

What is it?

A span<T> is:

  • A very lightweight abstraction of a contiguous sequence of values of type T somewhere in memory.
  • Basically a single struct { T * ptr; std::size_t length; } with a bunch of convenience methods. (Notice this is distinctly different from std::array<> because a span enables convenience accessor methods, comparable to std::array, via a pointer to type T and length (number of elements) of type T, whereas std::array is an actual container which holds one or more values of type T.)
  • A non-owning type (i.e. a "reference-type" rather than a "value type"): It never allocates nor deallocates anything and does not keep smart pointers alive.

It was formerly known as an array_view and even earlier as array_ref.

那些加粗的部分对理解来说至关重要,所以不要错过或误读它们! span 不是结构的 C-array,也不是 T 类型的 C-array 加上数组长度的结构(这本质上就是 std::array container 是),也不是 C-array 类型为 T 的指针结构加上长度,而是 single 结构包含一个指向类型 T 的单个 指针和 length,即 T 类型的指针指向的连续内存块中元素(T 类型)的数量! 这样,您添加的唯一开销是使用 span 是存储指针和长度的变量,以及您使用 span 提供的任何便利访问器函数。

这是 UNLIKE a std::array<> 因为 std::array<> 实际上为整个连续块分配内存,它是 UNLIKE std::vector<> 因为 std::vector 基本上只是一个std::array 每次它填满并且您尝试向其中添加其他内容时,它也会 动态增长 (通常大小加倍)。 A std::array 是固定大小的,而 a span 甚至不管理它指向的块的内存,它只是指向到内存块,知道内存块有多长,知道内存中 C-array 中的数据类型,并提供方便的访问器函数来处理该连续内存中的元素 .

C++标准的一部分:

从 C++20 开始,

std::span 是 C++ 标准的一部分。您可以在此处阅读其文档:https://en.cppreference.com/w/cpp/container/span。要了解如何在 C++11 或更高版本 中使用 Google 的 absl::Span<T>(array, length),请参阅下文。

摘要说明和主要参考文献:

  1. std::span<T, Extent> (Extent = "序列中元素的数量,或者 std::dynamic_extent 如果是动态的。一个跨度 指向 内存并使其易于访问,但不管理它!):
  2. https://en.cppreference.com/w/cpp/container/span
  3. std::array<T, N>(注意它有一个固定大小N!):
  4. https://en.cppreference.com/w/cpp/container/array
  5. http://www.cplusplus.com/reference/array/array/
  6. std::vector<T>(根据需要自动动态增大大小):
  7. https://en.cppreference.com/w/cpp/container/vector
  8. http://www.cplusplus.com/reference/vector/vector/

如何在 C++11 或更高版本中使用 span 今天

Google 有 open-sourced 其内部 C++11 库,其形式为“Abseil”库。该库旨在提供 C++14 到 C++20 以及适用于 C++11 及更高版本的功能,以便您今天就可以使用明天的功能。他们说:

Compatibility with the C++ Standard

Google has developed many abstractions that either match or closely match features incorporated into C++14, C++17, and beyond. Using the Abseil versions of these abstractions allows you to access these features now, even if your code is not yet ready for life in a post C++11 world.

以下是一些关键资源和链接:

  1. 主站点:https://abseil.io/
  2. https://abseil.io/docs/cpp/
  3. GitHub 存储库:https://github.com/abseil/abseil-cpp
  4. span.h header 和 absl::Span<T>(array, length) 模板 class: https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L153

其他参考资料:

  1. Struct with template variables in C++
  2. Wikipedia: C++ classes
  3. default visibility of C++ class/struct members

相关:

  1. [我关于模板和跨度的另一个回答]How to make span of spans