C++笔记:std::span

之前已经介绍了std::string_view,std::span可以说是它的拓展版本

C++笔记:std::string_view_std::stringview-CSDN博客

std::span 是"对一段连续内存的非拥有视图(view)"
零拷贝、零分配,只描述"指针 + 长度"

一句话直觉理解

复制代码
std::span<T>  ≈  T* + size

但比裸指针 安全、语义清晰、可组合

类声明

cpp 复制代码
#include<span>
template<
class T,
std::size_t Extent = std::dynamic_extent//inline constexpr size_t dynamic_extent = static_cast<size_t>(-1);
> class span;

对象构造方式

我们可以从各种东西构造一个span,由于可以传进来的形式过多,不一一介绍

cpp 复制代码
int a[5] = {1,2,3,4,5};
std::vector<int> v = {1,2,3};
std::array<int,3> arr = {1,2,3};

std::span<int> s1(a);     // C array
std::span<int> s2(v);     // vector
std::span<int> s3(arr);   // std::array

span构造的本质限制:只能构造自"连续内存"

你可以传数组,指针,容器类,容器迭代器,但是必须要保证内存连续,所以

cpp 复制代码
std::list<int>
std::set<int>
std::deque<int>
std::map<...>

这一类容器及其迭代器,是不能用来构造span的。

span<T> vs span<T, N>

span<T>:动态大小(最常用)

span<T, N>:静态大小(更严格)

特性 说明
大小 编译期常量
类型 span<int,4>span<int,5>
检查 编译期更安全
开销 一样(size 不存)
cpp 复制代码
int a[4];
std::span<int,4> s(a);     // ✔

std::vector<int> v(4);
std::span<int,4> s(v);     // ❌(vector size 非 constexpr)

像容器一样使用

常见的使用方式就不过多介绍了,接口和之前设计的基本一样

cpp 复制代码
for (int& x : s1) {
    x *= 2;
}

std::cout << s1[0];      // unchecked
std::cout << s1.at(0);   // bounds-checked

子视图

|-------|-------------------|
| first | 获得由序列首 N 个元素组成的子段 |
| last | 获得由序列末 N 个元素组成的子段 |

cpp 复制代码
// 编译期长度
template< std::size_t Count >
constexpr std::span<element_type, Count> first() const;
   
// 运行期长度
constexpr std::span<element_type, std::dynamic_extent> first( std::size_t Count ) const; 

// 编译期长度
template< std::size_t Count >
constexpr std::span<element_type, Count> last() const;
   
// 运行期长度
constexpr std::span<element_type, std::dynamic_extent> last( std::size_t Count ) const; 

注意,要是长度不合法,结果是UB的,并不会做边界检查

subspan

cpp 复制代码
// 编译期 offset + count
template< std::size_t Offset,
std::size_t Count = std::dynamic_extent >
constexpr auto  subspan() const;

// 运行期 offset + count
constexpr std::span<element_type, std::dynamic_extent>
    subspan( std::size_t Offset,
             std::size_t Count = std::dynamic_extent ) const; 
cpp 复制代码
auto mid = s.subspan(2, 4);//运行期版本
auto rest = s.subspan(2);//忽略 count(取到结尾)

//返回类型:std::span<T, 8>
auto payload = s.subspan<4, 8>();//编译期版本

转换 span 为对其底层字节的视图

std::as_bytes, std::as_writable_bytes这两把"任意类型的 span"视为"字节序列 span"

  • std::as_bytes

    👉 只读视图const std::byte

  • std::as_writable_bytes

    👉 可写视图std::byte

⚠️ 不拷贝数据,只是 reinterpret 视图

cpp 复制代码
#include <span>

// 只读
template <class T, size_t Extent>
span<const std::byte,
     Extent == dynamic_extent
         ? dynamic_extent
         : Extent * sizeof(T)>
as_bytes(span<T, Extent> s) noexcept;

