什么是"跨度",什么时候应该使用?

ein*_*ica 202 c++ c++-faq cpp-core-guidelines c++20 std-span

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

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

ein*_*ica 227

它是什么?

A span<T>是:

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

它以前被称为array_view,甚至更早array_ref.

我应该什么时候使用它?

首先,何时使用它:

  • 不要在代码中使用它,可能只是采取任何一对开始和结束迭代器一样的std::sort,std::find_if,std::copy和所有的超级通用模板功能.
  • 如果您有一个标准的库容器(或Boost容器等),并且您知道它适合您的代码,请不要使用它.它并不打算取代它们中的任何一个.

现在何时实际使用它:

span<T>分别使用(span<const T>而不是)具有长度值的独立T*(分别const T*).所以,替换以下功能:

  void read_into(int* buffer, size_t buffer_size);
Run Code Online (Sandbox Code Playgroud)

有:

  void read_into(span<int> buffer);
Run Code Online (Sandbox Code Playgroud)

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

哦,跨度很棒!使用span......

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

    • for (auto& x : my_span) { /* do stuff */ }
    • std::find_if(my_span.begin(), my_span.end(), some_predicate);

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

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

    int buffer[BUFFER_SIZE];
    read_into(buffer, BUFFER_SIZE);
    
    Run Code Online (Sandbox Code Playgroud)

    成为这个:

    int buffer[BUFFER_SIZE];
    read_into(buffer);
    
    Run Code Online (Sandbox Code Playgroud)

    ......这会做你想做的事.另见准则P.5.

  • const vector<T>&当您希望数据在内存中连续时,是传递给函数的合理替代方法.不再受到高强度C++大师的谴责.

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

  • 允许运行时边界检查的调试编译工具(即,span#ifndef NDEBUG...中的方法将有一些边界检查代码#endif)
  • 表示您的代码(使用span)不拥有指针.

使用spans的动机更多,你可以在C++核心指南中找到它- 但是你会发现这种情况.

为什么它不在标准库中(从C++ 17开始)?

它在标准库中 - 但仅限于C++ 20.原因在于它与目前的形式相比仍然是新的,与C++核心指南项目一起构思,该项目自2015年以来才刚刚起步.(尽管评论者指出,它有早期的历史.)

那么如果它不在标准库中,我该如何使用呢?

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

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

请注意,您可以将它与早期版本的语言标准一起使用 - C++ 11和C++ 14,而不仅仅是C++ 17.


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

  • @HảiPhạmLê:数组不会立即衰减成指针.尝试做`std :: cout << sizeof(buffer)<<'\n'`你会看到你得到100 sizeof(int)的. (7认同)
  • @Jim `std::array` 是一个容器,它拥有值。`span` 是非拥有的 (5认同)
  • 标准化一个通用范围(支持迭代器+哨兵和迭代器+长度,甚至迭代器+哨兵+长度)并使跨度成为一个简单的类型定义会更有意义。因为,你知道,这更通用。 (3认同)
  • `read_into(int* from, int* to)` 不是更符合标准库通常的(基于迭代器的)方法吗? (2认同)
  • @Deduplicator:范围已经进入C ++,但是当前的提议(由Eric Niebler提出)需要对Concepts的支持。因此,在C ++ 20之前不存在。 (2认同)
  • 你能解释一下,只有数组指针构造时,span才能知道数组的大小吗?是因为span可以读取指针旁边的字节来获取大小信息(我猜这些字节也是由`new`或`malloc`分配的,所以他们以后可以正确地`删除')?我找不到任何关于此的文章,并且提示表示赞赏.谢谢. (2认同)
  • @Jim:`std :: array`是完全不同的野兽。正如Caleth所解释的那样,它的长度在编译时是固定的,并且是值类型而不是引用类型。 (2认同)
  • @КонстантинВан:不......迭代器对更为通用。例如:`std::list`的开头和结尾是两个迭代器,但元素在内存中不连续,因此不适合跨度。 (2认同)

Gab*_*les 21

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 in the array

    // Plus a bunch of constructors and convenience accessor methods here
}
Run Code Online (Sandbox Code Playgroud)

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


更进一步:

@einpoklum在这里很好地介绍了他的回答中的aspan是什么。然而,即使在阅读了他的回答之后,对于一个新来跨越的人来说,仍然很容易有一系列未完全回答的思路问题,例如以下内容:

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

所以,这里有一些额外的澄清:

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

它是什么?

一个span<T>是:

  • 内存中某处类型的连续值序列的非常轻量级的抽象T
  • 基本上是一个单一的结构{ T * ptr; std::size_t length; }与一群的方便的方法。(请注意,这明显不同于std::array<>因为 aspan启用了方便的访问器方法,类似于std::array,通过指向 type 的类型T和长度(元素数量)的指针T,而std::array是一个实际的容器,其中包含一个或多个typeT。)
  • 一个非所属类型(即“引用类型”,而不是“价值型”):它从来不分配,也不解除分配什么和不守智能指针活着。

它以前称为array_view,甚至更早称为array_ref

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

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

C++ 标准的一部分:

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

摘要说明和主要参考资料:

  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 或更高版本中使用

谷歌已经以其“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. [我关于模板和跨度的另一个答案]如何制作跨度

  • 我真的不建议使用所有的绳降来获得跨度课程。 (4认同)

小智 6

Einpoklum 提供的答案很棒,但我必须深入评论部分才能理解一个具体细节,因此这意味着作为澄清该细节的扩展。

首先,什么时候不使用它:

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

任何一对开始和结束迭代器,而不是连续存储的开始和结束指针。

作为一个很少接触迭代器内部的人,在我阅读答案时,我没有意识到迭代器可以迭代链表,而简单的指针(和跨度)则不能。