std::span 是 C++20 引入的一种轻量级、非拥有的数组视图,旨在提供一种安全且高效的方式来传递连续存储的数据。相比传统的指针+长度或 std::array、std::vector,std::span 兼具灵活性与安全性,能够显著减少边界错误、内存泄漏等问题。
1. std::span 的基本特性
| 特性 | 说明 |
|---|---|
| 非拥有 | span 不管理内存,它只引用已有的数据。 |
| 长度可变 | 通过模板参数或构造函数可以指定长度;若未指定,长度由传入容器决定。 |
| 兼容 C 风格数组 | 可以直接构造 `span |
来引用int arr[10];` |
|
| 静态长度 | std::span<int, 10> 只接受长度为 10 的视图,编译期检查。 |
与 std::vector、std::array、std::string_view 互操作 |
通过 data() 与 size() 获取指针与长度即可。 |
2. 如何创建 std::span
int arr[5] = {1, 2, 3, 4, 5};
// 1. 基于 C 数组
std::span <int> sp1(arr); // 自动推断长度 5
// 2. 基于 std::array
std::array<int, 4> a{{10,20,30,40}};
std::span <int> sp2(a); // 长度 4
// 3. 基于 std::vector
std::vector <double> vec{1.1, 2.2, 3.3};
std::span <double> sp3(vec); // 长度 vec.size()
// 4. 只引用子范围
auto sub = sp1.subspan(1, 3); // 指向 {2,3,4}
3. 边界检查与异常安全
std::span 本身不做运行时边界检查。若需要安全访问,可使用 operator[] 并自行检查索引范围,或使用 std::span::at()(C++23 中新增),返回引用并在越界时抛出 std::out_of_range。
int x = sp1[2]; // 直接访问,无检查
if (idx < sp1.size()) { // 手动检查
int y = sp1[idx];
}
提示:在多线程环境下,确保数据在整个
span生命周期内不被修改或删除,避免悬空引用。
4. 与算法的配合
std::span 可以直接与标准算法配合,省去了手动传递指针与长度。
std::span <int> sp{arr, 5};
std::sort(sp.begin(), sp.end()); // 对 arr 进行排序
5. 示例:实现一个安全的加法函数
#include <span>
#include <iostream>
#include <vector>
double sum(std::span<const double> data) {
double total = 0.0;
for (double v : data) {
total += v;
}
return total;
}
int main() {
std::vector <double> values{1.5, 2.5, 3.0};
std::cout << "Sum: " << sum(values) << '\n';
double arr[4] = {4.0, 5.0, 6.0, 7.0};
std::cout << "Sum: " << sum(arr) << '\n';
}
std::span<const double>声明不可变视图,保证函数内部不会修改传入的数据。- 该函数既能接受
std::vector、std::array、C 数组,也能接受任何支持data()与size()的容器。
6. 静态长度的优势
std::span<int, 5> fixedSp{arr}; // 只允许长度为5
- 编译器在编译期检查长度,防止误传错误大小的视图。
- 适用于需要在函数参数中强制限定长度的场景,如固定协议头、硬件寄存器映射等。
7. 与 std::string_view 的区别
| 对象 | 所引用的数据 | 所有权 | 适用场景 |
|---|---|---|---|
std::span |
任意连续存储 | 无 | 数组、矩阵、缓冲区 |
std::string_view |
只读字符序列 | 无 | 字符串处理、文件路径 |
需要注意,
std::string_view对字符数据有特殊处理(如char_traits),而std::span更通用。
8. 常见坑点与最佳实践
- 悬空引用:不要让
span超出原始容器生命周期。 - 多线程写操作:若多个线程同时写,需同步锁。
- 子范围复合:使用
subspan时,若基容器发生移动,子span仍保持指向旧位置。 - 不要滥用
const:只在必要时使用const,以保持灵活性。
9. 未来展望
- C++23 新增
std::span::at(),提供越界检查。 - 进一步的容器视图如
std::ranges::view::subrange与span的结合,为算法提供更丰富的接口。
通过以上内容,你可以在 C++20 中安全、高效地使用 std::span,充分利用其对数组与容器的视图功能,提升代码的可读性与可维护性。