span<T>
的建议,或者在网站上看到一些使用span
的答案-应该是某种容器。但是-在C ++ 17标准库中找不到类似的东西。 那么,这个神秘的
span<T>
是什么?为什么(或何时)非标准使用它是个好主意?#1 楼
是什么?span<T>
是:内存中某个地方的
T
类型的值的连续序列的非常轻量级的抽象。基本上,
struct { T * ptr; std::size_t length; }
具有许多便捷方法。非所有者类型(即“引用类型”而不是“值类型”):它从不分配或取消分配任何东西,也不使智能指针保持活动状态。
它
什么时候应该使用它?
首先,什么时候不使用它:
不要在代码中使用它可以采用任意一对开始和结束迭代器,例如
array_view
,array_ref
,std::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::array
,span
与此有何不同?提醒我,不是吗像
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
评论
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年。