C++20 中的 std::span 与数组视图的使用

在 C++20 之前,处理数组、向量以及其它容器的子序列通常需要自己实现切片逻辑,或者使用标准库提供的 std::arraystd::vector 等,并通过 begin()/end() 等方法手动获取迭代器。
C++20 引入的 std::span(在 <span> 头文件中定义)为这类工作提供了一种轻量级、无所有权的视图(view)。std::span 不是容器,它只保存一个指向元素的指针和一个长度,不能修改其所指向的数据的大小,只能访问。

1. std::span 的基本定义

template<class T, std::size_t Extent = std::dynamic_extent>
class span;
  • T 是元素类型。
  • Extent 是元素数量,若未知可用 std::dynamic_extent

2. 如何构造 std::span

int arr[5] = {1, 2, 3, 4, 5};
std::span <int> sp1(arr);                    // 整个数组
std::span <int> sp2(arr, 3);                 // 前 3 个元素
std::span <int> sp3(arr + 1, 3);             // 从 arr[1] 开始的 3 个元素

`std::vector

vec = {10, 20, 30, 40};` `std::span sp4(vec);` // 直接使用容器,span 会调用 `data()` 与 `size()`。 ### 3. 常用成员函数 | 成员 | 说明 | |——|——| | `size()` | 返回元素数量 | | `data()` | 返回指向首元素的指针 | | `empty()` | 判断是否为空 | | `operator[]` | 访问指定下标 | | `front()`, `back()` | 访问首尾元素 | | `subspan(n)` | 从第 n 个元素开始的子 span | | `last(n)` | 末尾 n 个元素的子 span | | `first(n)` | 开始 n 个元素的子 span | ### 4. 与容器迭代器的兼容 `std::span` 的 `begin()` 与 `end()` 返回原始指针,可直接用于范围-based for 循环。 “`cpp for (int x : sp1) std::cout << x << ' '; “` ### 5. `std::span` 与内存安全 因为 `span` 不拥有底层数据,它的生命周期必须不超过所引用的原始容器。 “`cpp std::span sp = std::span(new int[10], 10); // 错误:sp 只是一视图,未负责释放内存 “` 建议只在栈上或已有容器的数据上创建 `span`,不用于动态分配。 ### 6. 典型使用场景 * **函数参数**: “`cpp void process(std::span data) { // 对 data 做处理,读写都可以 } “` 这样函数既可以接受数组、向量,也能接受子序列。 * **子序列访问**: “`cpp void print_first_half(std::span arr) { auto half = arr.first(arr.size() / 2); for (int v : half) std::cout << v << ' '; } “` * **非所有权的视图**: 在多线程场景下,线程可以只读取数据的子区间,`span` 只传递引用,避免拷贝。 ### 7. 与 std::array 的对比 `std::array` 是固定大小容器,拥有元素。 `std::span` 是无大小限制的视图,可对 `std::array`、`std::vector` 或裸数组创建。 两者可互换使用,但 `span` 更轻量,适合只需要访问而不修改大小的情况。 ### 8. 小技巧 * **自动推断**: “`cpp auto sp = std::span(arr); // C++20 推断 Extent 为 sizeof(arr)/sizeof(arr[0]) “` * **使用 `std::as_writable_bytes`**: 通过 `std::span` 将任意对象序列化为字节流。 ### 9. 示例代码:排序子范围 下面演示如何用 `std::span` 对向量的一部分进行排序,而不影响其它部分。 “`cpp #include #include #include #include void sort_subrange(std::vector & v, std::size_t lo, std::size_t hi) { std::span sub(v.data() + lo, hi – lo); // 只看 [lo, hi) std::sort(sub.begin(), sub.end()); } int main() { std::vector numbers = {9, 1, 4, 7, 3, 8, 2, 5, 6}; std::cout << "Before: "; for (auto n : numbers) std::cout << n << ' '; std::cout << '\n'; sort_subrange(numbers, 2, 6); // 对索引 2~5 进行排序 std::cout << "After: "; for (auto n : numbers) std::cout << n << ' '; std::cout << '\n'; } “` 输出: “` Before: 9 1 4 7 3 8 2 5 6 After: 9 1 3 4 7 8 2 5 6 “` 只排序了 4,7,3,8 这四个元素,其他元素保持不变。 ### 10. 结语 `std::span` 的出现使得 C++ 程序员可以更安全、更简洁地处理数组切片。它是对函数接口友好的解决方案,也是现代 C++ 开发中不可或缺的一部分。掌握 `span` 的使用,可以让你的代码更加灵活、易维护。

发表评论