什么是“跨度”?何时使用?


236

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

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


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

6
@jww:span可以在C ++ 11中使用... gsl::span而不是std::span。另请参阅下面的我的答案。
einpoklum

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

1
@KeithThompson:在2017年还不是……
einpoklum

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

Answers:


272

它是什么?

A span<T>是:

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

它以前被称为an array_view,甚至更早地被称为array_ref

我什么时候应该使用它?

首先,什么时候使用它:

  • 不要在可能只使用一对开始和结束迭代器(例如std::sortstd::find_ifstd::copy以及所有这些超通用模板化函数的代码中使用它。
  • 如果您有一个标准库容器(或Boost容器等),并且知道该库适合您的代码,请不要使用它。并不是要取代它们中的任何一个。

现在,何时使用它:

使用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 ++ 17开始)?

它在标准库中-但仅从C ++ 20开始。原因是它与C ++核心指南项目(自2015年才开始形成)一起构思时,其当前形式仍然很新。(尽管评论者指出,它具有更早的历史。)

那么,如果它不在标准库中,该如何使用呢?

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

  • Microsoft / Neil Macintosh的GSL包含一个独立的实现:gsl/span
  • GSL-Lite是整个GSL的单头实现(不算大,不用担心),包括span<T>

一般而言,GSL实现确实假定一个平台实现了C ++ 14支持[ 14 ]。这些替代的单头实现不依赖于GSL设施:

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


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


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

3
@Deduplicator:范围正在C ++中,但是当前的建议(由Eric Niebler提出)需要对Concepts的支持。因此,在C ++ 20之前不存在。
einpoklum

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

4
@Jim std::array是一个容器,它拥有值。span没有所有权
Caleth

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

1

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

  1. 怎么样 span从C数组有什么不同?为什么不只使用其中之一?似乎只是已知尺寸的那些之一...
  2. 等一下,听起来像是std::array,这有什么span不同?
  3. 噢,这让我想起了,是不是std::vectorstd::array吗?
  4. 我很困惑。:( 什么是span

因此,这有一些额外的清晰度:

他的答案的直接报价- 我的加粗字体

它是什么?

A span<T>是:

  • 类型值的连续序列的非常轻量级抽象 T内存中某个。
  • 基本上是带有多个便捷方法的单个结构{ T * ptr; std::size_t length; }(请注意,这与之明显不同,std::array<>因为a span启用了便捷访问器方法,可与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数组中的数据类型并提供便利的访问器功能以与该连续内存中的元素一起使用

C ++标准的一部分:

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

摘要说明和主要参考资料:

  1. std::span<T, Extent>Extent=“序列中元素的数量,或者std::dynamic_extent是动态的。”跨度仅指向内存并使其易于访问,但无法对其进行管理!):
    1. https://en.cppreference.com/w/cpp/container/span
  2. std::array<T, N>(注意,它的大小是固定的N!):
    1. https://en.cppreference.com/w/cpp/container/array
    2. http://www.cplusplus.com/reference/array/array/
  3. std::vector<T> (根据需要自动动态增加尺寸):
    1. https://en.cppreference.com/w/cpp/container/vector
    2. http://www.cplusplus.com/reference/vector/vector/

今天如何span在C ++ 11或更高版本中使用

Google已以其“ Abseil”库的形式开源了其内部C ++ 11库。该库旨在提供C ++ 14至C ++ 20以及在C ++ 11及更高版本中可用的其他功能,以便您可以在今天使用明天的功能。他们说:

与C ++标准的兼容性

Google开发了许多抽象,这些抽象与C ++ 14,C ++ 17及更高版本中包含的功能匹配或紧密匹配。使用这些抽象的Abseil版本,您可以立即访问这些功能,即使您的代码尚未准备好在C ++ 11后的世界中生活。

以下是一些关键资源和链接:

  1. 主站点:https : //abseil.io/
  2. https://abseil.io/docs/cpp/
  3. GitHub存储库:https : //github.com/abseil/abseil-cpp
  4. span.h标头和absl::Span<T>(array, length)模板类:https : //github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L189

1
我想您提出了重要而有用的信息,谢谢!
桂丽玛
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.