std::span是C++20引入的非拥有式连续内存视图,不能当普通指针用,因其不管理生命周期,底层容器销毁后访问即UB;构造需确保源对象生命周期覆盖span使用期,subspan无运行时检查,传参需注意const与模板推导。

std::span 是什么,为什么不能当普通指针用
std::span 是 C++20 引入的轻量视图类型,它不拥有数据,只持有指向连续内存的指针和长度。它不是智能指针,也不是容器,更不是 std::vector 的替代品——它没有分配、释放、扩容能力。常见误用是把它当作“安全版裸指针”传给旧接口后随意修改底层数据,结果引发未定义行为(UB),尤其在原容器生命周期结束之后仍访问 std::span。
关键约束:只要底层容器(如 std::vector、栈数组)还活着,且没被移动或销毁,std::span 就安全;一旦容器析构,std::span 立即变成悬空视图。
如何从 std::vector 或原生数组构造合法 span
构造本身很简单,但合法性取决于源对象的生命周期是否覆盖 span 的使用期。以下是最常用且安全的场景:
- 从局部
std::vector构造std::span,并在同一作用域内使用(推荐) - 从函数参数传入的
const std::vector&或T*+size_t构造(需确保调用方保证生命周期) - 从栈数组(如
int arr[10])构造,不跨函数返回
反例:不要返回局部 std::vector 的 std::span,也不要保存由临时 std::vector{...} 构造的 std::span。
立即学习“C++免费学习笔记(深入)”;
std::vectordata = {1, 2, 3, 4, 5}; std::span view = data; // OK:view 引用 data 的堆内存 std::span cview = data; // OK:隐式转换为 const 版本 int arr[] = {10, 20, 30}; std::span stack_view{arr}; // OK:编译期知道大小,更安全
切分子数组:subspan() 的边界检查与陷阱
subspan() 是获取子视图的核心方法,但它不做运行时越界检查(C++20 标准明确要求它是 noexcept 且零开销)。如果起始位置或长度超出当前 span 范围,行为是未定义的——不会抛异常,也不会断言,只会静默出错。
安全做法:手动校验参数,或封装一层带检查的辅助函数。尤其注意:subspan(pos) 和 subspan(pos, count) 中的 pos 是索引,不是指针偏移;count 为 0 是合法的(返回空 span)。
-
view.subspan(2, 3):从索引 2 开始取 3 个元素 →{3,4,5}(若原 view 长度 ≥5) -
view.subspan(10):若view.size() ,UB —— 不要依赖调试器或 ASan 自动捕获 -
view.subspan(view.size()):合法,返回空 span(data()==nullptr但size()==0)
std::vectorbuf(100); std::span full{buf}; if (offset < full.size() && len <= full.size() - offset) { std::span sub = full.subspan(offset, len); // 此时才安全 }
传递 span 给函数时要注意 const 与模板推导
函数参数用 std::span 还是 std::span,直接影响能否修改数据,也影响模板实参推导。常见错误是写成 void f(std::span 却传入 const std::vector,导致编译失败(因为 const vector 的 data() 返回 const int*,无法隐式转为 int*)。
更健壮的写法是使用模板参数自动推导,或统一用 const 视图接收只读需求:
- 只读处理:优先用
std::span参数 - 需要修改:确保传入的是非 const 容器引用,或明确要求调用方提供可写视图
- 泛型函数:用
template,让编译器根据实参决定void f(std::span s) T
void process_readonly(std::spans) { for (double x : s) { /* ... */ } } void process_mutable(std::span s) { for (float& x : s) x *= 2.f; } // 调用: std::vector d = {1.1, 2.2}; process_readonly(d); // OK std::vector f = {1.f, 2.f}; process_mutable(f); // OK // process_mutable(d); // 编译错误:double* 无法转 float*
最易被忽略的一点:std::span 的 lifetime 完全独立于其构造来源,它不参与 RAII,也不触发任何检查。你得自己画清楚变量作用域和所有权链——这恰恰是它高效的前提,也是它危险的根源。











