C++20 中 std::span 的实用技巧

在 C++20 标准中,std::span 成为一个轻量级的非拥有视图(view),它允许我们在不复制数据的情况下对数组、vector、字符串等连续存储的容器进行访问。下面从基本用法、性能优势、常见错误以及实战示例四个方面,系统梳理 std::span 的使用技巧。

1. 基础用法

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

int main() {
    std::vector <int> vec{1, 2, 3, 4, 5};

    // 直接从 vector 创建 span
    std::span <int> s1{vec};

    // 也可以从原始数组创建
    int arr[] = {10, 20, 30};
    std::span <int> s2{arr};

    // 访问元素
    std::cout << "s1: ";
    for (auto x : s1) std::cout << x << ' ';
    std::cout << "\ns2: ";
    for (auto x : s2) std::cout << x << ' ';
}

注意:std::span 仅是一个视图,它不拥有所指向的数据,生命周期须与底层数据保持一致。

2. 子视图(Subspan)与裁剪

使用 subspan 可以得到更小的视图,支持起始偏移和长度。C++20 之前的 subspan 只能接受偏移;C++23 开始支持长度。

std::span <int> full = {vec};           // {1,2,3,4,5}
std::span <int> part = full.subspan(2); // {3,4,5}
auto middle = full.subspan(1, 3);      // {2,3,4}

技巧:在算法中使用 subspan 可以避免手动计算下标,提升代码可读性。

3. 与 C++ 标准库算法的配合

std::span 能够直接作为标准算法的参数,因为它满足 ForwardRange 要求。示例:

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

int main() {
    std::vector <int> v{5, 2, 4, 1, 3};

    std::span <int> span_v{v};
    std::ranges::sort(span_v);           // std::sort(span_v.begin(), span_v.end());

    for (auto x : span_v) std::cout << x << ' '; // 输出排序后的结果
}

提示:如果你想保留原始容器而对其进行排序,直接把 span 传给算法即可,不需要复制。

4. 互操作性:与 std::array、C-style array、std::string

  • std::array

    std::array<double, 4> arr{1.1, 2.2, 3.3, 4.4};
    std::span <double> sp{arr};
  • C-style array:已演示 subspan 示例。

  • std::string:字符串本身可视作 char 的连续存储,`std::span

    ` 可以直接操作字符内容。 “`cpp std::string str = “Hello, world!”; std::span s = str; std::ranges::replace(s, ‘l’, ‘x’); // 替换所有 ‘l’ 为 ‘x’ “`

警告:对 std::string 进行 `span

` 视图时,若使用 `const std::string`,只能得到 `std::span`。

5. 常见错误与调试技巧

错误类型 典型表现 解决方案
悬空引用 对已析构对象创建 span 确保 span 的生命周期不超过底层数据
写入超界 通过 subspan 指定长度大于原视图长度 使用 subspan 的返回值检查 span::size()
类型不匹配 将 `span
传递给期望span的函数 | 明确const或使用std::as_const(span)`
隐式复制 span 作为函数返回值 只能返回 std::span<const T>,或者返回引用的容器

调试技巧:在调试器中使用 span::data()span::size() 检查指针和值是否合理。

6. 进阶:自定义 span 边界检查

如果想在运行时对 span 进行更严格的边界检查(如在 debug 版本中),可以写一个包装类:

template <typename T>
class CheckedSpan {
    std::span <T> sp_;
public:
    CheckedSpan(std::span <T> sp) : sp_(sp) {}
    T& operator[](size_t i) {
        assert(i < sp_.size());
        return sp_[i];
    }
    // 其它转发方法……
};

7. 小结

  • std::span 是一种非拥有、轻量级的视图,适合对连续存储的数据做无复制访问。
  • 利用 subspan 可以方便地裁剪视图,配合 std::ranges 算法,代码既简洁又高效。
  • 关注生命周期与边界安全,避免悬空引用和越界写入。
  • 对于字符串、vector、array 等常见容器,span 都提供了完美的互操作性。

掌握这些技巧后,你可以在 C++20 项目中无缝使用 std::span,既提升代码可读性,又保证运行时性能。

发表评论