在 C++20 之前,处理数组或容器的子区段通常需要手动管理指针、长度,或者使用 std::array、std::vector 的迭代器和 std::copy 等操作,这些都容易出现边界错误或多余的拷贝。C++20 引入了 std::span,它是一个轻量级、无所有权的视图,用来描述一段连续内存。结合 std::vector 的动态管理,std::span 可以在不产生拷贝、保持安全性的前提下,实现对子数组的快速访问。以下是使用 std::span 与 std::vector 的完整示例与说明。
1. std::span 简介
template<class T, std::size_t Extent = std::dynamic_extent>
class span;
- 无所有权:
span 只是对已有数据的引用,它不负责内存分配或释放。
- 零拷贝:创建
span 时不进行数据拷贝,直接引用原始内存。
- 安全性:可以通过
empty(), size(), data() 等成员进行安全检查。
2. 示例:从 `std::vector
` 获取子区段
“`cpp
#include
#include
#include
int main() {
std::vector
numbers{ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
// 取出第3到第6个元素(索引2到5)
std::span
subSpan{ numbers.data() + 2, 4 }; // 4个元素
// 打印子区段
for (auto v : subSpan) std::cout 观察到 `subSpan` 的修改直接影响了原始 `vector`,说明 `span` 并未拷贝数据。
—
### 3. 典型使用场景
1. **函数参数**
传统的函数签名往往需要 `T* begin, T* end` 或 `T* ptr, size_t count`。使用 `std::span` 可以一次性传递区段,接口更简洁且易读。
“`cpp
void process(span
data) {
// 处理
}
process(numbers); // 传整个 vector
process(numbers.subspan(3, 5)); // 只处理 4~8 个元素
“`
2. **迭代器封装**
对于非连续容器(如 `std::list`)不适用;但对于 `std::array`、`std::vector`、C 风格数组,`span` 提供了统一的访问方式。
3. **性能敏感代码**
避免无谓的拷贝、减少栈内存开销(`span` 本身只有两指针),提升缓存命中率。
—
### 4. 注意事项与陷阱
– **生命周期**:`span` 必须引用的底层数据在 `span` 生命周期内保持有效。
“`cpp
std::span
s;
{
std::vector
temp{1, 2, 3};
s = temp; // temp 失效,s 成为悬空引用
}
“`
– **可变性**:如果传递给 `const span
`,无法修改数据;如果想修改,确保底层容器可写。
– **多维数组**:`std::span` 本身是一维的,但可以嵌套使用 `std::span>` 或者使用 `std::span>` 处理二维矩阵。
—
### 5. 进一步扩展:使用 `std::span` 结合 `std::span` 进行内存映射
当需要将文件或网络数据映射为字节视图时,`std::span` 非常适合。
“`cpp
#include
#include
#include
#include
int main() {
std::ifstream file(“data.bin”, std::ios::binary);
file.seekg(0, std::ios::end);
std::size_t size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector buffer(size);
file.read(reinterpret_cast(buffer.data()), size);
std::span dataSpan{ buffer };
// 读取整数
std::uint32_t val = *reinterpret_cast(dataSpan.data());
std::cout