std::span
?std::span
是C++20引入的轻量级非拥有式容器,用于表示连续内存区域的视图。它不管理内存所有权,仅通过指针和大小描述一段数据,类似于“智能指针+长度”的组合。其核心设计目标是:
vector
、array
等连续容器。#include <span>
#include <vector>
int main() {
int arr[] = {1, 2, 3};
std::vector<int> vec = {4, 5, 6};
std::span<int> s1(arr); // 从数组构造
std::span<int> s2(vec); // 从vector构造
return 0;
}
span
(默认):大小在运行时确定(std::dynamic_extent
)std::span<int> dynamic_span(arr, 3); // 显式指定大小std::span<int, 3> static_span(arr); // 必须匹配数组大小span
:编译时固定大小(优化性能)传统方法需要传递指针和大小:
void process(int* data, size_t size); // 易出错
改用std::span
后:
void process(std::span<const int> data) { // 安全且通用
for (int v : data) { /* ... */ }
}
可接受任何连续容器:
int arr[5];
std::vector<int> vec(10);
process(arr); // 原生数组
process(vec); // vector
通过subspan()
创建局部视图:
std::span<int> s(arr, 5);
auto sub = s.subspan(1, 3); // 索引1开始,长度3的子视图
std::span
的底层实现仅包含两个核心成员(以 GCC 实现为例):
template <typename T, size_t Extent = dynamic_extent>
class span {
T* _ptr; // 指向连续内存首地址的指针
size_t _size; // 当前视图包含的元素数量
};
span
实例仅占用 16 字节内存(8 字节指针 + 8 字节大小)std::span
要求底层数据必须满足连续内存布局,其设计基于以下内存模型假设:
内存地址 | 0x1000 | 0x1004 | 0x1008 | 0x100C | ...
元素 | T[0] | T[1] | T[2] | T[3] | ...
std::vector
、std::array
、内存映射文件等连续存储结构span[i]
的访问通过 _ptr + i * sizeof(T)
实现,时间复杂度 O(1)int arr[5] = {1,2,3,4,5};
std::span<int, 5> static_span(arr); // Extent=5
_size
成员(通过模板参数 Extent
推导)std::vector<int> vec(10);
std::span<int> dynamic_span(vec); // Extent=dynamic_extent
if (index >= _size)
检查(可通过 -DNDEBUG
禁用)std::span
不管理内存生命周期,其有效性完全依赖底层数据:
// 危险示例:悬垂指针
auto create_span() {
std::vector<int> local_data{1,2,3};
return std::span(local_data); // 返回时 local_data 已销毁
}
模式 | 示例 | 安全等级 |
---|---|---|
栈内存视图 |
| ★★★★☆ |
堆内存视图 |
| ★★★☆☆ |
成员函数返回视图 |
| ★★☆☆☆ |
防御性编程建议:
span
的传递范围不超过底层数据生命周期vector::push_back
)后重新获取 span
std::span<const T>
避免意外修改span
的迭代操作可能被完全展开// 优化前
for (auto& x : span) sum += x;
// 优化后(示例 x86 AVX2 指令)
vmovdqu ymm0, [rdi]
vpaddd ymm1, ymm1, ymm0
操作 | 传统方式 |
| 性能提升 |
---|---|---|---|
子序列创建 |
|
| 300x |
数据传递 | 深拷贝 | 指针传递 | 1000x |
跨API交互 | 序列化/反序列化 | 直接内存映射 | 200x |
auto ptr = std::make_unique<int[]>(1e6);
std::span<int> big_data(ptr.get(), 1e6);
std::ranges::sort(big_data); // 无需拷贝直接排序
std::vector<int> vec(s.begin(), s.end()); // span转vector
int matrix[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
std::span<std::span<int>> rows(matrix, 3); // 二维视图
std::span::iterator
本质是 T* 的包装类型:
using iterator = T*;
begin()
返回 _ptr
end()
返回 _ptr + _size
std::span<int> s(arr, 100);
// 直接使用 STL 算法
auto it = std::find(s.begin(), s.end(), 42);
std::sort(s.subspan(10, 50));
性能对比(处理 1M 元素):
算法 | 原生指针 |
|
|
---|---|---|---|
| 15ms | 15ms | 18ms |
| 2ms | 2ms | 3ms |
#include <span>
#include <iostream>
template <typename T>
void inspect_memory(std::span<T> s) {
std::cout << "Start: " << s.data()
<< " End: " << s.data() + s.size()
<< " Stride: " << sizeof(T) << " bytes\n";
}
int main() {
int arr[5] = {1,2,3,4,5};
inspect_memory(std::span(arr));
}
输出示例:
Start: 0x7ffd4a3c4f00 End: 0x7ffd4a3c4f14 Stride: 4 bytes
错误示例:引用已释放内存
std::span<int> create_span() {
int arr[] = {1, 2, 3};
return {arr, 3}; // 返回栈内存地址!
}
解决方案:
span
的使用std::vector<int> vec = {1, 2};
std::span<int> s(vec);
vec.push_back(3); // 可能触发重分配,s失效!
std::span<const int> read_only(s); // 禁止修改
特性 | 原生指针 |
|
|
---|---|---|---|
内存所有权 | ❌ | ✔️ | ❌ |
边界检查 | ❌ | ✔️ | ✔️ |
零拷贝传递 | ✔️ | ❌ | ✔️ |
跨容器兼容性 | ❌ | ❌ | ✔️ |
推荐场景:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。