Tip of the Week 93 使用absl::Span
Tip of the Week#93: 使用absl::Span
在google,当我们想处理没有owner的字符串时,通常会使用absl::string_view作为函数的参数和返回值。它能够使API更加灵活,并且它能够通过避免对std::string进行不必要的转换来提升性能。
absl::string_view有一个更加通用的表亲,被称为absl::Span。absl::Span对std::vector就像absl::string_view对std::string一样。它为vector的元素提供只读接口,但是它也可以从由非vector(如数组和初始化列表)来构造,并且不会产生拷贝元素的消耗。
const可以被删除,因此absl::Span是一个元素不能改变的数组的视图,absl::Span允许对元素进行非常量访问。然而,与const的跨度不同,这些需要显式构造。
关于std::span/gsl::span的注释
需要重点注意的是,虽然absl::Span在设计和目的上与std::span方案(以及现存的gsl::span引用实现)相似,但是absl::Span目前并不保证是一个对最终标准的随时替代品,因为std::span方案仍在开发和变化。
相反,absl::Span旨在拥有一个尽可能与absl::string_view类似的接口,而不是针对特定于字符串的功能。
作为函数参数
使用absl::Span作为函数参数的一些好处类似于使用absl::string_view的好处。
调用者可以传递出事vector的一个切片,或者传递一个纯数组。它也兼容其他类数组的容器,像absl::InlinedVector,absl::FixedArray,google::protobuf::RepeatedField等等。
与absl::string_view一样,当用作函数参数时,通常最好是按值传递absl::Span;这种方式比通过const引用(在大多数平台上)传递稍微快点,并且生成更小的代码。
示例:Span的基本用法
1  | void TakesVector(const std::vector<int>& ints);  | 
预防缓冲区溢出
因为absl::Span知道它自己的长度,相较于C风格的指针长度,API使用它会更安全。
示例:更安全的memcpy()
1  | // 糟糕的代码  | 
1  | // 一个简单的示例,但是复用Span已知的大小来阻止上述错误  | 
关于指针的vector的const的正常性
传递std::vector<T*>的一个大问题是你不能再不改变容器类型的情况下使得指针指向的内容为const。
任何带有const std::vector<T*>的函数都不能修改vector,但是它能够修改T类型的值。这也适用于返回const std::vector<T*>&的访问器。你不能阻止调用者修改T类型的值。
通常的“解决方案”包括拷贝或者转换vector为正确的类型。这些解决方案是慢的(对于拷贝)或者未定义的行为(对于转换),应该避免它们。相反,请使用absl::Span
示例:函数参数
考虑这些Frob变体:
1  | void FrobFastWeak(const std::vector<Foo*>& v);  | 
从一个需要Frob的const std::vector<Foo*>& V开始,你有两个不完美的选项和一个完美的。
1  | // 更快更容易输入但是不安全  | 
示例:访问器
1  | // 糟糕的代码  | 
1  | // 好的代码  | 
结论
在恰当使用时,absl::Span可以提供解耦,常量正确性和性能优势。
需要重点注意的是,absl::Span的行为非常像absl::string_view,在引用一些外部占有的数据上。所有相同的警告都适用。特别的是,absl::Span不能比它引用的数据寿命更长。










