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 小时前
原创题目 - 空壳恋爱
c++·算法·dp·oi·信息学·原创题目·值得挑战的题目
间歇性努力持续性发呆的野生快乐选手3 小时前
stl容器使用和算法简介
数据结构·c++
kdxiaojie3 小时前
Linux USB驱动阅读笔记(1)
linux·笔记·学习·usb
半生过往3 小时前
前端学 Java 课程笔记
java·前端·笔记
九硕智慧建筑一体化厂家3 小时前
楼宇自控|智慧建筑核心引擎!楼宇自控系统,赋能建筑高效低碳运维
运维·人工智能·笔记·智慧城市
沙盘客4 小时前
AFSIM 官方案例库全景与解读方法论
c++·经验分享·后端
GGMM7894 小时前
施耐德 VX5A1HC90110(VX5A1HD9011)ATV61/ATV71 90‑110KW 电源驱动板详解
笔记·变频器·变频器维修
_wxd6665 小时前
STL-stack与queue与priority_queue(容器适配器)
c++
Wang's Blog5 小时前
PostgreSQL笔记48: 全文检索与向量检索的对比与混合检索实践
笔记·postgresql·全文检索
zh路西法5 小时前
【玩转VLA具身智能机械臂】(二):MoveIt2 运动规划——架构、配置与 C++ 上手
c++·机械臂·路径规划·vla·planner·moveit2