最近,我得到了span<T>
在代码中使用的建议,或者在网站上看到了使用span
s的答案-据说是某种容器。但是-我在C ++ 17标准库中找不到类似的东西。
那么,这是什么神秘的东西?span<T>
为什么(如果不标准)使用它是一个好主意?
gsl::span
而不是std::span
。另请参阅下面的我的答案。
最近,我得到了span<T>
在代码中使用的建议,或者在网站上看到了使用span
s的答案-据说是某种容器。但是-我在C ++ 17标准库中找不到类似的东西。
那么,这是什么神秘的东西?span<T>
为什么(如果不标准)使用它是一个好主意?
gsl::span
而不是std::span
。另请参阅下面的我的答案。
Answers:
A span<T>
是:
T
内存中某个类型的连续值序列的非常轻量级的抽象。struct { T * ptr; std::size_t length; }
一堆方便的方法。它以前被称为an array_view
,甚至更早地被称为array_ref
。
首先,什么时候不使用它:
std::sort
,std::find_if
)std::copy
以及所有这些超通用模板化函数的代码中使用它。现在,何时使用它:
使用
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);
...这将执行您希望它执行的操作。另请参阅准则P.5。
const vector<T>&
当您希望数据在内存中连续时,是传递给函数的合理替代方法。不再被强大的C ++专家骂了!
span
的方法中将包含一些边界检查代码#ifndef NDEBUG
...#endif
)span
您可以在C ++核心准则中找到使用s的更多动机-但您会遇到麻烦。
它在标准库中-但仅从C ++ 20开始。原因是它与C ++核心指南项目(自2015年才开始形成)一起构思时,其当前形式仍然很新。(尽管评论者指出,它具有更早的历史。)
它是“ 核心指南 ”支持库(GSL)的一部分。实现方式:
一般而言,GSL实现确实假定一个平台实现了C ++ 14支持[ 14 ]。这些替代的单头实现不依赖于GSL设施:
martinmoene/span-lite
需要C ++ 98或更高版本tcbrindle/span
需要C ++ 11或更高版本请注意,这些不同的跨度实现在其附带的方法/支持功能方面有所不同。并且它们也可能与C ++ 20中标准库的版本有所不同。
进一步阅读:您可以在C ++ 17,P0122R7之前的最终正式建议中找到所有详细信息和设计注意事项:跨度: Neal Macintosh和Stephan J. Lavavej 对对象序列的边界安全视图。不过有点长。同样,在C ++ 20中,跨度比较语义发生了变化(紧随Tony van Eerd撰写的这篇简短论文之后)。
std::cout << sizeof(buffer) << '\n'
,您会看到得到100 sizeof(int)。
std::array
是一个容器,它拥有值。span
没有所有权
std::array
是完全不同的野兽。正如Caleth所解释的那样,它的长度在编译时是固定的,并且是值类型而不是引用类型。
@einpoklum在介绍什么内容方面做得很好 span
他的答案是。但是,即使在阅读了他的答案之后,对于跨领域的新手来说,仍然很容易会遇到一系列思想问题,这些问题并未得到完全回答,例如:
span
从C数组有什么不同?为什么不只使用其中之一?似乎只是已知尺寸的那些之一...std::array
,这有什么span
不同?std::vector
像std::array
吗?span
?因此,这有一些额外的清晰度:
他的答案的直接报价- 我的加粗字体:
它是什么?
A
span<T>
是:
- 类型值的连续序列的非常轻量级抽象
T
内存中某个。- 基本上是带有多个便捷方法的单个结构
{ T * ptr; std::size_t length; }
。(请注意,这与之明显不同,std::array<>
因为aspan
启用了便捷访问器方法,可与std::array
,通过指向类型T
和类型的长度(元素的数量)的指针进行比较T
,而std::array
实际上是一个容器,其中包含一个或多个类型的值T
。)- 非所有类型(即“引用类型”而不是“值类型”):它从不分配或取消分配任何东西,也不使智能指针保持活动状态。
它以前被称为an
array_view
,甚至更早于array_ref
。
这些大胆的部分对于理解至关重要,因此请不要错过或误读它们!A span
不是结构的C数组,也不是类型为C的数组的结构T
加上数组的长度(实质上就是std::array
容器的长度),NOR也不是指针的C数组类型T
加上长度,但是它是一个单一结构,包含一个指向type的T
单个指针和length,这是指向类型的指针所指向的连续内存块中(type T
)元素T
这样,您使用的数量!span
是存储指针和长度的变量,以及您使用的所有便捷访问器函数,span
提供。
这是UNLIKE a,std::array<>
因为std::array<>
实际上是为整个连续块分配内存,而这是UNLIKE,std::vector<>
因为a std::vector
基本上是一个a std::array
,每次填充时它也会动态增长(通常是大小加倍),并且您尝试向其添加其他内容。A std::array
是固定大小的,a span
甚至不管理它所指向的块的内存,它仅指向该内存块,知道该内存块有多长时间,知道C数组中的数据类型并提供便利的访问器功能以与该连续内存中的元素一起使用。
std::span
是C ++ 20起C ++标准的一部分。您可以在这里阅读其文档:https : //en.cppreference.com/w/cpp/container/span。要了解如何使用谷歌的absl::Span<T>(array, length)
在C ++ 11或更高版本的今天,见下文。
std::span<T, Extent>
(Extent
=“序列中元素的数量,或者std::dynamic_extent
是动态的。”跨度仅指向内存并使其易于访问,但无法对其进行管理!):
std::array<T, N>
(注意,它的大小是固定的N
!):
std::vector<T>
(根据需要自动动态增加尺寸):
span
在C ++ 11或更高版本中使用?Google已以其“ Abseil”库的形式开源了其内部C ++ 11库。该库旨在提供C ++ 14至C ++ 20以及在C ++ 11及更高版本中可用的其他功能,以便您可以在今天使用明天的功能。他们说:
与C ++标准的兼容性
Google开发了许多抽象,这些抽象与C ++ 14,C ++ 17及更高版本中包含的功能匹配或紧密匹配。使用这些抽象的Abseil版本,您可以立即访问这些功能,即使您的代码尚未准备好在C ++ 11后的世界中生活。
以下是一些关键资源和链接:
span.h
标头和absl::Span<T>(array, length)
模板类:https : //github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L189
std::span
是在2017年提出的。它适用于C ++ 17或C ++ 20。另请参阅P0122R5,span:对象序列的边界安全视图。您真的要针对该语言吗?编译器赶上来还需要几年的时间。