在 C++20 标准中,std::span 成为一个轻量级的非拥有视图(view),它允许我们在不复制数据的情况下对数组、vector、字符串等连续存储的容器进行访问。下面从基本用法、性能优势、常见错误以及实战示例四个方面,系统梳理 std::span 的使用技巧。
1. 基础用法
#include <span>
#include <vector>
#include <iostream>
int main() {
std::vector <int> vec{1, 2, 3, 4, 5};
// 直接从 vector 创建 span
std::span <int> s1{vec};
// 也可以从原始数组创建
int arr[] = {10, 20, 30};
std::span <int> s2{arr};
// 访问元素
std::cout << "s1: ";
for (auto x : s1) std::cout << x << ' ';
std::cout << "\ns2: ";
for (auto x : s2) std::cout << x << ' ';
}
注意:std::span 仅是一个视图,它不拥有所指向的数据,生命周期须与底层数据保持一致。
2. 子视图(Subspan)与裁剪
使用 subspan 可以得到更小的视图,支持起始偏移和长度。C++20 之前的 subspan 只能接受偏移;C++23 开始支持长度。
std::span <int> full = {vec}; // {1,2,3,4,5}
std::span <int> part = full.subspan(2); // {3,4,5}
auto middle = full.subspan(1, 3); // {2,3,4}
技巧:在算法中使用
subspan可以避免手动计算下标,提升代码可读性。
3. 与 C++ 标准库算法的配合
std::span 能够直接作为标准算法的参数,因为它满足 ForwardRange 要求。示例:
#include <algorithm>
#include <span>
#include <vector>
#include <iostream>
int main() {
std::vector <int> v{5, 2, 4, 1, 3};
std::span <int> span_v{v};
std::ranges::sort(span_v); // std::sort(span_v.begin(), span_v.end());
for (auto x : span_v) std::cout << x << ' '; // 输出排序后的结果
}
提示:如果你想保留原始容器而对其进行排序,直接把
span传给算法即可,不需要复制。
4. 互操作性:与 std::array、C-style array、std::string
-
std::array:
std::array<double, 4> arr{1.1, 2.2, 3.3, 4.4}; std::span <double> sp{arr}; -
C-style array:已演示
subspan示例。 -
std::string:字符串本身可视作
` 可以直接操作字符内容。 “`cpp std::string str = “Hello, world!”; std::span s = str; std::ranges::replace(s, ‘l’, ‘x’); // 替换所有 ‘l’ 为 ‘x’ “`char的连续存储,`std::span
警告:对
` 视图时,若使用 `const std::string`,只能得到 `std::span`。std::string进行 `span
5. 常见错误与调试技巧
| 错误类型 | 典型表现 | 解决方案 |
|---|---|---|
| 悬空引用 | 对已析构对象创建 span |
确保 span 的生命周期不超过底层数据 |
| 写入超界 | 通过 subspan 指定长度大于原视图长度 |
使用 subspan 的返回值检查 span::size() |
| 类型不匹配 | 将 `span | |
传递给期望span的函数 | 明确const或使用std::as_const(span)` |
||
| 隐式复制 | 将 span 作为函数返回值 |
只能返回 std::span<const T>,或者返回引用的容器 |
调试技巧:在调试器中使用 span::data() 与 span::size() 检查指针和值是否合理。
6. 进阶:自定义 span 边界检查
如果想在运行时对 span 进行更严格的边界检查(如在 debug 版本中),可以写一个包装类:
template <typename T>
class CheckedSpan {
std::span <T> sp_;
public:
CheckedSpan(std::span <T> sp) : sp_(sp) {}
T& operator[](size_t i) {
assert(i < sp_.size());
return sp_[i];
}
// 其它转发方法……
};
7. 小结
- std::span 是一种非拥有、轻量级的视图,适合对连续存储的数据做无复制访问。
- 利用
subspan可以方便地裁剪视图,配合std::ranges算法,代码既简洁又高效。 - 关注生命周期与边界安全,避免悬空引用和越界写入。
- 对于字符串、vector、array 等常见容器,
span都提供了完美的互操作性。
掌握这些技巧后,你可以在 C++20 项目中无缝使用 std::span,既提升代码可读性,又保证运行时性能。