使用 C++20 的 std::span 与容器之间的高效转换

在 C++20 中,std::span 提供了一种轻量级的、非拥有的视图,用于访问连续内存块。与传统的指针或数组不同,std::span 还可以与任何支持 begin()end()data()size() 的容器互操作。下面我们通过几个例子来演示如何在不同容器之间快速、无拷贝地进行转换,以及如何利用 std::span 提升代码的灵活性和性能。


1. 直观的 span 对容器的包装

#include <iostream>
#include <vector>
#include <array>
#include <span>

void printSpan(const std::span <int>& s) {
    for (int x : s) std::cout << x << ' ';
    std::cout << '\n';
}

int main() {
    std::vector <int> vec = {1, 2, 3, 4, 5};
    std::array<int, 3> arr = {10, 20, 30};

    // 直接从容器创建 span
    std::span <int> sv = vec;
    std::span <int> sa = arr;

    printSpan(sv);  // 输出: 1 2 3 4 5
    printSpan(sa);  // 输出: 10 20 30
}

在上述代码中,std::span 的构造函数可以接受任何支持连续内存的容器,自动推断长度。


2. 通过 data()size() 手动构造

有时你需要对容器的子范围做视图:

std::vector <int> vec = {5, 10, 15, 20, 25, 30};

auto firstHalf = std::span <int>(vec.data(), vec.size() / 2);
auto secondHalf = std::span <int>(vec.data() + vec.size() / 2, vec.size() / 2);

printSpan(firstHalf);   // 5 10 15
printSpan(secondHalf);  // 20 25 30

使用 data() 指针和 size() 长度,可以在不复制数据的前提下切分视图。


3. 读写视图的区别

std::span 有两种形式:可读写的 `std::span

`,以及只读的 `std::span`。如果你不想让函数修改数据,最好传入 `const` 视图。 “`cpp void incrementAll(std::span s) { for (int& x : s) ++x; } void printReadOnly(const std::span& s) { for (int x : s) std::cout a = {1, 2, 3, 4}; int carray[5] = {5, 6, 7, 8, 9}; std::span spanA = a; // 从 std::array 直接 std::span spanC = std::make_span(carray, 5); // 指定长度 printSpan(spanA); // 1 2 3 4 printSpan(spanC); // 5 6 7 8 9 “` C++20 还引入了 `std::make_span`,它可以直接从裸指针和长度创建 span。 — ### 5. 与算法的无缝配合 许多标准算法已经接受 `std::span`。例如 `std::sort` 可以直接作用于 span: “`cpp std::vector data = {9, 3, 7, 1, 5}; std::sort(std::begin(data), std::end(data)); // 传统方式 std::sort(data.begin(), data.end()); // 同样可行 std::sort(std::span (data)); // 用 span 方式 printSpan(data); // 1 3 5 7 9 “` 通过 `std::span`,你可以在保持算法签名的同时,让它接受任何连续容器。 — ### 6. 性能与安全性 – **无拷贝**:`std::span` 只持有指针和长度,完全不复制数据,极大地提升性能。 – **边界检查**:在调试模式下,`std::span` 可以通过 `std::span::operator[]` 做边界检查;在发布模式下,默认不做检查,保持高效。 – **生命周期管理**:`std::span` 本身不拥有底层数据,因此在使用时必须确保底层容器在 span 生命周期内不被销毁或修改其内存布局。 — ### 7. 小结 – `std::span` 是一种非拥有、轻量级视图,适用于任何连续容器。 – 它提供了灵活的构造方式,支持子范围、只读/可写切换以及与算法的无缝集成。 – 在不牺牲性能的前提下,`std::span` 能让代码更安全、更易读。 掌握 `std::span` 的使用,可以让你的 C++20 代码在处理数组、向量、字符串等连续数据时更加简洁、高效。祝你编码愉快!

发表评论