在 C++20 标准中,std::span 被引入为一种轻量级、非拥有的视图,能够安全、高效地对数组、std::vector、以及连续内存块进行切片和访问。相比传统的裸指针加长度的组合,std::span 提供了更直观的语义、内置的边界检查、以及兼容性强的 API,适用于需要频繁传递子数组的场景。
1. 基本概念
- 非拥有:
std::span不管理底层内存的生命周期,它只存储指向首元素的指针和元素数量。 - 连续性:只能用于连续内存的数据结构(如数组、
std::vector、std::array)。 - 类型安全:编译器会检查类型是否匹配,避免类型错误。
2. 声明与初始化
#include <span>
#include <vector>
#include <array>
#include <iostream>
int main() {
// 从数组创建 span
int arr[] = {1,2,3,4,5};
std::span <int> sp1(arr); // 自动推断大小
std::span <int> sp2(arr, 3); // 指定大小
// 从 std::vector 创建 span
std::vector <int> vec = {10,20,30,40,50,60};
std::span <int> sp3(vec); // 自动推断
std::span <int> sp4(vec.data(), 4); // 手动指定
// 从 std::array 创建 span
std::array<int, 5> aarr = {100,200,300,400,500};
std::span <int> sp5(aarr);
// 常量视图
std::span<const int> const_sp = sp1; // 只读
}
3. 访问与遍历
std::span 提供了类似容器的接口:
for (int x : sp3) {
std::cout << x << ' ';
}
std::cout << '\n';
for (size_t i = 0; i < sp3.size(); ++i) {
std::cout << sp3[i] << ' ';
}
此外,还可以使用 begin()、end()、data() 等标准容器方法。
4. 切片(子视图)
std::span 的 subspan() 方法可以创建原始视图的子视图,而不需要复制数据。
auto sub = sp3.subspan(2, 3); // 从索引 2 开始,长度 3
// sub 视图包含 vec[2], vec[3], vec[4]
如果只指定起始位置,子视图会持续到原始视图末尾:
auto tail = sp3.subspan(4); // 从索引 4 开始,长度自动推断
5. 边界检查
- 构造:
std::span的构造函数不会自动检查边界;如果你使用std::array或std::vector,构造时大小与实际长度相符。 - 访问:使用
at()访问元素会在调试模式下进行边界检查;使用operator[]则不检查。 - 子视图:
subspan()在调试模式下会检查起始位置和长度是否合法。
6. 性能优势
- 零成本抽象:
std::span在编译期被消除,实际产生的对象只有指针与长度两字段,完全不增加运行时开销。 - 内存复制避免:切片仅传递视图,不涉及数据复制,适合大数据切片场景。
- 兼容性:既可以直接用作函数参数,也能与旧代码的裸指针兼容。
7. 与传统指针比较
| 方案 | 优点 | 缺点 |
|---|---|---|
int* + size_t |
简单、低级别 | 需要手动管理边界,易出错 |
| `std::vector | ||
| ` | 动态大小、管理生命周期 | 复制时会复制元素,额外内存 |
| `std::span | ||
| ` | 轻量、安全、可切片 | 仅支持连续内存,不可直接持久化 |
8. 实战案例:快速排序子数组
void quicksort(std::span <int> sp) {
if (sp.size() <= 1) return;
int pivot = sp.back();
size_t i = 0, j = sp.size() - 1;
while (i < j) {
while (sp[i] <= pivot && i < j) ++i;
while (sp[j] >= pivot && i < j) --j;
std::swap(sp[i], sp[j]);
}
std::swap(sp[i], sp.back());
quicksort(sp.subspan(0, i));
quicksort(sp.subspan(i + 1));
}
这段代码展示了如何在不复制数组的情况下,对任意子区间递归排序。
9. 小结
std::span 为 C++20 引入的一个强大工具,它在保持极低运行时成本的同时,提升了代码可读性与安全性。对于需要频繁传递子数组、切片或只读视图的场景,推荐优先使用 std::span,既避免了指针带来的隐患,又享受了容器 API 的便利。
在后续的 C++ 开发中,合理利用 std::span 可以让代码更简洁、更易维护,并且在性能上几乎与裸指针相当,甚至更好。希望这篇文章能帮助你快速掌握并应用 std::span。