💡 如果想阅读最新的文章,或者有技术问题需要交流和沟通,可搜索并关注微信公众号“希望睿智”。
为什么要引入std::span
在C++中,我们经常需要传递数组或容器的一部分给函数进行处理。通常的做法是使用指针和长度来表示数组的一部分,但这可能导致越界和难以维护的代码。C++ 20中新引入的std::span,则提供了一种更安全、更直观的方式来处理这种情况。
实际上,std::span是一个非常实用的容器适配器,用于表示连续的内存区域。它并不直接拥有数据,而是提供了一种访问现有数组或容器元素的方式,增强了代码的灵活性和泛型性。std::span可以看作是对指针和长度对的一个类型安全、范围安全的封装,特别适合用于算法和接口设计中,以提高代码的可重用性和安全性。
std::span基础
std::span<T>是一个模板类,其中T是元素的类型,它可以通过数组、标准库容器或其他任何提供迭代器的对象来构造。std::span有两个关键属性:一个是指向连续数据元素的指针,另一个表示元素数量的size_t值。这使得它可以安全地访问并操作数据范围内的元素,而不需要知道数据的具体存储方式。std::span提供了类似数组的接口,允许我们通过索引访问元素,并提供了size()、empty()、begin()、end()等成员函数来获取关于数据的信息。
std::span的主要特点如下。
1、非拥有性。std::span不拥有它所引用的数据,它只是一个视图。
2、类型安全。std::span知道它所引用的数据的类型。
3、边界检查。由于std::span知道数据的大小,因此可以在访问时执行边界检查。
4、灵活性。std::span可以与数组、std::vector、std::array等容器一起使用。
std::span的使用
创建一个std::span的对象有好几种方法,包括:默认构造函数、从数组构造、从指针和大小构造、从一对指针构造、从容器构造、具有指定大小的构造等。访问std::span中的元素有三种方法,分别为:通过索引访问元素、带有边界检查的访问元素方法at()、访问第一个和最后一个元素的front()/back()。
下面的示例代码演示了这些方法的使用,可以作为参考。
#include <iostream>
#include <span>
#include <vector>
using namespace std;
int main()
{
// 默认构造函数
span<int> emptySpan;
// 输出:0
cout << emptySpan.size() << endl;
// 从数组构造
int pNumber[]{10, 20, 30, 40, 50};
span<int> arraySpan(pNumber);
// 输出:10
cout << arraySpan.front() << endl;
// 从指针和大小构造
int* pData = pNumber;
span<int> ptrAndSizeSpan(pData, 3);
// 输出:30
cout << ptrAndSizeSpan[2] << endl;
// 从一对指针(首尾指针)构造
span<int> pairPtrSpan(pData, pData + 2);
// 输出:20
cout << pairPtrSpan[1] << endl;
// 从容器构造
vector<int> vctNumber = {66, 77, 88};
span<int> containerSpan(vctNumber);
// 输出:88
cout << containerSpan.back() << endl;
// 具有指定大小的构造(静态大小)
span<int, 3> staticSizeSpan(pNumber);
// 输出:20
cout << staticSizeSpan[1] << endl;
return 0;
}
除了以上这些接口,std::span还可以用来生成一个新的std::span。subspan()函数可生成一个新的span,表示当前span中的一部分。first()函数返回span的前count个元素组成的span,last()函数返回span的后count个元素组成的span。
#include <iostream>
#include <span>
#include <vector>
using namespace std;
int main()
{
vector<int> numbers = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
span<int> fullSpan(numbers);
// 使用subspan()方法生成新span(从索引3开始,长度为4的新span)
span<int> subSpan = fullSpan.subspan(3, 4);
// 输出:3 4 5 6
for(int num : subSpan)
cout << num << " ";
cout << endl;
// 使用first()方法获取前几个元素的span
span<int> span1 = fullSpan.first(3);
// 输出:0 1 2
for(int num : span1)
cout << num << " ";
cout << endl;
// 使用last()方法获取后几个元素的span
span<int> span2 = fullSpan.last(4);
// 输出:6 7 8 9
for(int num : span2)
cout << num << " ";
cout << endl;
return 0;
}
注意事项
1、std::span不拥有数据,因此不能延长其生命周期。当底层数组或容器销毁时,std::span变为悬挂引用,使用它将导致未定义行为。
2、尽管std::span提供了丰富的接口,但它不支持直接改变大小或直接插入/删除元素,因为它是对现有数据的视图。