在 C++20 中,std::span 作为一个轻量级的数组视图,为我们提供了一种更安全、更高效的方式来传递连续内存块。它既能兼容传统的 C 风格数组,也能与 STL 容器无缝配合。以下内容将从概念、实现细节、典型用法以及注意事项四个方面,帮助你快速掌握 std::span 的使用。
1. 什么是 std::span?
std::span 是一个模板类,定义在 <span> 头文件中。它不是一个拥有数据所有权的容器,而是一个“视图”,指向一块连续内存区域,并保持对该区域长度的追踪。基本形式:
template<class ElementType, std::size_t Extent = std::dynamic_extent>
class span;
ElementType:指向元素的类型,支持常量化(const)。Extent:元素数量,若为std::dynamic_extent(默认值)则长度由对象构造时确定;若为编译期常量,则长度固定。
2. 典型构造方式
int arr[5] = {1,2,3,4,5};
std::span <int> s1(arr); // 通过原生数组构造
std::span <int> s2(std::begin(arr), std::end(arr)); // 通过迭代器构造
std::span <int> s3(arr, 3); // 指定长度,截取前 3 个元素
std::vector <int> v = {10,20,30,40};
std::span <int> sv(v); // 直接从 vector 构造
std::span<const int> c_sv = v; // const 视图
注意:
std::span只引用传入的数据,若原数据生命周期结束,span将成为悬空指针。使用时务必保证数据有效。
3. 关键成员函数
| 函数 | 说明 |
|---|---|
size() / size_bytes() |
返回元素数量或字节大小 |
empty() |
检查视图是否为空 |
data() |
返回指向首元素的指针 |
operator[] |
访问指定索引元素 |
begin() / end() |
返回指向首尾元素的迭代器 |
subspan(pos, count) |
生成子视图(从 pos 开始,共 count 个元素) |
first(count) / last(count) |
前/后 count 个元素的子视图 |
4. 与 STL 容器的协同
std::span 与 STL 容器配合极为方便,尤其是在编写接受任意连续容器的通用函数时。示例:
template<class Sp>
int sum(const Sp& s) {
int total = 0;
for (auto x : s) total += x;
return total;
}
int main() {
std::vector <int> v = {1,2,3,4};
int arr[] = {5,6,7};
std::array<int,4> a = {8,9,10,11};
std::cout << sum(v) << '\n'; // 10
std::cout << sum(arr) << '\n'; // 18
std::cout << sum(a) << '\n'; // 38
}
此函数无需关心具体容器类型,只要传入的对象能提供 begin()、end() 并返回 span 或兼容的迭代器即可。
5. 性能优势
- 无所有权:不需要拷贝或移动数据,避免了额外的内存分配。
- 大小信息:与裸指针相比,
span内置长度信息,便于进行边界检查。 - 可组合:
subspan等成员可实现零拷贝切片,极大提升代码可读性与可维护性。
6. 常见陷阱
-
悬空引用
std::span <int> make_span() { int local[5] = {0}; return local; // ❌ 这里返回的 span 指向局部变量 } -
混用
std::array与std::span的长度
std::span<int, N>与std::array<int, N>直接兼容,但若使用不同长度的span,切片时需手动确保范围合法。 -
不可变视图
如果你只需要只读访问,建议声明为std::span<const T>,既能提供只读接口,又能防止意外修改。
7. 实战案例:高效的区间求和
int range_sum(std::span <int> s, std::size_t lo, std::size_t hi) {
if (lo > hi || hi > s.size()) throw std::out_of_range("Invalid range");
int sum = 0;
for (std::size_t i = lo; i < hi; ++i) sum += s[i];
return sum;
}
调用者可以传入任何连续容器,无需额外包装:
std::vector <int> v = {0,1,2,3,4,5,6};
std::cout << range_sum(v, 2, 5); // 输出 9 (2+3+4)
8. 结语
std::span 为 C++20 带来了更安全、更直观的数组视图能力。通过它,你可以轻松编写接受任意连续内存块的通用函数,避免繁琐的指针参数与长度传递,同时保留边界检查与可读性。只需记住其引用性质与生命周期约束,便能在项目中大放异彩。祝你编码愉快!