是一个轻量级的视图类型,它允许我们在不复制数据的前提下安全地访问数组、容器和裸数组。下面从安全性、性能和使用场景三方面进行深入剖析。
1. 什么是 std::span?
std::span 代表了对连续元素序列的非拥有视图。它由指向首元素的指针和长度两部分组成,类似于 (T*, std::size_t)。由于它不管理内存,生命周期完全取决于被引用的数据。
2. 安全性
2.1 边界检查
在构造时,std::span 通过 std::size_t 保存长度,所有成员函数(如 operator[], at())都可以根据此长度做边界检查,防止越界访问。
2.2 与容器生命周期保持一致
std::span 只在引用对象存在时有效。若引用的数据已被销毁,使用该 span 会导致未定义行为;因此在设计时应确保 span 的生命周期不超过所引用的数据。
2.3 类型安全
由于 span 是模板,只有相同类型的元素才能构造。编译器会自动匹配类型,防止类型错误。
3. 性能优势
3.1 零复制
std::span 只存储指针和长度,不会复制元素,调用开销仅为两次字节读。
3.2 传递效率
传递 span 只需要两个指针,几乎等同于传递裸指针,避免了传递容器对象时的拷贝或移动。
3.3 与算法的协同
标准算法已接受 std::span,可以直接在 span 上使用 std::for_each, std::sort 等,无需转换为迭代器。
4. 使用示例
#include <span>
#include <vector>
#include <algorithm>
#include <iostream>
void print_span(std::span<const int> sp) {
for (auto v : sp) std::cout << v << ' ';
std::cout << '\n';
}
int main() {
std::vector <int> vec{1, 2, 3, 4, 5};
// 通过 vector 直接创建 span
std::span <int> sp(vec);
// 访问子区间
std::span <int> sub = sp.subspan(1, 3); // [2,3,4]
print_span(sub); // 输出 2 3 4
// 用算法排序
std::ranges::sort(sub); // 对 subspan 进行排序
print_span(sub); // 输出 2 3 4(已排序)
}
5. 典型使用场景
- 函数参数:当函数需要读取或修改数组而不拥有它时,使用
std::span替代T*+size组合。 - 子序列视图:通过
subspan()快速获取任意长度的子视图。 - 与 C API 交互:可轻松将
std::span转为裸指针和长度,满足 C 接口要求。
6. 可能的陷阱
- 悬空引用:如果
span指向临时对象,使用时会悬空。 - 多线程并发:
span并不提供同步机制,读写并发需自行处理。 - 不可变性:虽然
std::span<const T>只读,但span<T>仍可修改底层数据,需注意意图。
7. 结论
std::span 将可变与不可变视图统一为轻量级对象,兼具安全性与高性能,已成为现代 C++ 开发不可或缺的工具。正确使用 span 可以显著简化接口设计,提升代码可读性与运行效率。