【问题标题】:What is a "span" and when should I use one?什么是“跨度”,我应该什么时候使用?
【发布时间】:2018-01-25 04:51:25
【问题描述】:

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

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

【问题讨论】:

  • std::span于2017年提出,适用于C++17或C++20。另见P0122R5, span: bounds-safe views for sequences of objects。您真的要针对该语言吗?编译器要赶上来还需要几年时间。
  • @jww:span 在 C++11 中非常有用...作为gsl::span 而不是std::span。另请参阅下面的答案。
  • 也记录在 cppreference.com:en.cppreference.com/w/cpp/container/span
  • @KeithThompson:不是在 2017 年它不是......
  • @jww 所有编译器现在都在 C++20 模式下支持 std::span。并且跨度可从许多 3rd 方库中获得。你是对的 - 是几年:准确地说是 2 年。

标签: c++ c++20 c++-faq cpp-core-guidelines std-span


【解决方案1】:

这是什么?

span<T> 是:

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

它以前称为array_view,更早时称为array_ref

我应该什么时候使用它?

首先,当使用它时:

  • 不要在可能只使用任何一对开始和结束迭代器的代码中使用它,例如 std::sortstd::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&amp; 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);
    

    ...它将做你想做的事。另见Guideline P.5

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

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

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

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

还有更多使用 spans 的动力,您可以在 C++ core guidelines 中找到它 - 但您会发现偏差。

但它在标准库中吗?

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

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

如果我写的是 C++17 或更早版本,我该如何使用它呢?

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

  • Microsoft / Neil Macintosh 的 GSL 包含一个独立的实现:gsl/span
  • GSL-Lite 是整个 GSL 的单头实现(没那么大,别担心),包括 span&lt;T&gt;

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

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


延伸阅读:您可以在 C++17 之前的最终官方提案中找到所有细节和设计注意事项,P0122R7:span: bounds-safe views for sequences of objects,作者是 Neal Macintosh 和 Stephan J. Lavavej。虽然有点长。此外,在 C++20 中,跨度比较语义发生了变化(遵循 Tony van Eerd 的 this short paper)。

【讨论】:

  • 标准化一个通用范围(支持iterator+sentinel和iterator+length,甚至可能是iterator+sentinel+length)并使span成为一个简单的typedef会更有意义。因为,你知道,这更通用。
  • @Deduplicator:范围即将出现在 C++ 中,但当前的提议(由 Eric Niebler 提出)需要对概念的支持。所以不是在 C++20 之前。
  • @HảiPhạmLê:数组不会立即衰减为指针。尝试做std::cout &lt;&lt; sizeof(buffer) &lt;&lt; '\n',你会看到你得到 100 sizeof(int)'s。
  • @Jim std::array 是一个容器,它拥有值。 span 不拥有
  • @Jim:std::array 是一个完全不同的野兽。正如 Caleth 解释的那样,它的长度在编译时是固定的,它是一个值类型而不是引用类型。
【解决方案2】:

span&lt;T&gt; 是这个:

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 很好地介绍了span 是什么in his answer here。然而,即使在阅读了他的回答之后,对于 spans 新手来说,仍然很容易遇到一系列尚未完全回答的思路问题,例如:

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

所以,这里有一些额外的说明:

直接引用他的答案-我的补充和粗体的括号我的斜体

这是什么?

span&lt;T&gt; 是:

  • 连续值序列在内存中某处的T 类型的非常轻量级的抽象。
  • 基本上是一个single结构{ T * ptr; std::size_t length; },带有一堆方便的方法。 (请注意,这与std::array&lt;&gt; 明显不同,因为span 通过指向类型T 的指针和长度(元素数) 类型为T,而std::array 是一个实际容器,其中包含一个或多个T 类型的。)
  • 一种非拥有类型(即"reference-type" 而不是“值类型”):它从不分配或释放任何东西并且不保持智能指针活动.

它以前称为array_view,更早时称为array_ref

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

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

C++ 标准的一部分:

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

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

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

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

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&lt;T&gt;(array, length) 模板类: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

【讨论】:

  • 我真的不建议使用所有的 abseil 来获得 span 类。
  • 知道了。最大的优势是重量轻。
  • @yushang,从 C++ 开发人员的角度来看,我认为最大的优势不是“轻量级”,而是:“环绕已经存在的 C 数组”,所以不需要复制,并且您现在在容器周围有一个包装器,该容器将其大小保存在其中,这与 C 数组不同,C 数组不知道也不携带有关其自身大小的信息。作为一名嵌入式开发人员,我在 C 方面的经验比在 C++ 方面的经验要多,不过,我通常更喜欢在一段时间内只使用原始 C 数组。
  • void array_operator ( const size_t count_, char arr [ static count_ ] ); 是标准 C。其中包含完整的数组信息。加上arr 必须有最少count_ 元素。上下文是这个讨论而不是“C 更好”的咆哮。
  • 另外,这也让我很困扰:developercommunity.visualstudio.com/t/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-07
  • 2011-01-18
  • 2011-08-18
  • 2017-03-21
  • 2012-09-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多