std::span 是 C++20 标准库中新加入的一个轻量级容器视图,旨在为数组、std::vector、甚至是普通指针+长度组合提供一种安全、无所有权的视图。通过使用 std::span,可以在不复制数据的情况下,传递和操作子范围,既保留了性能,又避免了传统裸指针可能导致的越界错误。
1. std::span 的基本概念
- 无所有权:
std::span只持有一个指针和长度信息,不负责内存分配或释放。 - 大小已知:编译时如果使用固定大小的数组,可以得到
std::span<T, N>,其中N是编译时已知的长度;否则使用动态大小的std::span<T>。 - 兼容性:可以从
T*、std::array、std::vector、甚至是std::initializer_list构造std::span。
2. 示例一:从普通数组创建 std::span
#include <iostream>
#include <span>
void process(std::span <int> data) {
for (int &x : data)
x *= 2;
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
std::span <int> whole(arr); // 视图整个数组
std::span <int> part(arr + 1, 3); // 视图从第二个元素开始的 3 个元素
process(whole);
process(part);
for (int x : arr)
std::cout << x << ' ';
std::cout << '\n'; // 输出:2 6 8 10 10
}
说明
whole视图覆盖整个数组;part仅覆盖中间 3 个元素;process在视图范围内直接修改原始数组。
3. 示例二:从 std::vector 创建 std::span
#include <vector>
#include <span>
#include <algorithm>
#include <iostream>
void sort_span(std::span <int> data) {
std::sort(data.begin(), data.end());
}
int main() {
std::vector <int> vec = {9, 1, 5, 3, 7};
std::span <int> view(vec); // 视图整个 vector
std::span <int> sub(view.data() + 1, 3); // 视图 vec[1..3]
sort_span(view); // 整个 vector 排序
sort_span(sub); // 只对子范围排序
for (int x : vec)
std::cout << x << ' ';
std::cout << '\n'; // 输出:1 3 5 7 9
}
说明
std::span兼容std::vector的内部连续内存;- 对子范围排序不会影响
view的其余部分。
4. 防止越界的安全性
传统的裸指针传递数组子范围时,常见错误是忘记减去偏移量导致访问越界。std::span 通过在构造时检查范围长度来避免这种错误。
int arr[3] = {10, 20, 30};
std::span <int> bad(arr + 1, 3); // 只剩 2 个元素,但指定 3 → 抛出 std::out_of_range
此时编译器不会捕获错误,但运行时 std::span 在构造时会抛出异常(如果已开启检查),或者在 debug 模式下产生断言。
5. 与 std::array 的结合
使用 std::array 时可以得到编译时已知大小的 std::span<T, N>:
#include <array>
#include <span>
int main() {
std::array<int, 5> a = {1,2,3,4,5};
std::span<int, 5> s(a); // 视图整个数组,大小 5 已知
}
此种方式提供了更强的类型安全,编译器可以在编译阶段检查长度。
6. 常见用途
- 函数接口:使用
std::span代替T*+size_t,减少参数数量并提高可读性。 - 切片操作:在不复制数据的情况下操作子数组,例如矩阵行/列切片。
- 互操作性:与 C 代码共享数组数据时,可直接转换为
std::span。
7. 性能评估
- 零成本:
std::span仅包含指针和长度两个成员,大小与T*+size_t相同。 - 无复制:所有操作都是对原始数据的视图,避免了深拷贝。
- 编译时检查:在固定长度情况下,编译器可进一步优化。
8. 结语
std::span 的出现大大简化了 C++ 对数组和容器子范围的操作,提供了既安全又高效的方式。通过将其与 STL 算法、模板编程相结合,能够写出更简洁、可维护且错误率更低的代码。下次在需要传递数组片段时,试试用 std::span 替换裸指针,感受一下它的“轻量”与“安全”。