String_view简介
String_view是C++17引入的一个新特性,它主要解决了以下问题:
1. 减少内存分配和拷贝操作:
- 传统的std::string对象需要动态分配内存并拷贝字符串数据,这会带来性能损耗。
- string_view则仅仅持有一个指向字符串数据的指针,不需要额外的内存分配和拷贝,提高了性能。
2. 避免字符串所有权问题:
- std::string对象需要管理字符串数据的生命周期,容易出现悬垂指针问题。
- string_view不拥有字符串数据,不需要管理其生命周期,避免了所有权问题。
3. 支持各种字符串类型:
- string_view可以适用于std::string、C风格字符串数组,以及其他类型的字符串数据。
- 这使得代码可以更加通用和灵活。
总之,string_view的引入大大提高了C++处理字符串的性能和灵活性,是C++17中一个非常实用的新特性。
以减少内存分配和拷贝操作 进行探讨
举例
高明的开发者或许一眼就能看出问题,在此期间有一次不必要的copy 这大大的浪费了程序的性能消耗,问题就在于 myString 实参传递和形参使用string 并不是引用。
void processString(std::string str) {
// 对字符串str进行一些操作
std::cout << "Processing string: " << str << std::endl;
}
std::string myString = "Hello, world!";
processString(myString); // 需要拷贝myString
在这种情况下,当我们调用processString(myString)时,需要进行以下操作:
创建一个临时的std::string对象,并将myString拷贝到该对象中。
将这个临时对象作为参数传递给processString函数。
在函数内部,还需要再次拷贝这个临时对象。
引入stringview后,代码可读性更强,不需要理会我到底加没加引用。
void processStringView(std::string_view str) {
// 对字符串str进行一些操作
std::cout << "Processing string view: " << str << std::endl;
}
std::string myString = "Hello, world!";
processStringView(myString);
在这种情况下,当我们调用processStringView(myString)时,只需要进行以下操作:
将myString隐式转换为std::string_view类型,这只需要传递一个指针,不需要进行任何拷贝操作。
将这个std::string_view对象传递给processStringView函数。
以避免字符串所有权问题 进行探讨
悬垂引用
举一个简单的例子来解释悬垂引用:
int* getPtr() {
int x = 42;
return &x;
}
int* p = getPtr();
std::cout << *p << std::endl; // 悬垂引用,x已经被销毁
回到string
//先看传统const string &引发的问题 悬垂引用
const std::string & fun(const std::string &str){
cout<<str<<endl; //引用右值 输出 此时生命周期还是存在的
return str; //返回回去
}
auto str = fun("hello word"); //首先这里传递右值 ,接住当前的右值
cout<<str<<endl; //此时该右值已经不存在了,为什么能运行?这是因为C++编译器在某些情况下可以进行特殊的优化,避免了悬垂引用的问题。
哪些优化?
编译器可以执行以下优化:
字符串字面量"hello word"是存储在只读内存区域的,它的生命周期贯穿整个程序的执行。
当我们调用fun("hello word")时,编译器可以将字符串字面量直接传递给fun函数,而不需要创建临时的std::string对象。
因此,当我们在fun函数中返回str时,实际上返回的是对字符串字面量的引用,而不是一个悬垂引用。
在main函数中,auto str = fun("hello word");这行代码,编译器可以将返回的引用直接绑定到str变量上,不需要额外的拷贝操作。
//下列例子 包是悬垂引用,虽然不会立即造成程序问题,为程序稳定埋下伏笔。
auto str = fun(string("hello word"));
那我们新版本stringview的写法呢?
std::string_view fun(std::string_view str) {
cout << str << endl; // 可以正常输出
return str; // 返回string_view,不会引发悬垂引用问题
}
auto str = fun("hello word");
cout << str << endl;
在这种情况下,确实没有产生任何拷贝操作。让我们仔细分析一下:
在fun函数中,我们接受一个std::string_view类型的参数str。
当我们在main函数中调用fun("hello word")时,编译器会自动将字符串字面量"hello word"转换为std::string_view类型,并传递给fun函数。
在fun函数内部,我们直接使用并返回了str参数,也就是对字符串字面量的引用。
在main函数中,我们将fun函数的返回值赋给了auto str。这里,str变量也是对字符串字面量的引用,没有发生任何拷贝操作。
总结:使用string_view会直接对字面量"hello word"进行引用,也就是说不会产生任何的copy.
支持各种字符串类型
第三点就不探讨了,因为在使用string时,也会兼容char,编译器会帮我们做隐式类型转换。
看点示例吧
void printLength(std::string_view str) {
std::cout << "Length: " << str.length() << std::endl;
}
std::string myString = "Hello, world!";
printLength(myString); // 输出: Length: 13
c风格
const char* cstr = "C-style string";
printLength(cstr); // 输出: Length: 14
自定义
class MyString {
public:
const char* data() const { return m_data; }
size_t size() const { return strlen(m_data); }
private:
char m_data[20] = "Custom string";
};
MyString customString;
printLength(customString); // 输出: Length: 13
总结
stringview 好处:可读性更强了,开发者不必理会字符串传递规则,直接使用就能写出高性能的代码,把工作重点放在业务上。
`std::string_view`虽然有很多优点,但也存在一些潜在的缺点,需要开发者注意:
1. 所有权问题:
- `std::string_view`不拥有它所引用的字符串数据,因此如果原始字符串数据在`std::string_view`对象的生命周期结束前被销毁,就会出现悬垂引用的问题。开发者需要谨慎管理字符串数据的生命周期。
2. 线程安全问题:
- 因为`std::string_view`只是持有一个指向字符串数据的指针,如果该字符串数据被并发修改,则`std::string_view`的行为是未定义的。开发者需要确保字符串数据在`std::string_view`的生命周期内保持不变。
3. 缺乏部分功能:
- `std::string_view`缺乏一些`std::string`常见的功能,如字符串修改、格式化等。如果需要这些功能,仍然需要使用`std::string`。
4. 潜在的性能问题:
- 虽然`std::string_view`可以减少内存分配和拷贝操作,但在某些情况下,频繁创建和销毁`std::string_view`对象也可能会影响性能。开发者需要权衡使用`std::string_view`的利弊。
5. 兼容性问题:项目用的c11,则无法使用。