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

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


当前回答

是什么?

span<T>为:

内存中某个地方的T类型连续值序列的非常轻量级抽象。 基本上是一个struct {T * ptr;std:: size_t长度;}用一堆方便的方法。 非拥有类型(即“引用类型”而不是“值类型”):它从不分配或释放任何东西,也不保持智能指针处于活动状态。

它以前被称为array_view,甚至更早的是array_ref。

什么时候使用?

首先,何时不使用span:

Don't use a span in code that could just take any pair of start & end iterators (like std::sort, std::find_if, std::copy and other templated functions from <algorithm>), and also not in code that takes an arbitrary range (see The C++20 ranges library for information about those). A span has stricter requirements than a pair of iterators or a range: element contiguity and presence of the elements in memory. Don't use a span if you have a standard library container (or a Boost container etc.) which you know is the right fit for your code. spans are not intended to supplant existing containers.

现在来看看什么时候使用span:

当分配的长度或大小也很重要时,使用span<T>(分别为span<const T>)而不是独立的T*(分别为const T*)。因此,替换函数如下: Void read_into(int* buffer, size_t buffer_size); : Void read_into(span<int> buffer)

我为什么要用它?为什么这是一件好事?

哦,跨度太棒了!使用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++的核心指导方针中找到——但是你能领会其中的大意。

但它在标准库中吗?

edit:是的,std::span被添加到c++语言的c++ 20版本中!

为什么只有c++ 20?好吧,虽然这个想法并不新鲜——它目前的形式是与c++核心指导方针项目一起构思的,该项目在2015年才开始成型。所以花了一段时间。

那么,如果我正在编写c++ 17或更早的版本,该如何使用它呢?

它是核心指南支持库(GSL)的一部分。实现:

Microsoft / Neil Macintosh的GSL包含一个独立的实现:GSL /span GSL- lite是整个GSL的单头实现(不用担心,它没有那么大),包括span<T>。

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的这篇短文之后)。

其他回答

是什么?

span<T>为:

内存中某个地方的T类型连续值序列的非常轻量级抽象。 基本上是一个struct {T * ptr;std:: size_t长度;}用一堆方便的方法。 非拥有类型(即“引用类型”而不是“值类型”):它从不分配或释放任何东西,也不保持智能指针处于活动状态。

它以前被称为array_view,甚至更早的是array_ref。

什么时候使用?

首先,何时不使用span:

Don't use a span in code that could just take any pair of start & end iterators (like std::sort, std::find_if, std::copy and other templated functions from <algorithm>), and also not in code that takes an arbitrary range (see The C++20 ranges library for information about those). A span has stricter requirements than a pair of iterators or a range: element contiguity and presence of the elements in memory. Don't use a span if you have a standard library container (or a Boost container etc.) which you know is the right fit for your code. spans are not intended to supplant existing containers.

现在来看看什么时候使用span:

当分配的长度或大小也很重要时,使用span<T>(分别为span<const T>)而不是独立的T*(分别为const T*)。因此,替换函数如下: Void read_into(int* buffer, size_t buffer_size); : Void read_into(span<int> buffer)

我为什么要用它?为什么这是一件好事?

哦,跨度太棒了!使用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++的核心指导方针中找到——但是你能领会其中的大意。

但它在标准库中吗?

edit:是的,std::span被添加到c++语言的c++ 20版本中!

为什么只有c++ 20?好吧,虽然这个想法并不新鲜——它目前的形式是与c++核心指导方针项目一起构思的,该项目在2015年才开始成型。所以花了一段时间。

那么,如果我正在编写c++ 17或更早的版本,该如何使用它呢?

它是核心指南支持库(GSL)的一部分。实现:

Microsoft / Neil Macintosh的GSL包含一个独立的实现:GSL /span GSL- lite是整个GSL的单头实现(不用担心,它没有那么大),包括span<T>。

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的这篇短文之后)。

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 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
}

它是C风格数组的轻量级包装器,c++开发人员在使用C库时,为了“类型安全”、“c++风格”和“感觉良好”,喜欢使用c++风格的数据容器包装它们。:)

注意:我将上面定义的结构容器(称为span)称为“c风格数组的轻量级包装器”,因为它指向一个连续的内存块,例如c风格数组,并使用访问器方法和数组的大小包装它。这就是我所说的“轻量级包装器”:它是一个包含指针和长度变量以及函数的包装器。

