336

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

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

4

2 回答 2

396

它是什么?

一个span<T>是:

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

它以前称为 anarray_view甚至更早于array_ref.

我应该什么时候使用它?

一、什么时候使用:

  • 不要在可能只使用任何一对开始和结束迭代器的代码中使用它,例如,std::sort和所有这些超通用模板化函数。std::find_ifstd::copy
  • 如果您有一个您知道适合您的代码的标准库容器(或 Boost 容器等),请不要使用它。它并不打算取代其中任何一个。

现在了解何时实际使用它:

当分配的长度或大小也很重要时,使用span<T>(分别span<const T>)而不是独立的T*(分别)。const T*因此,替换如下功能:

void read_into(int* buffer, size_t buffer_size);

和:

void read_into(span<int> buffer);

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

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

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

    • 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 中)

    ...但绝对没有大多数容器类产生的开销。

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

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

    变成这样:

    int buffer[BUFFER_SIZE];
    read_into(buffer);
    

    ...这将做你想做的事。另见指南 P.5

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

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

  • 允许用于运行时边界检查的调试编译工具(即的方法将在...span中包含一些边界检查代码)#ifndef NDEBUG#endif

  • 表示您的代码(使用跨度)不拥有指向的内存。

使用spans 的动机甚至更多,您可以在C++ 核心指南中找到它- 但您会发现偏差。

但它在标准库中吗?

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

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

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

它是核心指南的支持库 (GSL) 的一部分。实现:

  • Microsoft / Neil Macintosh 的GSL包含一个独立的实现:gsl/span
  • GSL-Lite是整个 GSL 的单头实现(没那么大,不用担心),包括span<T>.

GSL 实现通常假设一个实现 C++14 支持的平台 [ 11 ]。这些替代的单头实现不依赖于 GSL 设施:

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


进一步阅读:您可以在 C++17、P0122R7 之前的最终官方提案中找到所有细节和设计注意事项:跨度: Neal Macintosh 和 Stephan J. Lavavej的对象序列的边界安全视图。虽然有点长。此外,在 C++20 中,跨度比较语义发生了变化(在 Tony van Eerd 的这篇简短论文之后)。

于 2017-08-16T22:15:05.487 回答
29

Aspan<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
}

它是 C 风格数组的轻量级包装器,C++ 开发人员在使用 C 库并希望用 C++ 风格的数据容器包装它们以实现“类型安全”和“C++ 风格”和“感觉良好”时首选”。:)

注意:我将上面定义的结构容器称为跨度,称为“围绕 C 样式数组的轻量级包装器”,因为它指向一块连续的内存,例如 C 样式数组,并用访问器方法和数组的大小。这就是我所说的“轻量级包装器”:它是指针和长度变量以及函数的包装器。


更进一步:

@einpoklum 很好地介绍了 aspan他的答案中的含义。然而,即使在阅读了他的答案之后,对于 spans 新手来说,仍然很容易遇到一系列尚未完全回答的思路问题,例如:

  1. aspan与 C 数组有何不同?为什么不只使用其中之一?似乎它只是其中一种尺寸也已知的......
  2. 等等,这听起来像 a std::array,aspan和那有什么不同?
  3. 哦,这让我想起了,是不是也std::vector像一个std::array
  4. 我很混乱。:( 是什么span

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

直接引用他的回答——我的补充和括号内的评论用粗体字和我的斜体字强调

它是什么?

一个span<T>是:

  • 内存中某处类型的连续值序列的非常轻量级的抽象。T
  • 基本上是一个{ T * ptr; std::size_t length; }带有一堆便利方法的结构。(请注意,这与 a 明显不同,std::array<>因为 a通过指向 type的类型和长度(元素数)的指针span启用了与 相当的便利访问器方法,而它是一个包含一个或多个type的实际容器。)std::arrayTTstd::arrayT
  • 非拥有类型(即“引用类型”而不是“值类型”):它从不分配或取消分配任何东西,也不使智能指针保持活动状态。

它以前称为 anarray_view甚至更早于array_ref.

那些粗体部分对一个人的理解至关重要,所以不要错过它们或误读它们!Aspan不是结构的 C 数组,也不是类型 C 数组T加上数组长度的结构(这本质上就是std::array 容器),也不是指针结构的 C 数组typeT加上长度,而是一个包含一个指向 type的指针和length的单个结构,它是指向 type 的指针指向的连续内存块中的元素数(类型) 通过这种方式,您添加的唯一开销是使用TTTspan是存储指针和长度的变量,以及您使用的任何便利访问器函数span

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

C++ 标准的一部分:

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

摘要描述和主要参考文献:

  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/

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

Google 以“Abseil”库的形式开源了他们内部的 C++11 库。该库旨在提供 C++14 到 C++20 以及在 C++11 及更高版本中工作的功能,以便您可以在今天使用明天的功能。他们说:

与 C++ 标准的兼容性

Google 已经开发了许多抽象,它们要么匹配或紧密匹配 C++14、C++17 及更高版本中包含的特性。使用这些抽象的 Abseil 版本允许您现在访问这些功能,即使您的代码还没有准备好在 C++11 后的世界中生存。

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

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

其他参考:

  1. C++中带有模板变量的结构
  2. 维基百科:C++ 类
  3. C++ 类/结构成员的默认可见性

有关的:

  1. [我对模板和跨度的另一个答案]如何制作跨度
于 2020-04-14T20:34:35.727 回答