string_View理解与用法(一)

什么是string_view

当你创建一个将(常量)字符串作为参数的函数时,你有四个选择,你可能知道两个,但不知道另外两个:

void TakesCharStar(const char* s);             // C convention
void TakesString(const string& s);             // Old Standard C++ convention
void TakesStringView(absl::string_view s);     // Abseil C++ convention
void TakesStringView(std::string_view s);      // C++17 C++ convention

当调用者已经有已提供的格式的字符串时即提供的字符串类型完全匹配时(对于第1个函数,调用者提供的字符串类型为:const char*,如:const char *p = "test";  对于第2个函数,调用者提供的字符串类型为const string,如: const string p = "test"; ),前两者各自对应的方法最有效,但是当需要进行转换(如从const char *到string 或 string到char *)时发生什么呢?

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

void AlreadyHasString(const string& s) {
  TakesCharStar(s.c_str());               // explicit conversion
}

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

string有什么缺点?

本节内容主要摘自博客【现代C++】性能控的工具箱之string_view

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

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

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

  • substr O(n)复杂度

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

怎么办

C++17中引入了string_view,能很好的解决以上两个问题。

std::string_view是C++ 17标准中新加入的类,正如其名,它提供一个字符串的视图,即可以通过这个类以各种方法“观测”字符串,但不允许修改字符串。由于它只读的特性,它并不真正持有这个字符串的拷贝,而是与相对应的字符串共享这一空间。即——构造时不发生字符串的复制(具体请参考《详解C++17下的string_view》)。同时,你也可以自由的移动这个视图,移动视图并不会移动原定的字符串。

  • 通过调用 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,它关心实际的字符个数。

Google首选通过stringview接受这样的字符串参数。这是C++17的“pre-adopted”类型,在C++17的构建中,您应该使用std::string_view,在任何不依赖C++17的代码中,您应该使用absl::string_view(Abseil是Google开源的C++库)。

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构造函数)。

void AlreadyHasString(const string& s) {
  TakesStringView(s); // no explicit conversion; convenient!
}

void AlreadyHasCharStar(const char* s) {
  TakesStringView(s); // no copy; efficient!
}

因为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终止的。因此,编写以下内容并不安全:
printf("%s\n", sv.data()); // DON’T DO THIS

以下这篇文章很好说明了上例的不安全:

《C++ string_view 的坑》

但是,下面是好的代码:

printf("%.*s\n", static_cast<int>(sv.size()), sv.data());
  • 你可以输出string_view,就像输出字符串或const char*一样:
std::cout << "Took '" << s << "'";

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值