背景
在进行参数传递时,当数据占用的内存比较大是,减少数据的拷贝可以有效提升程序的性能。在C语言中使用的是指针,在C++中使用了安全性更高的引用类型。所以C++中const std::string&成为了传递只读数据的不二方式。但是从实践来看,也存在一些问题:
- 字符串字面值、字符数组、字符串指针的传递仍要数据拷贝
这三类低级数据类型与string
类型不同,传入时,编译器需要做隐式转换,即需要拷贝这些数据生成临时string
对象。const string&
指向的实际上是这个临时对象。通常字符串字面值较小,性能损耗可以忽略不计;但字符串指针和字符数组某些情况下可能会比较大(比如读取文件的内容),此时会引起频繁的内存分配和数据拷贝,会严重影响程序的性能。 substr
O(n)复杂度
这是一个特别常用的函数,好在std::string
提供了这个函数,美中不足的是其每次都返回一个新生成的子串,很容易引起性能热点。实际上我们本意并不是要改变原字符串,为什么不在原字符串基础上返回呢?
在c++ 17中,c++标准库采用了一个特殊的字符串类std::string_view,它允许我们处理字符串之类的字符序列,该类型不会为数据分配存储空间,而且该数据类型只能用来读。该数据类型可通过{数据的起始指针,数据的长度}
两个元素表示,实际上该数据类型的实例不会具体存储原数据,仅仅存储指向的数据的起始指针和长度,所以这个开销是非常小的。
使用这样的string视图既便宜又快捷(通过值传递string_view总是便宜的)。但是,它也有潜在的危险,因为与原始指针类似,在使用string_view时,由程序员来确保引用的字符序列仍然有效)。
string
类重载了string
到string_view
的转换操作符:operator std::basic_string_view<CharT, Traits>() const noexcept;
所以,string_view foo(string("abc"))
实际执行了两步操作:
string("abc")
转换为string_view
对象astring_view
使用对象本篇文章从string_view
引入的背景.
std::string_view使用陷阱
1. string_view
范围内的字符可能不包含\0
#include <iostream>
#include <string_view>
int main() {
std::string_view str{"abc", 1};
std::cout << str.data() << std::endl;
return 0;
}
输出结果为abc。这是因为字符串相关的函数都有一条兼容C的约定:\0
代表字符串的结尾。上面的程序打印从开始到字符串结束的所有字符,虽然str
包含的有效字符是a
,但cout
认\0
。好在这块内存空间有合法的字符串结尾符,如果str
指向的是一个没有\0
的字符数组,程序很有可能会出现内存问题,所以我们在将string_view
类型的数据传入接收字符串的函数时要非常小心。
2.从[const] char*
构造string_view
对象时间复杂度O(n)
这是因为获取字符串的长度需要从头开始遍历。如果对[const] char*
类型仅仅是一些O(1)
的操作,相比直接使用[const] char*
,转为string_view
是没有性能优势的。只不过是相比const string&
,string_view
少了拷贝的损耗。实际上我们完全可以用[const] char*
接收所有的字符串,但这个类型太底层了,不便使用。在某些情况下,我们转为string_view
可能仅仅是想用其中的一些函数,比如substr
。
3.string_view
指向的内容的生命周期可能比其本身短string_view
并不拥有其指向内容的所有权,用Rust的术语来说,它仅仅是暂时borrow
(借用)了它。如果拥有者提前释放了,你还在使用这些内容,那会出现内存问题,这跟悬挂指针
(dangling pointer)或悬挂引用(dangling references)很像。Rust专门有套机制在编译时分析变量的生命期,保证borrow
的资源在使用期间不会被释放,但C++没有这样的检查,需要人工保证。下面列出一些典型的问题情况:
//情况一
std::string_view sv = std::string{"hello world"};
//情况二
string_view foo() {
std::string s{"hello world"};
return string_view{s};
}
//情况三
auto id(std::string_view sv) { return sv; }
int main() {
std::string s = "hello";
auto sv = id(s + " world");
}