在 C++20 中,std::span 是一种轻量级、无所有权的容器视图,用来安全地访问数组、std::vector、std::array 等连续数据块。它通过在编译期和运行时检查尺寸和边界,降低了数组越界的风险,并提升了代码的可读性与可维护性。
1. 什么是 std::span?
std::span 本质上是一个指向连续内存块的指针加上长度信息的组合。它不拥有数据,只是对已有数据的一种“视图”。
#include <span>
#include <vector>
#include <iostream>
std::vector <int> v = {1, 2, 3, 4, 5};
std::span <int> s(v); // 创建对 vector 的 span 视图
std::span<const int> cs = s; // 对 const 数据的视图
2. 防止越界的机制
2.1 编译期检查
std::span的构造函数可以接受固定大小的数组(如int[10]),编译器会检查大小是否匹配,防止错误初始化。std::span的operator[]是一个未检查的访问(与裸指针相同),但可以使用at()或subspan()进行边界检查。
2.2 运行时检查
std::span::at(size_type pos)在访问时会检查pos < size(),如果越界则抛出std::out_of_range。std::span::subspan(pos, count)同样会检查pos + count <= size(),保证子段合法。
std::span <int> s(v);
try {
std::cout << s.at(10) << '\n'; // 会抛出异常
} catch (const std::out_of_range& e) {
std::cerr << "越界访问: " << e.what() << '\n';
}
3. 与原始指针的区别
| 原始指针 | std::span | |
|---|---|---|
| 语义清晰 | 仅指针 | 指针 + 长度 |
| 传参便利 | 需要手动传递长度 | 自动携带长度 |
| 越界风险 | 可能出现 | 可以通过 at() / subspan() 防止 |
| 对齐需求 | 任何地址 | 同样不要求对齐 |
4. 实际使用场景
4.1 函数参数
使用 std::span 作为函数参数可以让函数既能接受 std::vector、std::array、裸数组,又能显式表达函数不拥有数据。
void process(std::span <int> data) {
// data 可以安全访问,使用 at() 防止越界
}
4.2 读写视图
在需要对数组做分块处理时,subspan 非常方便。
std::span <int> full(v);
auto firstHalf = full.subspan(0, full.size() / 2);
auto secondHalf = full.subspan(full.size() / 2);
4.3 与 STL 算法配合
STL 算法往往接受迭代器区间,std::span 可以轻松转换为 begin() / end()。
std::sort(full.begin(), full.end()); // 就地排序
5. 代码示例:安全加法
#include <span>
#include <iostream>
#include <vector>
#include <numeric>
#include <stdexcept>
int safe_add(std::span<const int> a, std::span<const int> b) {
if (a.size() != b.size()) {
throw std::invalid_argument("向量大小不匹配");
}
int sum = 0;
for (size_t i = 0; i < a.size(); ++i) {
sum += a[i] + b[i]; // 这里使用 [],因为我们已检查大小
}
return sum;
}
int main() {
std::vector <int> a{1, 2, 3};
std::vector <int> b{4, 5, 6};
std::cout << "总和: " << safe_add(a, b) << '\n';
return 0;
}
6. 小结
std::span通过提供长度信息,天然地提升了对连续数据的安全访问。- 与裸指针相比,它降低了忘记传递长度的风险,并可以在需要时通过
at()等方法做边界检查。 - 在 C++20 之后,使用
std::span已成为函数接口传递数组的推荐做法,既保持了性能,又提升了安全性。
通过掌握 std::span 的使用,你可以在不牺牲性能的前提下,让你的 C++ 代码更安全、更易读。