参考链接:abseil.io/tips/1

什么是string_view,为什么需要string_view

当你在把一个字符串(或者字符串常量)作为一个函数参数的时候,通常有三种选择:前面两种你肯定是知道的,剩下的你可能没有意识到:

1
2
3
4
5
6
7
8
9
// C Convention
void TakesCharStar(const char* s);

// Old Standard C++ convention
void TakesString(const std::string& s);

// string_view C++ conventions
void TakesStringView(absl::string_view s); // Abseil
void TakesStringView(std::string_view s); // C++17

当调用者传入字符串格式的参数来调用函数,前面两种方式是可以正常工作的,但是在这个过程中是如何做转化的呢?无论是从const char*std::string还是从std::stringconst char*?

调用者需要把std::string转化为const char*,虽然说效率很高,但是还是需要调用c_str()函数,不是很方便:

1
2
3
void AlreadyHasString(const std::string& s) {
TakesCharStar(s.c_str()); // explicit conversion
}

在下面的代码中,调用者需要把const char*转化为std::string虽然说在代码上不需要做额外的事情,但是在转化的过程中,会创建一个临时的字符串,并且拷贝到string对象中:

1
2
3
void AlreadyHasCharStar(const char* s) {
TakesString(s); // compiler will make a copy
}

该怎么做呢?

Google建议选择使用string_view来处理这种字符串参数。标准C++17是支持std::string_view的,在这之前可以使用absl::string_view,但是需要注意的是,他们不是完全兼容的,不能直接做替换。

一个string_view实例可以看作是一个字符串缓存的描述。明确的说,一个string_view包含了一个指向字符串buffer的指针和字符串长度值,也就是说这个string_view不是这个字符串的所有者并且不能修改字符串内容。因此,复制一个string_view是一个很轻量级的操作:没有字符串数据拷贝!

string_view有一个隐式的构造函数传入const char*或者const std::string&,由于string_view没有做拷贝的动作,所以在内存空间消耗为O(n)。当传入的是一个const std::string&,构造函数时间消耗为O(1),当传入的是一个const char*类型时,构造函数会调用strlen()来获取字符串长度。

1
2
3
4
5
6
7
void AlreadyHasString(const std::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是可以的。如果还需要修改字符串数据,可以隐式的使用std::string(my_string_view)来转化为一个C++的string对象。

在一个现有项目的代码中添加string_view往往不是一个很好的方案,在函数的参数中把std::string替换为string_view往往会提升效率,但是最好在项目初期就考虑使用string_view

一些额外需要注意的地方

  • 不像其他的字符串类型,在函数传递的时候应该直接传入string_view的值,就像intdouble一样,因为string_view是个很小的值。
  • string_view设置为const只会影响string_view对象本身是否可以修改,不会影响string_view指向的真正字符串数据是否可以修改。这和const char*不能用来修改字符数据的方式是一样的,即使指针本身可以修改。
  • 对于函数参数,不要再函数中不要使用const来声明string_view参数。
  • string_view不一定是以空字符结尾的,因此下面的写法是不安全的:
      `printf("%s\n", sv.data()); // DON’T DO THIS`
    
    用一下方式代替:
      `absl::PrintF("%s\n", sv);`
    
  • 使用如下方式打印string_view日志
    std::cout << "Took '" << s << "'";
  • 在大多数情况下把const std::string&或者以空结尾的字符串转化为string_view都是可以接受的。唯一有风险的是当被用作函数指针的时候。
  • string_view有一个constexpr的构造函数和不是很重要的析构函数,所以在静态和全局变量使用的时候需要记住这一点。