C++-span

引入

看看如下函数定义:

1
2
3
DoSomething(char (&arr) [10]);     // 10个元素的数组, sizeof(arr)==10*sizeof(char)==10
DoSomething(char arr[]); // arr退化为指针, sizeof(arr)==sizeof(char*)
DoSomething(char *arr, size_t n); // 处理任意含n个char的连续内存

通常我们为了程序的复用性,会使用最后DoSomething(char *arr, size_t n);的形式,通过传入字符指针和长度灵活处理。但可能出现以下情况:

1
2
char str[10];
DoSomething(str, 20);

可能造成数组越界问题,如果需要解决这个问题,则需要进行如下繁琐操作:

1
2
char str[10];
DoSomething(str, sizeof(str)/sizeof(str[0]));

这里,引入std::span,用于范围检查。cppreference中对其的解释是类模板 span 所描述的对象能指代对象的相接序列,序列的首元素在零位置。 span 能拥有静态长度,该情况下序列中的元素数已知并编码于类型中,或拥有动态长度。典型实现只保有两个成员:指向 T 的指针和大小。

详解

std::span是指向一组连续的对象的对象, 是一个视图view, 不是一个拥有者owner

一组连续的对象可以是 C 数组, 带着大小的指针, std::array, 或者std::string。

std::span可以有两种范围:

  • 静态范围static extend:编译期就可以确定大小;
  • 动态范围dynamic extend:由指向第一个对象的指针和连续对象的大小组成;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <ranges>
#include <vector>
#include <iostream>
#include <span>
#include <format>

void printSpan(std::span<int> container)
{
std::cout << container.size();
}

int main()
{
std::vector v1{1, 2, 3, 4, 5, 8};
std::vector v2{9, 2, 4, 2, 6, 78};

std::span<int> dynamicSpan(v1);
std::span<int, 6> staticSpan(v2);

printSpan(dynamicSpan);
printSpan(staticSpan);
}

总结

无法确定长度的连续内存数组形参都可以使用std::span