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

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

评论

std :: span是在2017年提出的。它适用于C ++ 17或C ++ 20。另请参阅P0122R5,span:对象序列的边界安全视图。您真的要针对该语言吗?编译器赶上来还需要几年时间。

@jww:跨度在C ++ 11中非常有用...作为gsl :: span而不是std :: span。另请参阅下面的答案。

也记录在cppreference.com上:en.cppreference.com/w/cpp/container/span

@KeithThompson:不是在2017年...

@jww所有编译器现在都在C ++ 20模式下支持std :: span <>。跨度可从许多第三方库获得。您说得对-这是几年:准确地说是2年。

#1 楼

是什么?
span<T>是:

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


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

不要在代码中使用它可以采用任意一对开始和结束迭代器,例如array_viewarray_refstd::sort和所有这些超通用模板化函数。
如果您有标准库容器(或Boost容器等),则不要使用它。 ),您知道该代码最适合您的代码。现在不打算何时使用它:

使用std::find_if(分别为std::copy)代替独立的span<T>(分别为span<const T>) ),则分配的长度或大小也很重要。因此,将函数替换为:
  void read_into(int* buffer, size_t buffer_size);


  void read_into(span<int> buffer);


为什么要使用它?为什么好呢?
跨度很棒!使用T* ...


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

const T*
span

for (auto& x : my_span) { /* do stuff */ }(在C ++ 20中)



...但是大多数容器类绝对不会产生任何开销。


让编译器有时为您做更多的工作。例如,以下内容:
  int buffer[BUFFER_SIZE];
  read_into(buffer, BUFFER_SIZE);

变成以下内容:
  int buffer[BUFFER_SIZE];
  read_into(buffer);

...它将执行您想要的操作。另请参阅准则P.5。


当您希望数据在内存中连续时,是将std::find_if(my_span.cbegin(), my_span.cend(), some_predicate);传递给函数的合理选择。



不再需要强大的C ++专家的责骂!


方便静态分析,因此编译器可以帮助您捕获愚蠢的bug。


允许调试编译工具进行运行时边界检查(即std::ranges::find_if(my_span, some_predicate);的方法将在const vector<T>& ... span内进行一些边界检查代码)


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


使用#ifndef NDEBUG的动机更大,您可以在C ++核心准则中找到它-但是您可以
但是它在标准库中吗?
编辑:是的,#endif是使用C ++ 20版本的语言添加到C ++的!
为什么只有C ++ 20吗好吧,尽管这个想法并不新鲜-它的当前形式是与C ++核心准则项目一起构思的,该项目于2015年才开始形成。花了一段时间。
如果我愿意,该如何使用?是否正在编写C ++ 17或更早版本?
它是《核心准则》支持库(GSL)的一部分。实现:

Microsoft / Neil Macintosh的GSL包含一个独立的实现:span


GSL-Lite是整个GSL的单头实现(不是这样)大,请放心),包括std::span

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


gsl/span需要C ++ 98或更高版本

span<T>需要C ++ 11或更高版本

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

进一步阅读:您可以在C ++ 17,P0122R7之前的最终正式建议中找到所有详细信息和设计注意事项:跨度:Neal Macintosh和Stephan J. Lavavej编写的对象序列的边界安全视图。不过有点长。另外,在C ++ 20中,跨度比较语义发生了变化(紧随Tony van Eerd的这篇简短论文之后)。

评论


标准化一个通用范围(支持迭代器+前哨和迭代器+长度,甚至迭代器+前哨+长度),并使span成为一个简单的typedef会更有意义。因为,这更通用。

–重复数据删除器
17年8月17日在12:47

@Deduplicator:范围已经进入C ++,但是当前的提议(由Eric Niebler提出)需要对Concepts的支持。因此,在C ++ 20之前不存在。

– einpoklum
17年8月17日在12:52

@HảiPhạmLê:数组不会立即衰减为指针。尝试执行std :: cout << sizeof(buffer)<<'\ n',您会看到获得100 sizeof(int)。

– einpoklum
17年8月23日在7:59

@Jim std :: array是一个容器,它拥有值。跨度不属于

– Caleth
18/12/4在14:18

@Jim:std :: array是完全不同的野兽。正如Caleth所解释的那样,它的长度在编译时是固定的,并且是值类型而不是引用类型。

– einpoklum
18/12/4在14:56

#2 楼

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”和“ feelgoodery”。 :)

进一步:
@einpoklum在这里介绍span的含义方面做得很好。但是,即使在阅读了他的答案之后,对于跨领域的新手来说,仍然很容易会遇到一连串的思想流问题,而这些问题并未得到完全回答,例如:

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

所以,这方面还有一些其他的清晰度:
他的答案的直接报价-我的加粗文字和括号内的注释以及我对斜体的强调:

是什么?


span<T>是:




连续序列的非常轻量级的抽象基本上在内存中某个地方的类型为T的值。
带有多个便捷方法的单个结构{ T * ptr; std::size_t length; }。(请注意,这与std::array<>明显不同,因为span通过指向类型的指针启用了与std::array相当的便捷访问器方法。 T和类型T的长度(元素数),而std::array是一个实际的容器,其中包含一个或多个T类型的值。)

非所有者类型(即“引用类型”而不是“值类型”):它永远不会分配或取消分配任何东西,也不会使智能指针保持活动状态。



它以前被称为一个array_view,甚至更早于array_ref

这些大胆的部分对于理解您的个人至关重要,因此请不要错过或误读它们! span不是结构的C数组,也不是T类型的C数组的结构加上数组的长度(本质上就是std::array容器的长度),NOR也不是它的C数组指向T类型的指针的结构加上长度,但是它是一个包含一个指向T类型的单个指针和长度的单个结构,该长度是指针所指向的连续内存块中元素的数量(属于T类型) T指向!这样,使用span唯一增加的开销就是用于存储指针和长度的变量,以及span提供的任何便利访问器函数。
这与std::array<>类似,因为std::array<>实际上为整个连续块分配内存,这是不喜欢的std::vector<>,因为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<T>(array, length),请参见下文。
概述和主要参考资料:


std::span<T, Extent>Extent =“序列中的元素,如果动态,则为std::dynamic_extent。“跨度仅指向内存并使其易于访问,但无法对其进行管理!):
https://zh.cppreference.com/w/cpp/container/span

std::array<T, N>(请注意,其尺寸为N固定大小!):
https://en.cppreference.com / w / cpp / container / array
http://www.cplusplus.com/reference/array/array/

std::vector<T>(根据需要自动动态增大尺寸):
https://en.cppreference.com/w/cpp/container/vector
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后的世界中还没有准备就绪,您现在也可以使用这些功能。这里有一些关键的资源和链接:

主站点:https://abseil.io/

https://abseil.io/docs/cpp/
GitHub存储库:https:// github.com/abseil/abseil-cpp


span.h标头和absl::Span<T>(array, length)模板类:https://github.com/abseil/abseil-cpp/blob/master/absl/types /span.h#L153


其他参考文献:

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


评论


我想您提出了重要而有用的信息,谢谢!

–圭利马
6月24日21:12

我真的不建议使用所有的Abseil来获取span类。

– einpoklum
8月28日10:19