在 C++20 中引入的 std::span 为数组、容器和裸数组提供了一种轻量级、无所有权的视图,使得安全遍历和切片操作变得异常简洁。下面从概念、使用场景、典型代码以及性能与安全性四个维度进行深入剖析,帮助你在实际项目中高效、可靠地使用 std::span。
1. 什么是 std::span?
std::span 是一个模板类,用来描述一段连续存储区(如数组、std::vector、std::array、裸数组等)的非所有权视图。它包含两个核心成员:
T* data_:指向首元素的指针。size_t size_:元素数量(Extent 可为动态或固定值)。
与 std::vector、std::array 等拥有所有权的容器不同,span 只提供对已有存储的访问,不负责内存分配或释放,使用更安全、轻量。
2. 典型使用场景
| 场景 | 说明 | 示例 |
|---|---|---|
| 函数参数 | 接受数组、容器等任意连续区块 | `void process(span |
| data)` | ||
| 切片 | 在不复制数据的前提下截取子段 | auto sub = full_span.subspan(5, 10); |
| 统一接口 | 对多种容器提供统一的遍历方式 | for (auto v : span(container)) … |
| 安全边界检查 | 通过 size() 进行范围检查,避免越界 |
if (idx < data.size()) … |
3. 基础用法示例
#include <iostream>
#include <span>
#include <vector>
#include <array>
void print(span <int> s) {
for (int v : s) std::cout << v << ' ';
std::cout << '\n';
}
int main() {
int arr[5] = {1,2,3,4,5};
vector <int> vec = {10,20,30,40,50,60};
// 直接传递裸数组
print(arr);
// 传递 vector 的 span
print(span <int>(vec));
// 传递 std::array
array<int,4> a{100,200,300,400};
print(a);
// 切片示例
auto sub = vec.subspan(2,3); // 取 vec[2],vec[3],vec[4]
print(sub);
}
运行结果:
1 2 3 4 5
10 20 30 40 50 60
100 200 300 400
30 40 50
重要细节
-
构造
span<T, N>可通过span<T> s{arr}或span<T> s{arr, N}初始化。- 对
std::vector、std::array直接隐式构造。 - 对裸数组
int*+size亦可构造。
-
子切片
subspan(offset)返回从offset开始、到结尾的视图。subspan(offset, length)返回指定长度的视图。- 若
offset + length > size(),会throw std::out_of_range。
-
空视图
- `span ()` 创建空视图。
- `span empty{}` 亦可。
4. 与传统指针和引用的对比
| 方式 | 优点 | 缺点 |
|---|---|---|
| 原始指针 + 长度 | 简洁 | 易出错,缺乏边界检查 |
| std::array / std::vector | 拥有所有权 | 不适合只需要读访问时的无所有权需求 |
| std::span | 轻量、无所有权、边界检查 | 仅支持连续内存 |
5. 性能考量
- 无拷贝:span 本身仅保存指针和长度,复制成本极低(两个指针)。
- 对齐与缓存:与裸指针等效,编译器可进行同等级别的优化。
- ABI 兼容:span 的 ABI 与结构体相同,能与 C 接口无缝对接。
6. 安全性与常见陷阱
-
悬空引用
- span 指向的底层数据若在 span 生命周期内被销毁,使用将导致未定义行为。
- 解决方案:保证 span 生命周期不超过底层数据。
-
长度超界
subspan需要手动检查范围,否则抛异常。- 推荐在调用前先用
size()验证。
-
空指针
span可以合法持有空指针(如 `span {nullptr, 0}`)。- 避免对
data()的解引用,先检查empty()。
7. 与 C++23 迭代器的整合
C++23 对 span 引入了更丰富的迭代器支持(如 span::begin()、span::end()),可以直接使用标准算法:
#include <algorithm>
auto s = span <int>{vec};
std::sort(s.begin(), s.end()); // 对原 vector 进行排序
8. 小结
std::span 在 C++20 里是一个极具价值的工具,它把“视图”概念与容器无缝结合。通过使用 span:
- 代码更简洁、可读性更高。
- 函数接口更通用,支持多种容器。
- 同时提供安全边界检查,降低越界风险。
- 性能几乎不受影响,接近裸指针级别。
掌握 std::span 的使用,你的 C++ 代码将在接口设计、性能与安全性之间实现更优的平衡。