Tip of the Week 1 string_view
参考链接:abseil.io/tips/1
什么是string_view,为什么需要string_view
当你在把一个字符串(或者字符串常量)作为一个函数参数的时候,通常有三种选择:前面两种你肯定是知道的,剩下的你可能没有意识到:
1 | // C Convention |
当调用者传入字符串格式的参数来调用函数,前面两种方式是可以正常工作的,但是在这个过程中是如何做转化的呢?无论是从const char* 到 std::string还是从std::string到const char*?
调用者需要把std::string转化为const char*,虽然说效率很高,但是还是需要调用c_str()函数,不是很方便:
1 | void AlreadyHasString(const std::string& s) { |
在下面的代码中,调用者需要把const char*转化为std::string虽然说在代码上不需要做额外的事情,但是在转化的过程中,会创建一个临时的字符串,并且拷贝到string对象中:
1 | void AlreadyHasCharStar(const char* s) { |
该怎么做呢?
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 | void AlreadyHasString(const std::string& s) { |
因为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的值,就像int和double一样,因为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的构造函数和不是很重要的析构函数,所以在静态和全局变量使用的时候需要记住这一点。










