c++20的std::span和std::string_view有什么核心区别? (所有权与可变性)

std::span是泛型可读写视图,持有原始指针与长度且不管理内存;std::string_view是字符专用只读视图,隐含字符串语义但不保证null结尾,二者类型定位不同导致行为差异。

std::span 持有原始指针+长度,不管理内存所有权

它只是对已有内存的一层轻量视图,std::span 可以指向栈数组、堆分配的 std::vector::data(),甚至 C 风格数组,但绝不负责释放——析构时不做任何事。这点和 std::string_view 一致,但适用范围更广。

常见误用:std::span make_span() { char buf[10] = {}; return std::span{buf}; } —— 返回后 buf 被销毁,span 指向悬垂内存。

std::string_view 专用于字符序列,隐含 null-termination 语义但不依赖它

std::string_view 的底层是 const char* + size_t,只接受字符类型(charchar8_t 等),且设计初衷是替代 const std::string& 做只读字符串参数传递。它不保证数据以 \0 结尾,但多数成员函数(如 data())返回的指针可安全传给 C 接口——前提是调用者确保内容合法。

关键限制:std::string_view 不能持有 std::vectordata() 并自动推导长度(除非显式传入 .size()),而 std::span 对任意连续容器都支持构造:

std::vector v = {'h', 'e', 'l', 'l', 'o'};
std::span s1{v};                    // OK,自动推导为 span
std::string_view sv{v.data(), v.size()}; // 必须手动传 size,否则编译失败

可变性:std::span 支持非常量元素访问,std::string_view 固定只读

std::span 中的 T 可以是非 const 类型,因此能修改所指内存:

int arr[] = {1, 2, 3};
std::span s{arr};
s[0] = 42; // 合法:修改 arr[0]

std::string_view 的模板参数固定为 const CharT*,没有非 const 版本。即使你写 std::string_view,实际仍是 const char* 底层——C++20 标准明确禁止提供可变字符视图,避免与 std::string 职责混淆。

注意:这不是“设计疏漏”,而是有意为之。若需可变字符操作,请用 std::span 或直接操作原容器。

类型安全与隐式转换陷阱

std::string_view 允许从 const char* 隐式构造(只要带长度或含 \0),但 std::span 对隐式转换极其克制:

  • std::string_view{"hello"} → OK(字面量推导为 const char[6]
  • std::span{arr} → OK(数组退化为指针+推导长度)
  • std::span{ptr}(仅指针)→ 编译失败:必须显式指定长度或使用容器
  • std::span{std::vector{1,2,3}} → OK(完美转发容器)

最易踩坑的是把 std::string_view 当通用视图用:它无法表示 std::vectorstd::array 等非字符类型——这时候必须换 std::span

核心差异其实就两点:一个是「字符专用只读视图」,一个是「泛型可读写视图」;所有行为差异都从这个根本定位里长出来。别试图让 string_viewspan 的事,也别用 span 替代 string_view 去接 API 要求字符串语义的地方——类型系统在这儿是有真实约束力的。