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
不能比它引用的数据寿命更长。