c++17新特性:string_view

string_view 是C++17所提供的用于处理只读字符串的轻量对象。这里后缀 view 的意思是只读的视图

一些问题

void TakesCharStar(const char* s);             // C convention
void TakesString(const string& s); // Old Standard C++ convention

当需要进行转换(如从const char *stringstringchar *)时发生什么呢?

将字符串转换为const char *时,需要用(高效但不方便)c_str()函数

将const char *转换为字符串时,不需要做任何其他操作(这是好消息);但是将创建临时字符串(方便但效率低),并复制该字符串的内容(这是坏消息)。

在数据传递中减少拷贝是提高性能的最常用办法。在C中指针是完成这一目的的标准数据结构,而在C++中引入了安全性更高的引用类型。所以在C++中若传递的数据仅仅可读,const string&成了C++天然的方式。但这并非完美,从实践上来看,它至少有以下几方面问题:

字符串字面值、字符数组、字符串指针的传递依然要数据拷贝

这三类低级数据类型与string类型不同,传入时编译器要做隐式转换,即需要拷贝这些数据生成string临时对象。const string&指向的实际上是这个临时对象。通常字符串字面值较小,性能损失可以忽略不计;但字符串指针和字符数组某些情况下可能会比较大(比如读取文件的内容),此时会引起频繁的内存分配和数据拷贝,影响程序性能。

substr O(n)复杂度

substr是个常用的函数,好在std::string提供了这个函数,美中不足的时每次都要返回一个新生成的子串,很容易引起性能热点。实际上我们本意不是要改变原字符串,为什么不在原字符串基础上返回呢?

string_view特性

在C++17中引入了string_view,能很好的解决以上两个问题。std::string_view是C++ 17标准中新加入的类,正如其名,它提供一个字符串的视图,即可以通过这个类以各种方法“观测”字符串,但不允许修改字符串。由于它只读的特性,它并不真正持有这个字符串的拷贝,而是与相对应的字符串共享这一空间。即——构造时不发生字符串的复制.同时,你也可以自由的移动这个视图,移动视图并不会移动原定的字符串。

  • 通过调用 string_view 构造器可将字符串转换为 string_view 对象。 string 可隐式转换为 string_view。
  • string_view 是只读的轻量对象,它对所指向的字符串没有所有权。
  • string_view通常用于函数参数类型,可用来取代 const char* 和 const string&。 string_view 代替 const string&,可以避免不必要的内存分配。
  • string_view的成员函数即对外接口与 string 相类似,但只包含读取字符串内容的部分。 string_view::substr()的返回值类型是string_view,不产生新的字符串,不会进行内存分配。 string::substr()的返回值类型是string,产生新的字符串,会进行内存分配。
  • string_view字面量的后缀是 sv。(string字面量的后缀是 s)

代码展示


#include <string_view>
#include <iostream>

int main()
{
using namespace std::literals;

std::string_view s1 = "abc\0\0def";
std::string_view s2 = "abc\0\0def"sv;
std::cout << "s1: " << s1.size() << " \"" << s1 << "\"\n";
std::cout << "s2: " << s2.size() << " \"" << s2 << "\"\n";
}

/*
s1: 3 "abc"
s2: 8 "abc^@^@def"
*/

以上例子能很好看清二者的语义区别,\0对于字符串而言,有其特殊的意义,即表示字符串的结束,字符串视图根本不care,它关心实际的字符个数。

string_view类的实例可以看作是现有字符串缓冲区的“视图”。具体来说,string_view仅由一个指针和一个长度组成,用于标记不是string _view拥有且不能被该视图修改的字符串数据部分。所以,复制string_view是一项浅层的操作:不复制任何字符串数据。

string_view有来自const char *const string&的隐式转换构造函数,并且由于string_view不拷贝,因此进行浅拷贝不产生O(n)内存损失。在传递cosnt string&的情况下,构造函数在O(1)时间进行。在传递const char*的情况下,构造函数会自动调用strlen()(或者你可以使用具有两个参数的string_view构造函数)。

注意事项

因为string_view不拥有数据,所以string_view所指的任何字符串必须具有已知的生命周期,并且必须比string_view本身生命周期更长。这意味着使用string_view进行存储通常是有问题的:你需要一些证据证明基础数据的生命周期将超过string_view。

如果你的API仅需在一次调用中引用字符串数据,而无需修改数据,则接受string_view就足够了。如果以后需要引用数据或需要修改数据,则可以使用string(my_string_view)显式转换为C ++字符串对象。

将string_view添加到现有代码库中并非总是正确的答案:更改参数以通过string_view传递可能效率不高,如果这些参数随后传递给需要字符串或以NUL终止的·const char *·的函数。最好从实用程序代码开始向上使用string_view,或者在启动新项目时保持完全一致。

  • 与其他字符串类型不同,你应该按值传递string_view,就像int或double一样,因为string_view是一个很小的值。
  • string_view不一定是NUL终止的。
  • 你可以输出string_view,就像输出字符串或const char*一样:

大多数情况下,你可以将接受const string&或NUL终止的const char*的现有例程安全的转换为string_view。在执行此操作时遇到的唯一危险是,如果已获取函数的地址,则将导致编译中断,因为生成的函数指针类型将有所不同。