C++20 引入了 std::span
类模板,它是一个轻量级的、非拥有的范围视图,用于表示连续的内存区域。std::span
可以用于数组、std::vector
、std::string
等数据结构,提供了一种统一的方式来访问这些数据结构的元素。
std::span
首先不是一个容器,而是一个视图,它不拥有数据,只是对数据的引用。这使得 std::span
更加高效,因为它不需要复制数据。std::span
可以用于函数参数,允许函数接受任意大小的数组或容器,而无需复制数据。
本文将介绍 std::span
的基本用法和一些示例。
创建 std::span
std::span
可以通过多种方式创建.在声明时指定了元素数量的是固定 span,未指定大小的是动态 span。动态span的大小可以随着对应容器大小的变化而变化,而固定span的大小是固定的。
std::vector<int> v{1, 2, 3, 4, 5};
// 从vector容器创建 span
auto s1 = std::span{v};
fmt::println("s1: {}", s1); // s1: [1, 2, 3, 4, 5]
// 使用指针和大小创建 span
auto s2 = std::span{v.data(), 3};
fmt::println("s2: {}", s2); // s2: [1, 2, 3]
// 从view创建 span
auto s3 = std::views::counted(v.begin() + 2, 3);
fmt::println("s3: {}", s3); // s3: [3, 4, 5]
// 从数组创建 span
int arr[] = {1, 2, 3, 4, 5};
auto s4 = std::span{arr + 1, 3};
fmt::println("s4: {}", s4); // s4: [2, 3, 4]
// 从std::array创建 span
std::array<int, 5> arr2{1, 2, 3, 4, 5};
auto s5 = std::span{arr2};
fmt::println("s5: {}", s5); // s5: [1, 2, 3, 4, 5]
// 从span构建sub span
auto s6 = s5.subspan(1, 3);
fmt::println("s6: {}", s6); // s6: [2, 3, 4]
std::span
的操作
可以使用 first
和 last
方法获取 span 的前几个或后几个元素; 这里有两个版本:
first<N>()
和last<N>()
返回一个新的 span,包含前 N 个或后 N 个元素。是一个模板函数.first(N)
和last(N)
返回一个新的 span,包含前 N 个或后 N 个元素。subspan(pos, count)
返回一个新的 span,包含从位置 pos 开始的 count 个元素。
std::vector<int> vec{1, 2, 3, 4, 5, 6};
std::span s{vec};
auto sp1 = s.first<2>();
fmt::println("sp1: {}", sp1); // sp1: [1, 2]
auto sp2 = s.last<2>();
fmt::println("sp2: {}", sp2); // sp2: [5, 6]
auto sp3 = s.first(2);
fmt::println("sp3: {}", sp3); // sp3: [1, 2]
auto sp4 = s.last(2);
fmt::println("sp4: {}", sp4); // sp4: [5, 6]
auto sp5 = s.subspan(2, 3);
fmt::println("sp5: {}", sp5); // sp5: [3, 4, 5]
std::span
注意事项
std::span
不拥有数据,只是对数据的引用,因此在std::span
存在时,数据必须保持有效。std::span
如果span的底层数据被销毁,那么span将变成悬垂指针,这是一种未定义行为。
std::vector<int> vec{1, 2, 3, 4, 5, 6};
std::span s{vec};
auto current = vec.capacity();
// may cause dangling reference
for (auto i = 7; i < 10; i++) {
vec.push_back(i);
}
if (current != vec.capacity()) {
// fmt::println("s: {}", s); // Error
// s now is a dangling reference
// s should be re-assigned
s = vec;
}
总结
可以看出std::span
跟std::views
有很多相似之处, 实际上, std::span
是std::views
的一个特例. 关于std::views
的更多内容可以查看我之前的博客: C++20 Ranges 简介.