在 C++20 中引入的 std::span 为我们提供了一种安全、轻量级且高效的方式来表示对数组或容器片段的视图。它并不拥有数据,而是仅仅维护一个指针和长度,类似于 STL 中的指针+长度组合,但提供了更好的类型安全和接口。下面从概念、使用场景、优势与限制四个方面进行阐述,并给出实际代码示例。
1. std::span 的基本概念
- 无所有权:span 不会拷贝或持有数据,只是对已有连续内存区域的“窗口”。
- 指针 + 长度:内部实现通常是
T* ptr 与 size_t len,因此大小仅为 16 字节(在 64 位平台上)。
- 模板化:
std::span<T, Extent>,其中 Extent 可为固定大小或 std::dynamic_extent 表示动态大小。
- 兼容性:可以直接从 C++ 标准容器(如
std::array、std::vector)或裸数组创建,也可以转换为另一种 T 类型的 span(如 const、volatile)。
2. 典型使用场景
| 场景 |
用法 |
说明 |
| 函数参数 |
`void process(span |
data)| 传递任何int` 数组或容器的连续块,避免复制。 |
| 子区切片 |
auto sub = data.subspan(5, 10) |
取原视图的一段,常用于处理子数组。 |
| 循环遍历 |
for (auto& x : data) { ... } |
支持基于范围的 for,避免手动指针运算。 |
| 统一接口 |
接收 `std::span |
与std::string_view` 共同处理 |
可以将字符串视为字符数组,兼容老旧代码。 |
3. 代码示例
#include <iostream>
#include <vector>
#include <array>
#include <span>
#include <numeric>
#include <algorithm>
// 1. 计算数组和
int sum(span <int> data) {
return std::accumulate(data.begin(), data.end(), 0);
}
// 2. 反转视图
void reverse_in_place(span <int> data) {
std::reverse(data.begin(), data.end());
}
// 3. 将子段拷贝到另一个容器
template<typename T>
void copy_subspan(span <T> src, std::vector<T>& dst, size_t offset, size_t len) {
if (offset + len > src.size()) throw std::out_of_range("subspan out of range");
dst.insert(dst.end(), src.subspan(offset, len).begin(), src.subspan(offset, len).end());
}
int main() {
std::array<int, 10> arr{0,1,2,3,4,5,6,7,8,9};
std::vector <int> vec{10,11,12,13,14};
// 直接使用 span
std::span <int> s_arr = arr; // 从数组创建
std::span <int> s_vec = vec; // 从 vector 创建
std::cout << "sum arr: " << sum(s_arr) << '\n'; // 45
std::cout << "sum vec: " << sum(s_vec) << '\n'; // 60
reverse_in_place(s_arr);
std::cout << "reversed arr: ";
for (auto v : s_arr) std::cout << v << ' ';
std::cout << '\n';
// 子段拷贝
std::vector <int> dst;
copy_subspan(s_vec, dst, 1, 3); // 复制 vec[1..3] -> dst
std::cout << "dst after copy: ";
for (auto v : dst) std::cout << v << ' ';
std::cout << '\n';
// 与 const 兼容
std::span<const int> const_s = s_arr;
std::cout << "const span size: " << const_s.size() << '\n';
return 0;
}
关键点说明
- 自动推断:`std::span
s_arr = arr;` 自动推断指针类型。
- 子段:
subspan(offset, len) 必须保证范围合法,否则会抛出异常。
- 范围兼容:
span 与 std::initializer_list、std::string_view 等不直接兼容,需显式转换。
4. 优势与局限
| 优势 |
说明 |
| 轻量 |
仅 16 字节,适合堆栈传递 |
| 安全 |
提供范围检查,可避免越界访问 |
| 兼容性 |
与多种容器无缝交互 |
| 可读性 |
语义明确,代码可维护 |
| 局限 |
说明 |
| 不支持非连续容器 |
只能视为连续内存 |
| 复制成本 |
需要手动拷贝以持久化数据 |
| 老旧编译器 |
仅在 C++20+ 可用,部分 IDE 仍需配置 |
5. 结语
std::span 为 C++20 提供了一个简单、强大且类型安全的“数组视图”工具,能大幅提升代码的可读性与性能。无论是对已有 API 的包装,还是对新算法的实现,掌握 span 的使用都是现代 C++ 开发者的必备技能之一。欢迎你在项目中尝试 span,并观察其对代码质量和运行效率的积极影响。