最近我得到了建议在我的代码中使用 span<T>
,或者在这个网站上看到了一些使用 span
的答案 - 据说是某种容器。但是 - 我在 C++17 标准库中找不到类似的东西。
那么这个神秘的 span<T>
是什么,为什么(或何时)使用它是一个好主意,如果它是非标准的呢?
最近我得到了建议在我的代码中使用 span<T>
,或者在这个网站上看到了一些使用 span
的答案 - 据说是某种容器。但是 - 我在 C++17 标准库中找不到类似的东西。
那么这个神秘的 span<T>
是什么,为什么(或何时)使用它是一个好主意,如果它是非标准的呢?
span<T>
是:
T
的值的抽象。struct { T * ptr; std::size_t length; }
。它曾经被称为 array_view
,甚至更早的时候是 array_ref
。
首先,不应该使用 span 的情况:
std::sort
、std::find_if
、std::copy
和其他来自 <algorithm>
的模板化函数)以及不接受任意范围的代码(有关这些,请参见 C++20 范围库的信息)。与一对迭代器或范围相比,span 具有更严格的要求:元素连续性和元素存在于内存中。现在让我们看看什么时候使用 span:
Use
span<T>
(respectively,span<const T>
) instead of a free-standingT*
(respectivelyconst T*
) when the allocated length or size also matter. So, replace functions like:
void read_into(int* buffer, size_t buffer_size);
with:
void read_into(span<int> buffer);
哦,span很棒!使用span...
means that you can work with that pointer+length / start+end pointer combination like you would with a fancy, pimped-out standard library container, e.g.:
for (auto& x : my_span) { /* do stuff */ }
std::find_if(my_span.cbegin(), my_span.cend(), some_predicate);
std::ranges::find_if(my_span, some_predicate);
(in C++20)
... but with absolutely none of the overhead most container classes incur.
lets the compiler do more work for you sometimes. For example, this:
int buffer[BUFFER_SIZE];
read_into(buffer, BUFFER_SIZE);
becomes this:
int buffer[BUFFER_SIZE];
read_into(buffer);
... which will do what you would want it to do. See also Guideline P.5.
is the reasonable alternative to passing const vector<T>&
to functions when you expect your data to be contiguous in memory. No more getting scolded by high-and-mighty C++ gurus!
facilitates static analysis, so the compiler might be able to help you catch silly bugs.
allows for debug-compilation instrumentation for runtime bounds-checking (i.e. span
's methods will have some bounds-checking code within #ifndef NDEBUG
... #endif
)
indicates that your code (that's using the span) doesn't own the pointed-to memory.
使用span有更多的动机,你可以在C++核心准则中找到,但你已经掌握了要领。
编辑:是的,std::span
已经在C++20版本中添加到了C++中!
为什么只有在C++20中才有?虽然这个想法并不新鲜——它的现在形式是与C++核心准则项目一起构思出来的,该项目直到2015年才开始形成。所以花费了一段时间。
它是Core Guidelines支持库(GSL)的一部分。实现:
GSL实现通常假设平台支持C++14 [12]。这些替代的单头文件实现不依赖于GSL设施:
martinmoene/span-lite
需要C++98或更高版本tcbrindle/span
需要C++11或更高版本请注意,这些不同的span实现在其提供的方法/支持函数方面存在一些差异;它们也可能与C++20标准库中采用的版本有所不同。
进一步阅读:您可以在C++17最终官方提案P0122R7中找到所有细节和设计考虑因素:span: 对象序列的边界安全视图,作者是Neal Macintosh和Stephan J. Lavavej。尽管有点长。此外,在C++20中,跨度比较语义发生了变化(遵循Tony van Eerd的这篇短文)。
std::cout << sizeof(buffer) << '\n'
,你会发现你得到的是100个整型的大小。 - einpoklumstd::array
是一个容器,它拥有其中的值。而span
则是一种非拥有型容器。 - Calethstd::array
是一种完全不同的数据结构。它在编译时长度固定,并且是值类型而不是引用类型,如Caleth所解释的那样。 - einpoklumspan<T>
是这样的:template <typename T>
struct span
{
T * ptr_to_array; // pointer to a contiguous C-style array of data
// (which memory is NOT allocated nor deallocated
// nor in any way managed by the span)
std::size_t length; // number of elements of type `T` in the array
// Plus a bunch of constructors and convenience accessor methods here
}
@einpoklum在他的回答中已经很好地介绍了span
是什么,请参见此处。然而,即使阅读了他的回答,对于对span
新手来说仍然容易产生一连串没有完全回答的思维问题,例如以下问题:
span
与C数组有何不同?为什么不直接使用一个C数组?它似乎只是具有已知大小的C数组...std::array
,span
与之有何不同?std::vector
也像std::array
一样吗?span
?因此,这里提供了一些额外的清晰度:
他的回答的直接引用--带有我的补充说明和括号注释以及我的强调部分:
它是什么?
span<T>
是:
以前被称为
- 类型为
T
的连续值序列在内存中的非常轻量级的抽象。- 基本上是一个
{T * ptr; std::size_t length;}
结构体与一堆方便的方法。(请注意,这与std::array<>
明显不同,因为span
通过指向类型T
和长度(元素数)为类型T
的指针启用了方便的访问器方法,类似于std::array
,而std::array
是实际容器,其中包含一个或多个类型为T
的值。)- 非拥有类型(即"引用类型"而不是"值类型"):它从不分配也不释放任何东西,也不会保持智能指针的生命。
array_view
,更早是array_ref
。span
不是C结构体数组,也不是类型为T
的C数组和数组长度的结构体(这实际上就是std::array
容器),也不是指向类型T
的结构体指针的C数组加上长度,而是一个只包含单个指向类型T
的指针和长度的单一结构体,并且该长度是指该指向类型T
的指针所指连续内存块中的元素数量(类型为T
)。因此,使用span
添加的唯一开销是用于存储指针和长度的变量,以及您使用的任何便利访问器函数,由span
提供。std::array<>
不同,因为std::array<>
实际上为整个连续块分配内存;它与std::vector<>
不同,因为std::vector
基本上只是一个std::array
,它还可以进行动态增长(通常是加倍大小),每当它填满并尝试添加其他东西时。 std::array
的大小是固定的;而span
甚至不管理其指向的内存块的内存,它只是指向内存块,知道内存块的长度,知道内存中C数组中的数据类型,并提供方便的访问器函数以处理该连续内存中的元素。
std::span
是C++20标准的一部分。您可以在此处阅读其文档:https://en.cppreference.com/w/cpp/container/span。要了解如何在今天的C++11或更高版本中使用Google的absl::Span<T>(array, length)
,请参见下文。
std::span<T, Extent>
(Extent
= "序列中元素的数量,如果是动态的,则为 std::dynamic_extent
"。一个 span 只是指向内存并使其易于访问,但它不管理它!):std::array<T, N>
(注意它具有固定大小的 N
!):std::vector<T>
(根据需要自动动态增长大小):span
?谷歌已将其内部 C++11 库开源,形成了他们的 "Abseil" 库。该库旨在提供 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#L153std::array
是C风格数组的包装器。std::span
是对连续序列(例如C风格数组)的引用。 - John McFarlanespan
是指针和长度变量(加上函数)的包装器。这就是我所说的“轻量级”。 - Gabriel Staplesspan
可以从连续的迭代器构造,但这些迭代器不一定是指针。例如,vector::iterator
是连续的,但不是指针。你引用的评论实际上归结为“使用合适的工具来完成任务”。如果你正在编写一个对任意序列进行操作的算法/实用程序,不要尝试将其擦除到 span
中,因为这会防止使用任何常规容器。然而,如果你想要一个表示非拥有连续数据的容器,那么 span
很可能是正确的选择。 - Human-Compilervoid qs( span<T> data ) {
split(data);
qs( span{ data+0,sizeoffirst } );
qs( span{ data+sizeoffirst,sizeofsecond } );
std::span
是在2017年提出的。它适用于C++17或C++20。另请参见P0122R5,span:用于对象序列的边界安全视图。你真的想要以那种语言为目标吗?编译器追赶上来还需要几年时间。 - jwwspan
非常适用……作为gsl::span
而不是std::span
。请参见下面的答案。 - einpoklum