然而,与std::vector<>和其他c++标准容器不同的是,这些标准容器也可能只有固定的类大小,并包含指向其存储内存的指针,span不拥有它所指向的内存,并且永远不会删除它,调整它的大小,也不会自动分配新内存。同样,像vector这样的容器拥有它所指向的内存,并将对其进行管理(分配、重新分配等),但span不拥有它所指向的内存,因此也不会管理它。


进一步:

@einpoklum在他的回答中很好地介绍了张成的概念。然而,即使在阅读了他的回答后,对于新跨越的人来说,仍然很容易有一系列没有完全回答的问题,比如下面这些:

span与C数组有什么不同?为什么不直接用一个呢?它似乎只是那些众所周知的大小之一…… 等等,这听起来像一个std::array, span和它有什么不同? 哦,这提醒了我,std::vector不也像std::array吗? 我很困惑。什么是span?

所以,这里有一些更明确的解释:

直接引用他的回答——加上我的补充和插入注释,用粗体,我的强调用斜体:

是什么?

span<T>为:

A very lightweight abstraction of a contiguous sequence of values of type T somewhere in memory. Basically a single struct { T * ptr; std::size_t length; } with a bunch of convenience methods. (Notice this is distinctly different from std::array<> because a span enables convenience accessor methods, comparable to std::array, via a pointer to type T and length (number of elements) of type T, whereas std::array is an actual container which holds one or more values of type T.) A non-owning type (i.e. a "reference-type" rather than a "value type"): It never allocates nor deallocates anything and does not keep smart pointers alive.

它以前被称为array_view,甚至更早的是array_ref。

Those bold parts are critical to one's understanding, so don't miss them or misread them! A span is NOT a C-array of structs, nor is it a struct of a C-array of type T plus the length of the array (this would be essentially what the std::array container is), NOR is it a C-array of structs of pointers to type T plus the length, but rather it is a single struct containing one single pointer to type T, and the length, which is the number of elements (of type T) in the contiguous memory block that the pointer to type T points to! In this way, the only overhead you've added by using a span are the variables to store the pointer and length, and any convenience accessor functions you use which the span provides.

This is UNLIKE a std::array<> because the std::array<> actually allocates memory for the entire contiguous block, and it is UNLIKE std::vector<> because a std::vector is basically just a std::array that also does dynamic growing (usually doubling in size) each time it fills up and you try to add something else to it. A std::array is fixed in size, and a span doesn't even manage the memory of the block it points to, it just points to the block of memory, knows how long the block of memory is, knows what data type is in a C-array in the memory, and provides convenience accessor functions to work with the elements in that contiguous memory.

它是c++标准的一部分:

std::span是c++ 20标准的一部分。你可以在这里阅读它的文档:https://en.cppreference.com/w/cpp/container/span。要了解如何在今天的c++ 11或更高版本中使用谷歌的absl::Span<T>(array, length),请参阅下面。

摘要描述和主要参考文献:

std::span<T, Extent> (Extent = "the number of elements in the sequence, or std::dynamic_extent if dynamic". A span just points to memory and makes it easy to access, but does NOT manage it!): https://en.cppreference.com/w/cpp/container/span std::array<T, N> (notice it has a fixed size N!): https://en.cppreference.com/w/cpp/container/array http://www.cplusplus.com/reference/array/array/ std::vector<T> (automatically dynamically grows in size as necessary): https://en.cppreference.com/w/cpp/container/vector http://www.cplusplus.com/reference/vector/vector/

我如何在c++ 11或以后的今天使用span ?

谷歌以“Abseil”库的形式开放了他们的内部c++ 11库。这个库旨在提供c++ 14到c++ 20及以上版本的c++ 11及以后版本的特性,这样您就可以在今天使用明天的特性。他们说:

与c++标准的兼容性

谷歌开发了许多抽象,这些抽象要么与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>(数组,长度)模板类:https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L153

其他参考资料:

结构的模板变量在c++ 维基百科:c++类 c++类/结构成员的默认可见性

相关:

[另一个关于模板和跨度的回答]如何制作跨度的跨度

Einpoklum提供的答案很好,但我不得不深入评论部分来理解一个具体的细节,所以这意味着作为一个扩展来澄清这个细节。

首先,什么时候不使用: 不要在代码中使用它,因为代码可以取任意一对start和end 迭代器,比如std::sort, std::find_if, std::copy等等 超泛型模板函数。如果你有一个标准,就不要用它 库容器(或Boost容器等) 适合您的代码。它并不是要取代它们中的任何一个。

任意一对开始和结束迭代器,而不是连续存储的开始和结束指针。

作为一个很少接触迭代器内部的人,我在阅读答案时忘记了迭代器可以遍历链表,而简单的指针(和span)不能。