翻译自:Tip of the Week #1: string_view,如有错误欢迎指正。
这个tip的背景是,将字符串作为函数参数进行传递。
常规方法
将字符串作为函数参数进行传递,容易想到的是以下两个方法:
void TakesCharStar(const char* s); // C
void TakesString(const string& s); // Old Standard C++</code></pre>
这两种方法只能接收确定类型的字符串作为参数,否则可能需要类型转换。比如下面这两个场景:
一、使用c_str()
函数将string
类型显式转换为const char*
void AlreadyHasString(const string& s) {
TakesCharStar(s.c_str()); // 显式类型转换
}
二、对于void TakesString(const string& s
,即第二个方法,如果我们已有的数据是const char*
类型,这时虽然不需要显式地进行类型转换,但是编译器会执行拷贝操作来创建一个临时的string
void AlreadyHasCharStar(const char* s) {
TakesString(s); // compiler will make a copy
}
应该怎么办?
谷歌建议利用string_view
来传递字符串参数。
需要注意的是,std::string_view
到C++17标准才支持,否则的话你可以用absl::string_view
void TakesStringView(absl::string_view s); // Abseil C++
void TakesStringView(std::string_view s); // C++17 C++
string_view
可以看成是一个字符串缓存的view,它只保留着这块内存的地址和长度,也因此无法通过string_view
来修改字符串的内容,拷贝时也无需对实际的字符串数据进行拷贝。
从const char*
和const string&
到string_view
的类型转换是隐式的,而且过程中也不会发生数据的拷贝。所以我们可以认为string_view
的构造时间复杂度是O(1)的。
void AlreadyHasString(const string& s) {
TakesStringView(s); // 没有显式的类型转换
}
void AlreadyHasCharStar(const char* s) {
TakesStringView(s); // 没有拷贝
}
由于string_view
不拥有实际的数据,而只是保存一个指向实际数据的指针,所以在使用string_view
时我们需要保证:字符串实际数据的生命周期要长于string_view
。
如果你的API只在一次调用中需要使用这个字符串,而且不需要修改其中的内容,此时使用string_view
不需要有任何顾虑。如果后面还要使用这个字符串,或者需要修改其中内容,可以使用string(my_string_view)
进行显式的类型转换。
在现在的项目中使用string_view
可能不会是一个明智的选择,尤其是当一个字符串在后续使用中确定需要string
或者const char*
类型。所以,如果想使用string_view
,最好是在上游代码中,或者是全新开始的新项目中。
注意
- 传递
string_view
可以大胆按值传递,因为其本身非常小 string_view
不保证NUL-terminated,所以下面这种写法不安全
printf("%s\n", sv.data()); // DON’T DO THIS
- 但可以这样用:
printf("%.*s\n", static_cast<int>(sv.size()), sv.data());
std::cout << "Took '" << sv << "'";
- 大多情况下,我们可以放心地把以
const string&
或const char*
为参数的函数直接改写为接收string_view
类型。唯一的风险在于:如果在其他地方使用了函数的地址,则将导致Build中断,因为生成的函数指针类型将不同。