C++ / a working model

57 / 80   ·   C++20   ·   约 10 分钟

string_view 与 span:轻量借用不延寿

先记住这句话

string_view 与 span 把一段连续数据的地址和范围交给调用者,不拥有数据。它们能减少复制并统一接口,但原对象销毁、重新分配或失效后,视图也会失效;只读视图同样不能逃离生命周期约束。

本篇内容
  1. 拥有数据与描述数据分开
  2. 只读对象与只读元素是两层 const
  3. 范围信息不等于自动边界保护
  4. 运行示例
  5. 动手练习

拥有数据与描述数据分开

C++17 的 string_view 提供只读字符序列接口,C++20 的 span<T> 描述连续 T 序列。它们不负责释放元素,也不会因为复制视图而复制底层数据。适合在调用期间借用 string、数组或 vector 的一段内容,避免强迫调用者先构造新的容器。

这种轻量性有明确代价:视图不延长所有者生命期,底层存储重新分配后旧地址也不再可用。函数若返回指向自身局部 string 的 string_view,或把临时 vector 的存储借给长期 span,都把生命周期错误包装成了看似现代的接口。

只读对象与只读元素是两层 const

span<const int> 禁止通过视图修改元素,const span<int> 则只是禁止修改视图对象自己的绑定信息,仍可写入 int 元素。string_view 的字符访问本身是只读的,但外部所有者仍可能修改字符,所以视图不是不可变快照。

string_view 还不保证结尾有零字符。截取前缀后,data 指针后面可能紧跟原字符串的剩余内容,而不是 '\0'。传给要求 C 字符串的接口前,应改用明确长度的接口,或构造一个真正拥有且零结尾的 string。

范围信息不等于自动边界保护

span 可以有编译期固定 extent,也可以在运行期保存长度。范围信息让接口比裸指针更完整,但 C++20 的 operator[] 不进行标准要求的抛异常边界检查;subspan 的参数也必须满足前置条件。调用者与实现仍应验证切片范围。

示例用仍存活的 string 生成首字段视图,再用 span 修改数组尾部。修改前后都没有让所有者扩容或销毁,切片长度也明确合法。需要跨线程、跨回调或长期缓存结果时,先决定谁拥有存储;如果无法简单证明生命期,复制成拥有对象通常比隐藏悬空风险更合理。

容易答错的地方

  • string::substr 返回新的 string,不能直接把其临时结果长期赋给 string_view;先形成 string_view 再调用其 substr 才是对原存储切片。
  • vector 扩容、string 的失效操作或所有者销毁都会影响视图;视图复制与 const 限定都无法修复已失效的存储。

运行一个例子

最低标准 C++20 · 完整程序 · 下载 .cpp

#include <array>
#include <cassert>
#include <iostream>
#include <span>
#include <string>
#include <string_view>

std::string_view first_field(std::string_view text) {
    return text.substr(0, text.find(','));
}
void increment(std::span<int> values) {
    for (int& n : values) ++n;
}

int main() {
    std::string text = "alpha,beta";
    std::string_view field = first_field(text);
    assert(field == "alpha" && field.size() == 5);
    std::array<int, 3> values{1, 2, 3};
    increment(std::span<int>(values).subspan(1));
    std::span<const int> read_only(values);
    assert(read_only[0] == 1 && read_only[1] == 3 && read_only[2] == 4);
    std::cout << field << ' ' << read_only[1] << ' ' << read_only[2] << '\n';
}

在本地编译

g++ -std=c++20 -Wall -Wextra -Wpedantic -pthread modern-views.cpp -o example && ./example

预期结果

alpha 3 4

CHECK YOUR UNDERSTANDING

合上答案,试着解释。

为什么 auto field = first_field(std::string("alpha,beta")); 不能在下一条语句安全输出?怎样保存结果?

查看参考答案

临时 string 在初始化这条完整表达式结束时销毁,返回的视图随即悬空。先把 string 保存为比 field 活得久的局部所有者,或在临时仍存活的同一完整表达式中构造拥有结果:std::string field(first_field(std::string("alpha,beta")));。后者复制字符,独立管理生命期。

继续查证

标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。

回到目录