C++20 中的 std::span 如何帮助避免数组越界?

在 C++20 中,std::span 是一种轻量级、无所有权的容器视图,用来安全地访问数组、std::vectorstd::array 等连续数据块。它通过在编译期和运行时检查尺寸和边界,降低了数组越界的风险,并提升了代码的可读性与可维护性。

1. 什么是 std::span?

std::span 本质上是一个指向连续内存块的指针加上长度信息的组合。它不拥有数据,只是对已有数据的一种“视图”。

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

std::vector <int> v = {1, 2, 3, 4, 5};
std::span <int> s(v);           // 创建对 vector 的 span 视图
std::span<const int> cs = s;   // 对 const 数据的视图

2. 防止越界的机制

2.1 编译期检查

  • std::span 的构造函数可以接受固定大小的数组(如 int[10]),编译器会检查大小是否匹配,防止错误初始化。
  • std::spanoperator[] 是一个未检查的访问(与裸指针相同),但可以使用 at()subspan() 进行边界检查。

2.2 运行时检查

  • std::span::at(size_type pos) 在访问时会检查 pos < size(),如果越界则抛出 std::out_of_range
  • std::span::subspan(pos, count) 同样会检查 pos + count <= size(),保证子段合法。
std::span <int> s(v);
try {
    std::cout << s.at(10) << '\n';   // 会抛出异常
} catch (const std::out_of_range& e) {
    std::cerr << "越界访问: " << e.what() << '\n';
}

3. 与原始指针的区别

原始指针 std::span
语义清晰 仅指针 指针 + 长度
传参便利 需要手动传递长度 自动携带长度
越界风险 可能出现 可以通过 at() / subspan() 防止
对齐需求 任何地址 同样不要求对齐

4. 实际使用场景

4.1 函数参数

使用 std::span 作为函数参数可以让函数既能接受 std::vectorstd::array、裸数组,又能显式表达函数不拥有数据。

void process(std::span <int> data) {
    // data 可以安全访问,使用 at() 防止越界
}

4.2 读写视图

在需要对数组做分块处理时,subspan 非常方便。

std::span <int> full(v);
auto firstHalf = full.subspan(0, full.size() / 2);
auto secondHalf = full.subspan(full.size() / 2);

4.3 与 STL 算法配合

STL 算法往往接受迭代器区间,std::span 可以轻松转换为 begin() / end()

std::sort(full.begin(), full.end()); // 就地排序

5. 代码示例:安全加法

#include <span>
#include <iostream>
#include <vector>
#include <numeric>
#include <stdexcept>

int safe_add(std::span<const int> a, std::span<const int> b) {
    if (a.size() != b.size()) {
        throw std::invalid_argument("向量大小不匹配");
    }
    int sum = 0;
    for (size_t i = 0; i < a.size(); ++i) {
        sum += a[i] + b[i];            // 这里使用 [],因为我们已检查大小
    }
    return sum;
}

int main() {
    std::vector <int> a{1, 2, 3};
    std::vector <int> b{4, 5, 6};
    std::cout << "总和: " << safe_add(a, b) << '\n';
    return 0;
}

6. 小结

  • std::span 通过提供长度信息,天然地提升了对连续数据的安全访问。
  • 与裸指针相比,它降低了忘记传递长度的风险,并可以在需要时通过 at() 等方法做边界检查。
  • 在 C++20 之后,使用 std::span 已成为函数接口传递数组的推荐做法,既保持了性能,又提升了安全性。

通过掌握 std::span 的使用,你可以在不牺牲性能的前提下,让你的 C++ 代码更安全、更易读。

发表评论