跳转到内容

什么是`std::span`?为什么用?怎么用?

有时代码中不可避免要使用 C 风格数组,C++20引入std::span来帮助我们避免使用 C 风格数组时会遇到的大部分问题。

可以像直接使用C风格数组那样使用std::span,除此之外它有一个成员函数.size()用于获取底层数组的大小,还有很多其他方便的功能。

std::span并不拥有底层元素,只是提供一个轻量的包装器让我们以更友好的方式访问底层容器。

std::span是一个视图(view)。“视图”(view)通常指的是一种不拥有底层数据的抽象,提供对某些数据的只读或只写访问,而不复制数据本身。视图用于高效地处理数据片段,而不引入不必要的开销。

例子:使用std::span作为函数参数类型,接收C风格数组

#include <span>
#include <iostream>
void handle(std::span<int> values) {
std::cout << "Span Size: " << values.size() << std::endl;
}
int main() {
int values[]{1,2,3,4,5};
handle(values);
return 0;
}

如果不使用std::span,C风格数组名在作为参数传入后,将衰减为指针,就无法再从其本身得知数组的大小。而使用std::span,可以使用size()成员访问其大小。

例子:使用std::span作为函数参数类型,接收多种类型的容器

可以为任何其元素储存在连续内存区域的容器创建std::span,如std::vectorstd::array,或者其他合适的自定义类型。这样使用std::span的目的是,能够为任何类型的数组创建简单、一致的接口。

#include <span>
#include <iostream>
#include <vector>
void handle(std::span<int> values) {
std::cout << "Span Size: " << values.size() << std::endl;
std::cout << "First element: " << values.front() << std::endl;
}
int main() {
int values[]{1,2,3,4,5};
std::vector values2{6, 7, 8};
std::array values3{9, 10, 11};
handle(values);
handle(values2);
handle(values3);
return 0;
}

例子:使用模板std::span作为函数参数类型,接收多种类型的容器,指定容器中元素的类型

#include <span>
#include <iostream>
#include <vector>
template<typename T>
void handle(std::span<T> values) {
std::cout << "Span Size: " << values.size() << std::endl;
std::cout << "First element: " << values.front() << std::endl;
}
int main() {
int values[]{1,2,3,4,5};
std::vector values2{6.3, 7.4, 8.5};
std::array values3{9.7f, 10.2f, 11.3f};
handle<int>(values);
handle<double>(values2);
handle<float>(values3);
return 0;
}

例子:从std::span得到C风格数组

使用 data() 成员函数可以得到指向第一个元素的指针。

使用场景是,为接收C风格数组的函数提供参数。这种情况,通常还需要提供大小。

#include <span>
#include <iostream>
void handle(int values[], std::size_t size) {
for (std::size_t i{0}; i < size; ++i) {
std::cout << values[i] << ", ";
}
std::cout << std::endl;
}
int main() {
int values[]{1,2,3,4,5};
std::span<int> spanInt{values};
handle(spanInt.data(), spanInt.size());
return 0;
}

例子:从std::span创建容器,使用迭代器

int values[]{1,2,3,4,5};
std::span<int> spanInt{values};
std::vector<int> valuesVec{spanInt.begin(), spanInt.end()};

如果创建std::vector是为了将其作为函数参数传递,那么可以像下面这样做,避免创建中间副本:

#include <span>
#include <iostream>
#include <vector>
void handleVector(std::vector<int> values) {
for (const int & x : values) {
std::cout << x << ", ";
}
std::cout << std::endl;
}
int main() {
int values[]{1,2,3,4,5};
std::span<int> spanInt{values};
handleVector({spanInt.begin(), spanInt.end()});
return 0;
}

例子:使用.first(), .last(), .subspan()创建子std::span

#include <span>
#include <iostream>
void handle(std::span<int> values) {
for (const int & x : values) {
std::cout << x << ", ";
}
std::cout << std::endl;
}
int main() {
int values[]{1,2,3,4,5};
std::span<int> spanInt{values};
std::span<int> spanFirst3{spanInt.first(3)}; // 1, 2, 3
std::span<int> spanLast3{spanInt.last(3)}; // 3, 4, 5
std::span<int> spanMid{spanInt.subspan(1,3)}; // 2, 3, 4
handle(spanFirst3);
handle(spanLast3);
handle(spanMid);
return 0;
}

参考