在C++20中,std::span被引入为轻量级、无所有权的视图,用来表示一段连续存储的元素。相比传统的指针+长度或数组指针,std::span提供了更安全、易用的接口,并可与标准库算法无缝结合。下面从概念、使用场景、性能以及代码示例几个角度,详细介绍如何使用std::span实现安全的数组遍历。
1. std::span 简介
template<class ElementType, std::size_t Extent = std::dynamic_extent>
class span;
- ElementType:元素类型。
- Extent:大小,可为动态(默认)或静态。
std::span内部只保存指向首元素的指针和长度(若动态),不持有内存,适合作为函数参数、返回值或临时视图。
2. 为什么要用 std::span?
| 传统方法 | 缺点 | std::span | 优点 |
|---|---|---|---|
int* arr, std::size_t n |
参数错误可能导致越界;无法直接与算法配合;需要手动检查长度。 | `std::span | |
| ` | 自动保存长度;可与算法直接使用;防止越界。 | ||
| `std::vector | |||
& v| 需要复制或移动;对只读数据不够灵活。 |std::span` |
只读视图;无需拷贝。 | ||
T* begin, T* end |
参数对不一致时容易出错;需要自行计算长度。 | `std::span | |
| ` | 自动计算;支持范围 for。 |
3. 安全遍历的实现
3.1 传参方式
void printAll(std::span<const int> data) {
for (int x : data)
std::cout << x << ' ';
}
- 只读视图,任何尝试修改都会在编译期报错。
printAll({arr, 10});或printAll(vector)皆可。
3.2 边界检查
std::span 在标准库算法中会被视为容器,使用迭代器时会自动进行边界检查(如 std::for_each、std::sort)。如果你手动使用索引,仍需自行检查,或者使用 std::array 或 std::vector 等安全容器。
3.3 子视图
auto sub = data.subspan(2, 5); // 从第3个开始,取5个元素
如果超出范围,调用会触发 std::out_of_range。
4. 与标准算法结合
#include <algorithm>
#include <numeric>
#include <iostream>
#include <span>
int main() {
int arr[] = {1,2,3,4,5,6,7,8,9,10};
std::span <int> s(arr); // 自动推导大小
std::sort(s.begin(), s.end(), std::greater<>()); // 就地排序
int sum = std::accumulate(s.begin(), s.end(), 0);
std::cout << "Sum: " << sum << '\n';
}
std::sort直接接受span的迭代器,内部不会做越界检查,但传入的范围已确定。- 对于不可变数据,使用
std::span<const int>,可安全传递给只读算法。
5. 性能对比
| 方法 | 复制成本 | 运行时检查 | 典型使用 |
|---|---|---|---|
int* + size |
0 | 需要手动检查 | 旧代码、性能极限 |
| `std::vector | |||
| ` | 需要复制/移动 | 自动 | 大多数业务 |
| `std::span | |||
| ` | 0 | 只在创建子视图时检查 | 函数接口、临时视图 |
std::span 与原始指针相比仅多了一份长度信息,几乎不增加运行时成本;其优势在于类型安全和易用性。
6. 常见误区
- 误认为
std::span会拷贝数据- 它只是视图,未拥有内存。
- 把
std::span用作持久化成员- 若引用的底层容器被销毁,
span将悬空。
- 若引用的底层容器被销毁,
- 使用
subspan时不检查越界subspan会在超出范围时抛异常,需捕获或避免。
7. 进阶应用
- 与C风格接口交互
void c_func(const int* data, std::size_t n); c_func(span <int>{arr}.data(), span<int>{arr}.size()); - 将
span作为返回值
返回子视图而不是拷贝整个容器。
8. 小结
std::span 通过提供一种轻量、安全的视图,极大提升了 C++20 代码的可读性与可维护性。它在函数参数、临时遍历、子视图以及与标准算法的结合上都表现出色。掌握 span 的正确使用方式,是现代 C++ 编程的重要技能。
实践建议:
- 在需要只读访问时使用
std::span<const T>。 - 在接口设计时优先使用
std::span替代指针+长度。 - 对动态数组,尽量使用
std::vector或std::array;对临时遍历,使用std::span。
通过上述方法,你可以在不牺牲性能的前提下,写出更安全、更易维护的 C++20 代码。