**如何使用C++20的std::span实现安全的数组切片**

std::span 是 C++20 标准库中新加入的一个轻量级容器视图,旨在为数组、std::vector、甚至是普通指针+长度组合提供一种安全、无所有权的视图。通过使用 std::span,可以在不复制数据的情况下,传递和操作子范围,既保留了性能,又避免了传统裸指针可能导致的越界错误。


1. std::span 的基本概念

  • 无所有权std::span 只持有一个指针和长度信息,不负责内存分配或释放。
  • 大小已知:编译时如果使用固定大小的数组,可以得到 std::span<T, N>,其中 N 是编译时已知的长度;否则使用动态大小的 std::span<T>
  • 兼容性:可以从 T*std::arraystd::vector、甚至是 std::initializer_list 构造 std::span

2. 示例一:从普通数组创建 std::span

#include <iostream>
#include <span>

void process(std::span <int> data) {
    for (int &x : data)
        x *= 2;
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    std::span <int> whole(arr);          // 视图整个数组
    std::span <int> part(arr + 1, 3);    // 视图从第二个元素开始的 3 个元素

    process(whole);
    process(part);

    for (int x : arr)
        std::cout << x << ' ';
    std::cout << '\n';  // 输出:2 6 8 10 10
}

说明

  • whole 视图覆盖整个数组;
  • part 仅覆盖中间 3 个元素;
  • process 在视图范围内直接修改原始数组。

3. 示例二:从 std::vector 创建 std::span

#include <vector>
#include <span>
#include <algorithm>
#include <iostream>

void sort_span(std::span <int> data) {
    std::sort(data.begin(), data.end());
}

int main() {
    std::vector <int> vec = {9, 1, 5, 3, 7};
    std::span <int> view(vec);           // 视图整个 vector
    std::span <int> sub(view.data() + 1, 3); // 视图 vec[1..3]

    sort_span(view);   // 整个 vector 排序
    sort_span(sub);    // 只对子范围排序

    for (int x : vec)
        std::cout << x << ' ';
    std::cout << '\n';  // 输出:1 3 5 7 9
}

说明

  • std::span 兼容 std::vector 的内部连续内存;
  • 对子范围排序不会影响 view 的其余部分。

4. 防止越界的安全性

传统的裸指针传递数组子范围时,常见错误是忘记减去偏移量导致访问越界。std::span 通过在构造时检查范围长度来避免这种错误。

int arr[3] = {10, 20, 30};
std::span <int> bad(arr + 1, 3);  // 只剩 2 个元素,但指定 3 → 抛出 std::out_of_range

此时编译器不会捕获错误,但运行时 std::span 在构造时会抛出异常(如果已开启检查),或者在 debug 模式下产生断言。


5. 与 std::array 的结合

使用 std::array 时可以得到编译时已知大小的 std::span<T, N>

#include <array>
#include <span>

int main() {
    std::array<int, 5> a = {1,2,3,4,5};
    std::span<int, 5> s(a);  // 视图整个数组,大小 5 已知
}

此种方式提供了更强的类型安全,编译器可以在编译阶段检查长度。


6. 常见用途

  1. 函数接口:使用 std::span 代替 T* + size_t,减少参数数量并提高可读性。
  2. 切片操作:在不复制数据的情况下操作子数组,例如矩阵行/列切片。
  3. 互操作性:与 C 代码共享数组数据时,可直接转换为 std::span

7. 性能评估

  • 零成本std::span 仅包含指针和长度两个成员,大小与 T* + size_t 相同。
  • 无复制:所有操作都是对原始数据的视图,避免了深拷贝。
  • 编译时检查:在固定长度情况下,编译器可进一步优化。

8. 结语

std::span 的出现大大简化了 C++ 对数组和容器子范围的操作,提供了既安全又高效的方式。通过将其与 STL 算法、模板编程相结合,能够写出更简洁、可维护且错误率更低的代码。下次在需要传递数组片段时,试试用 std::span 替换裸指针,感受一下它的“轻量”与“安全”。

发表评论