正文:
在 C++20 中,std::span 提供了一个轻量级、无所有权的视图,用来表示一段连续内存。它可以用来替代传统的裸指针和长度对,但使用时必须谨慎,尤其是与容器的生命周期相关。以下是关于安全使用 std::span 的关键点和实战建议。
1. std::span 的基本定义
#include <span>
#include <vector>
#include <array>
std::span <int> make_span(std::vector<int>& v) {
return std::span <int>(v.data(), v.size());
}
std::span 本身不持有数据,它只包含:
- 一个指向起始元素的指针 (
T*) - 一个表示长度的
size_t
因此,std::span 并不管理对象的生命周期。
2. 生命周期与所有权
当你从一个容器返回 std::span 时,需要确保返回的 std::span 只在容器有效时使用。常见错误示例:
std::span <int> bad_span() {
std::vector <int> local_vec{1, 2, 3};
return std::span <int>(local_vec); // UB: local_vec destroyed at end of function
}
在此,std::span 指向已被销毁的内存,导致未定义行为。
正确做法:仅返回对外部已存在容器的 std::span,或将 std::span 用作函数参数(传递引用而非所有权)。
3. 作为函数参数的安全模式
void process(std::span<const int> data) {
// 只读访问
for (auto v : data) {
std::cout << v << ' ';
}
std::cout << '\n';
}
- 传递
const:如果不需要修改,使用const可以防止意外写入。 - **传递 `std::span `**:若需要修改,确保调用者传入的容器在函数内部保持生命周期。
使用时,推荐把容器放在外部:
std::vector <int> numbers{10, 20, 30};
process(numbers); // 隐式转换为 std::span<const int>
4. 与 std::array、C-风格数组配合
std::array<int, 5> arr{ {1, 2, 3, 4, 5} };
process(arr); // 同样支持
int c_arr[4] = { 4, 5, 6, 7 };
process(std::span <int>(c_arr, 4)); // 需要显式指定长度
由于 std::array 的大小在编译时已知,std::span 的使用更安全。C-风格数组必须手动传递长度,错误的长度会导致越界。
5. 与 std::vector 的扩展使用
- 子视图:使用
std::span::subspan
std::vector <int> vec{1,2,3,4,5,6,7,8,9,10};
std::span <int> full(vec);
std::span <int> mid = full.subspan(3, 4); // [4,5,6,7]
- 连续性检查:在容器插入/删除时,原
std::span可能失效。若需要保持引用,请使用std::vector::reserve或std::list(不支持std::span)。
6. 防止悬挂 std::span
- 不可在
std::span生命周期内修改容器:如push_back、clear、resize等会重新分配内存,导致std::span指针失效。 - 使用
std::span::data()的const版本:如果你不需要写入,使用const可以防止误操作。
7. 实战示例:安全地批量更新
假设你有一个数值矩阵,需要按行批量更新:
void batch_update(std::vector<std::vector<int>>& matrix,
const std::vector<std::size_t>& rows,
const std::vector <int>& new_values)
{
// 计算总长度
std::size_t total = 0;
for (auto r : rows) total += matrix[r].size();
if (total != new_values.size())
throw std::invalid_argument("size mismatch");
// 创建一个连续视图
std::span <int> values(new_values.data(), new_values.size());
std::size_t idx = 0;
for (auto r : rows) {
auto row_span = std::span <int>(matrix[r].data(), matrix[r].size());
std::copy(values.subspan(idx, row_span.size()).begin(),
values.subspan(idx, row_span.size()).end(),
row_span.begin());
idx += row_span.size();
}
}
matrix必须保持不变(不执行reserve、clear等)才能安全使用std::span。- 通过
subspan实现对每行的局部更新,避免拷贝整行。
8. 结论
std::span是一个强大的工具,但它不管理生命周期,使用时必须确保被视图的底层数据在整个使用期间保持有效。- 在设计接口时,优先将
std::span作为参数(而非返回值),并使用const或mutable版本根据需求控制访问。 - 对于会导致容器重新分配的操作,需在使用
std::span前后避免或重新获取视图。
遵循上述规则,可以在 C++20 及以后版本中安全、高效地使用 std::span,充分发挥其轻量视图的优势。