每周技巧 #1:`string_view`
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #1: string_view。
原文最初作为 TotW #1 发布于 2012 年 4 月 20 日。
更新于 2020 年 8 月 18 日。
快捷链接:abseil.io/tips/1
什么是 string_view,为什么你应该关心它?
当你创建一个函数,让它接收一个(常量)字符串参数时,通常有三种选择:其中两种你已经熟悉,另一种你可能还没注意到:
|
|
如果调用方手里已经有对应格式的字符串,前两种写法工作得很好。但如果需要转换呢?比如从 const char* 转成 std::string,或者从 std::string 转成 const char*?
需要把 std::string 转成 const char* 的调用方,必须使用高效但不太方便的 c_str() 函数:
|
|
需要把 const char* 转成 std::string 的调用方不需要额外写任何东西,这是好消息;但这会触发一个临时字符串的创建,并复制字符串内容,这是方便但低效的坏消息:
|
|
应该怎么做?
Google 更推荐用 string_view 接收这类字符串参数。它是一个从 C++17 “提前采用”的类型;目前即使 std::string_view 可用,也请使用 absl::string_view。
string_view 类的一个实例可以理解为对已有字符缓冲区的一层“视图”。具体来说,string_view 只包含一个指针和一个长度,用来标识一段字符数据;这段数据不归 string_view 所有,也不能通过这个视图修改。因此,拷贝 string_view 是浅拷贝:不会复制任何字符串数据。
string_view 有从 const char* 和 const std::string& 隐式转换的构造函数。由于 string_view 不会复制内容,隐藏拷贝不会带来 O(n) 的内存代价。传入 const std::string& 时,构造函数以 O(1) 时间运行。传入 const char* 时,构造函数会自动调用 strlen();你也可以改用接收两个参数的 string_view 构造函数。
|
|
因为 string_view 不拥有数据,所以它指向的任何字符串,就像 const char* 指向的字符串一样,都必须有已知的生命周期,并且必须比 string_view 本身活得更久。
这意味着,把 string_view 用作存储字段时常常值得怀疑:你需要证明底层数据一定会比这个 string_view 活得更久。例如,如果下面这个结构体可能在接收它的函数调用结束后继续保存,那么它很可能就不合理:
|
|
如果你的 API 只需要在单次调用期间引用字符串数据,并且不需要修改这些数据,那么接收 string_view 就足够了。
如果你需要稍后继续使用这些数据,或者需要修改数据,可以用 std::string(my_string_view) 显式转换成 C++ 字符串对象。另一种做法见 技巧 #117:按值传递 std::string,并在适用时让调用方使用 std::move。
把 string_view 加入已有代码库并不总是正确答案:如果这些参数随后又会传给要求 std::string 或以 NUL 结尾的 const char* 的函数,那么把参数改成按 string_view 传递可能反而低效。最好从工具代码开始逐层向上采用 string_view,或者在新项目里从一开始就保持完全一致。
几点补充说明
-
与其他字符串类型不同,应该像传递
int或double一样按值传递string_view,因为string_view是一个很小的值。 -
把
string_view标记为const,只会影响这个string_view对象本身能否被修改,并不会影响它能否用来修改底层字符。它永远不能修改底层字符。这和const char*完全类似:即使指针本身可以被修改,也不能通过它修改字符。 -
对函数参数来说,不要在函数声明中用
const修饰string_view(见 技巧 #109)。在函数定义中,可以由你或你的团队自行决定是否用const修饰string_view,例如为了和周围代码保持一致(技巧 #109)。对于其他局部变量,使用const既不特别鼓励,也不特别反对(见 Google C++ 风格指南)。 -
string_view不一定以 NUL 结尾。因此,下面这种写法不安全:1printf("%s\n", sv.data()); // 不要这样做更推荐这样写(见 技巧 #124):
1absl::PrintF("%s\n", sv); -
你可以像记录
std::string或const char*一样记录string_view:1LOG(INFO) << "Took '" << sv << "'"; -
在多数情况下,可以安全地把一个接收
const std::string&或以 NUL 结尾的const char*的已有函数改成接收string_view。我们在执行这种改动时遇到的唯一风险是:如果有人取了这个函数的地址,构建会失败,因为改动后的函数指针类型不同。 -
string_view有constexpr构造函数和平凡析构函数;在静态变量和全局变量(见 风格指南)或常量(见 技巧 #140)中使用它时,需要记住这一点。 -
string_view本质上是一种引用,因此未必适合作为成员变量(另见 技巧 #180)。