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

时间:2017-08-16 22:15:05

标签: c++ c++-faq cpp-core-guidelines guideline-support-library std-span

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

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

2 个答案:

答案 0 :(得分:196)

它是什么?

span<T>是:

  • 内存中某个类型为T的连续值序列的非常轻量级的抽象。
  • 基本上是一个带有一堆便利方法的struct { T * ptr; 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& 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);
    

    成为这个:

    int buffer[BUFFER_SIZE];
    read_into(buffer);
    

    ......这会做你想做的事。另见Guideline P.5

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

  • 有助于静态分析,因此编译器可能能够帮助您捕获愚蠢的错误。
  • 允许运行时边界检查的调试编译工具(即span的方法将在#ifndef NDEBUG ... #endif内有一些边界检查代码
  • 表示您的代码(使用跨度)不拥有指针。

使用span的动机更有动力,你可以在C++ core guidelines中找到它,但是你可以找到漂移。

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

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

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

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

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

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

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

答案 1 :(得分:11)

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

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

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


进一步:

@einpoklum在介绍spanin his answer here方面做得很好。但是,即使在阅读了答案之后,对于新手来说,仍然很容易会遇到一系列问题,而这些问题并未得到充分回答,例如:

  1. span与C数组有何不同?为什么不只使用其中之一?似乎只是已知尺寸的那些之一...
  2. 等等,听起来像是std::arrayspan与此有何不同?
  3. 提醒我,std::vector也不像std::array一样吗?
  4. 我很困惑。 :(什么是span

因此,在此方面有一些额外的说明:

他的答案的直接报价- 我的添加和括号内的注释以粗体显示,并且我强调斜体

这是什么?

一个span<T>是:

  • 内存中某处类型T连续值序列的非常轻量级的抽象。
  • 基本上是一个单个结构{ T * ptr; std::size_t length; },具有许多便捷方法。 (请注意,这与std::array<>截然不同,因为span通过指针键入std::array启用了与T相当的便捷访问器方法和类型T的长度(元素数),而std::array是一个实际的容器,其中包含一个或多个类型T。)< / strong>
  • 一种非所有类型(即"reference-type"而不是“值类型”):它从不分配或取消分配任何东西,并且不保持智能指针还活着。

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

这些大胆的部分对人们来说是至关重要的,所以请不要错过它们或误读它们! span不是结构的C数组,也不是类型为T的C数组的结构加上数组的长度(实际上,这就是std::array container 是),NOR也不是一种指向T类型的指针结构的C数组加上长度,但它是包含一个单个字符的单个结构要键入T的指针长度,它是连续内存块中元素的数量(类型T)指向类型T的指针指向!! 这样,您使用span添加的唯一开销就是用于存储指针和长度的变量以及任何方便的访问器span提供的功能。

这是不喜欢std::array<>,因为std::array<>实际上为整个连续块分配了内存,而它是不喜欢std::vector<>,因为std::vector基本上只是一个{{ 1}}也会在每次填满并且尝试向其中添加其他内容时进行动态增长(通常会增加一倍)。 std::array的大小是固定的,而 a std::array 甚至不管理它指向的块的内存,它仅指向内存的块,了解内存块的长度,了解内存中C数组中的数据类型,并提供方便的访问器功能来处理该连续内存中的元素。

是C ++标准的一部分:

span是C ++ 20起的C ++标准的一部分。您可以在这里阅读其文档:https://en.cppreference.com/w/cpp/container/span。要了解如何在今天的C ++ 11或更高版本的 中使用Google的std::span,请参见下文。

摘要说明和关键参考:

  1. absl::Span<T>(array, length)std::span<T, Extent> =“序列中的元素数,如果是动态的,则为Extent”。跨度仅指向内存并使之易于访问,但无法管理!):
  2. https://en.cppreference.com/w/cpp/container/span
  3. std::dynamic_extent(注意,它的大小固定为 std::array<T, N>!):
  4. https://en.cppreference.com/w/cpp/container/array
  5. http://www.cplusplus.com/reference/array/array/
  6. N(根据需要自动动态增加尺寸):
  7. https://en.cppreference.com/w/cpp/container/vector
  8. http://www.cplusplus.com/reference/vector/vector/

如何在今天的C ++ 11或更高版本中使用std::vector<T>??

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标头和span.h模板类: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