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 作为长期成员

❌ 不要跨线程长期保存

相关推荐
方安乐1 分钟前
科普:股票 vs 债券的区别
笔记
_F_y3 分钟前
C++重点知识总结
java·jvm·c++
初願致夕霞1 小时前
Linux_进程
linux·c++
Thera7772 小时前
【Linux C++】彻底解决僵尸进程:waitpid(WNOHANG) 与 SA_NOCLDWAIT
linux·服务器·c++
Wei&Yan2 小时前
数据结构——顺序表(静/动态代码实现)
数据结构·c++·算法·visual studio code
傻小胖2 小时前
22.ETH-智能合约-北大肖臻老师客堂笔记
笔记·区块链·智能合约
wregjru2 小时前
【QT】4.QWidget控件(2)
c++
浅念-2 小时前
C++入门(2)
开发语言·c++·经验分享·笔记·学习
小羊不会打字2 小时前
CANN 生态中的跨框架兼容桥梁:`onnx-adapter` 项目实现无缝模型迁移
c++·深度学习
Max_uuc2 小时前
【C++ 硬核】打破嵌入式 STL 禁忌:利用 std::pmr 在“栈”上运行 std::vector
开发语言·jvm·c++