C++20 std::span:简洁高效的数组视图

在 C++20 中引入的 std::span 为我们提供了一种安全、轻量级且高效的方式来表示对数组或容器片段的视图。它并不拥有数据,而是仅仅维护一个指针和长度,类似于 STL 中的指针+长度组合,但提供了更好的类型安全和接口。下面从概念、使用场景、优势与限制四个方面进行阐述,并给出实际代码示例。

1. std::span 的基本概念

  • 无所有权:span 不会拷贝或持有数据,只是对已有连续内存区域的“窗口”。
  • 指针 + 长度:内部实现通常是 T* ptrsize_t len,因此大小仅为 16 字节(在 64 位平台上)。
  • 模板化std::span<T, Extent>,其中 Extent 可为固定大小或 std::dynamic_extent 表示动态大小。
  • 兼容性:可以直接从 C++ 标准容器(如 std::arraystd::vector)或裸数组创建,也可以转换为另一种 T 类型的 span(如 constvolatile)。

2. 典型使用场景

场景 用法 说明
函数参数 `void process(span
data)| 传递任何int` 数组或容器的连续块,避免复制。
子区切片 auto sub = data.subspan(5, 10) 取原视图的一段,常用于处理子数组。
循环遍历 for (auto& x : data) { ... } 支持基于范围的 for,避免手动指针运算。
统一接口 接收 `std::span
std::string_view` 共同处理 可以将字符串视为字符数组,兼容老旧代码。

3. 代码示例

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

// 1. 计算数组和
int sum(span <int> data) {
    return std::accumulate(data.begin(), data.end(), 0);
}

// 2. 反转视图
void reverse_in_place(span <int> data) {
    std::reverse(data.begin(), data.end());
}

// 3. 将子段拷贝到另一个容器
template<typename T>
void copy_subspan(span <T> src, std::vector<T>& dst, size_t offset, size_t len) {
    if (offset + len > src.size()) throw std::out_of_range("subspan out of range");
    dst.insert(dst.end(), src.subspan(offset, len).begin(), src.subspan(offset, len).end());
}

int main() {
    std::array<int, 10> arr{0,1,2,3,4,5,6,7,8,9};
    std::vector <int> vec{10,11,12,13,14};

    // 直接使用 span
    std::span <int> s_arr = arr;   // 从数组创建
    std::span <int> s_vec = vec;   // 从 vector 创建

    std::cout << "sum arr: " << sum(s_arr) << '\n';   // 45
    std::cout << "sum vec: " << sum(s_vec) << '\n';   // 60

    reverse_in_place(s_arr);
    std::cout << "reversed arr: ";
    for (auto v : s_arr) std::cout << v << ' ';
    std::cout << '\n';

    // 子段拷贝
    std::vector <int> dst;
    copy_subspan(s_vec, dst, 1, 3);   // 复制 vec[1..3] -> dst
    std::cout << "dst after copy: ";
    for (auto v : dst) std::cout << v << ' ';
    std::cout << '\n';

    // 与 const 兼容
    std::span<const int> const_s = s_arr;
    std::cout << "const span size: " << const_s.size() << '\n';

    return 0;
}

关键点说明

  • 自动推断:`std::span s_arr = arr;` 自动推断指针类型。
  • 子段subspan(offset, len) 必须保证范围合法,否则会抛出异常。
  • 范围兼容spanstd::initializer_liststd::string_view 等不直接兼容,需显式转换。

4. 优势与局限

优势 说明
轻量 仅 16 字节,适合堆栈传递
安全 提供范围检查,可避免越界访问
兼容性 与多种容器无缝交互
可读性 语义明确,代码可维护
局限 说明
不支持非连续容器 只能视为连续内存
复制成本 需要手动拷贝以持久化数据
老旧编译器 仅在 C++20+ 可用,部分 IDE 仍需配置

5. 结语

std::span 为 C++20 提供了一个简单、强大且类型安全的“数组视图”工具,能大幅提升代码的可读性与性能。无论是对已有 API 的包装,还是对新算法的实现,掌握 span 的使用都是现代 C++ 开发者的必备技能之一。欢迎你在项目中尝试 span,并观察其对代码质量和运行效率的积极影响。

发表评论