// 可写
template <class T, size_t Extent>
span<std::byte,
     Extent == dynamic_extent
         ? dynamic_extent
         : Extent * sizeof(T)>
as_writable_bytes(span<T, Extent> s) noexcept;

返回 span 的元素类型变了,长度也变了。类型变成了std::byte字节类型,长度变成了字节数

比如你要读写字节流,就可能需要这两个函数

cpp 复制代码
struct Header {
    uint32_t len;
    uint16_t type;
};

Header h{100, 2};

std::span<Header> sh(&h, 1);
auto bytes = std::as_bytes(sh);

// bytes 是 span<const std::byte>
send(fd, bytes.data(), bytes.size());

有什么应用场景?

作为标准库引入的新类,并且原理并不复杂,肯定是有相当的场景需要用span

场景 1️⃣:公共 API 的"输入参数"

cpp 复制代码
void process(const float* data, size_t n);//以前
void process(std::span<const float> data);//有了 span(标准推荐写法)

好处(不是语法糖)

  • ✔️ 数据 + 长度绑定

  • ✔️ 明确连续内存

  • ✔️ 明确只读

  • ✔️ 可接:

    • vector

    • array

    • C 数组

    • 子 span

场景 2️⃣:协议 / 二进制解析

cpp 复制代码
void parse(std::span<const std::byte> buf) {
    auto header = buf.first<8>();
    auto body   = buf.subspan(8);
}

这样解析非常快速

没有 span 就只能写:

cpp 复制代码
const uint8_t* p = buf + 8;
size_t len = total - 8;

场景 3️⃣:零拷贝切片(first / last / subspan)

span 的切片是 视图语义

cpp 复制代码
auto body = buf.subspan(16, payload_len);
  • ❌ 不分配

  • ❌ 不拷贝

  • ✔️ 明确边界

场景 4️⃣:替代"const vector&"作为参数

错误但常见的接口设计

cpp 复制代码
void foo(const std::vector<int>& v);

问题:

  • ❌ 只能传 vector

  • ❌ 不能传 array / span / 子区间

  • ❌ 不表达"是否需要 owning"

正确的现代接口

cpp 复制代码
void foo(std::span<const int> v);

👉 容器解耦

场景 5️⃣:字节级操作(as_bytes)

span + bytes = 标准化 raw buffer

cpp 复制代码
hash(std::as_bytes(span(data)));

这块我倒是有点体会,以前必须要把data通过reinterpret_cast成uint_8或者char这种字节数组。现在可以直接通过标准库转化成字节。

什么时候"不该"用 span?

❌ 不要用 span 表示"拥有数据"

❌ 不要存 span 作为长期成员

❌ 不要跨线程长期保存

相关推荐
王老师青少年编程2 小时前
2024年6月GESP真题及题解(C++七级): 黑白翻转
c++·题解·真题·gesp·csp·七级·黑白翻转
小尧嵌入式2 小时前
【Linux开发二】数字反转|除数累加|差分数组|vector插入和访问|小数四舍五入及向上取整|矩阵逆置|基础文件IO|深入文件IO
linux·服务器·开发语言·c++·线性代数·算法·矩阵
代码游侠2 小时前
ARM开放——阶段问题综述(一)
arm开发·笔记·嵌入式硬件·学习·架构
试试勇气2 小时前
Linux学习笔记(十二)--用户缓冲区
linux·笔记·学习
你的秋裤穿反了2 小时前
笔记13--------报警记录
笔记
一只小bit2 小时前
Qt 网络:包含Udp、Tcp、Http三种协议的客户端实践手册
前端·c++·qt·页面
jojo_zjx2 小时前
GESP 23年6月2级 找素数
c++
wdfk_prog2 小时前
[Linux]学习笔记系列 -- [drivers][base]faux
linux·笔记·学习
kimicsdn2 小时前
opentelemetry-demo currency cpp 项目编译流程分享
c++·cmake·libprotobuf-dev