在 C++20 之前,指针或引用常常被用来手工管理数组切片,但这很容易导致越界、悬空指针以及内存泄漏。C++20 引入的 std::span 为这些问题提供了一种轻量、无所有权的视图。本文将通过代码示例展示 std::span 的使用场景、优势以及最佳实践。
1. 什么是 std::span
std::span 是一个非所有权的数据结构,它包含一个指向连续存储区的指针和长度信息。它本身不管理内存,而是借用已有的数据结构(如数组、std::vector、C 风格数组等)提供一个统一的视图。
2. 基本用法
#include <span>
#include <vector>
#include <iostream>
void printSpan(std::span <int> s) {
for (int v : s) std::cout << v << ' ';
std::cout << '\n';
}
int main() {
int arr[5] = {1,2,3,4,5};
std::vector <int> vec = {10,20,30,40,50};
std::span <int> span1(arr); // 直接从数组构造
std::span <int> span2(vec); // 直接从 vector 构造
printSpan(span1); // 输出 1 2 3 4 5
printSpan(span2); // 输出 10 20 30 40 50
}
3. 切片(subspan)
span 提供 subspan 方法,可以方便地取子视图:
auto mid = span2.subspan(2); // 从索引 2 开始,到末尾
auto part = span2.subspan(1, 3); // 从索引 1 开始,长度 3
printSpan(mid); // 输出 30 40 50
printSpan(part); // 输出 20 30 40
4. 与 C 风格数组和指针的对比
传统做法:
void process(int* data, std::size_t len) { ... }
使用 span 可以直接传递 std::vector 或数组:
void process(std::span <int> data) { ... }
// 调用
process(vec); // 自动转换
process(arr); // 自动转换
5. 通过 std::array、std::vector 和 C 风格数组的隐式转换
std::array:构造span时可直接使用 `std::span s = arr;`。std::vector:`std::span s = vec;` 只在 `vector` 的数据未移动时安全。- C 风格数组:`std::span s = {arr, std::size(arr)};` 或者 `std::span s = arr;`(C++23 之后支持)
6. 安全性与异常
std::span仅存储指针和长度;其生命周期与引用的底层对象相同。若底层对象被销毁,span会悬空。- 在多线程环境下,如果一个线程修改底层数据,另一个线程读取,必须使用同步机制。
- 由于
span本身不抛异常,使用subspan时若参数越界会抛出std::out_of_range,因此请在调用前检查尺寸。
7. 性能
std::span只包含两字段(指针 + 长度),占用空间与指针相当,传递开销极小。- 由于没有所有权,编译器可进行更多优化,如把
span直接作为内联参数。
8. 代码示例:实现一个通用求和函数
template <typename T>
T sum(std::span <T> s) {
T total = T{};
for (const auto& v : s) total += v;
return total;
}
int main() {
std::vector <int> vec = {1,2,3,4,5};
int arr[] = {10,20,30};
std::cout << "sum(vec) = " << sum(vec) << '\n'; // 15
std::cout << "sum(arr) = " << sum(arr) << '\n'; // 60
}
9. 常见陷阱
- 悬空 span:在
vector重新分配时,已有span仍指向旧内存。 - 多余复制:
std::span只是一个视图,使用时要确保不把它当作所有权容器。 - 长度检查:在使用
subspan前最好检查size(),避免抛出异常。
10. 结语
std::span 为 C++20 引入的一种轻量级、无所有权的数据视图,使得函数签名更简洁、调用更安全、代码更易读。掌握它的使用,能够让你在处理数组、向量以及其他连续容器时更加高效。希望本文能帮助你在日常项目中更好地利用 std::span。