57 / 80 · C++20 · 约 10 分钟
string_view 与 span:轻量借用不延寿
string_view 与 span 把一段连续数据的地址和范围交给调用者,不拥有数据。它们能减少复制并统一接口,但原对象销毁、重新分配或失效后,视图也会失效;只读视图同样不能逃离生命周期约束。
拥有数据与描述数据分开
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")));。后者复制字符,独立管理生命期。
继续查证
标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。