C++17中的std::string_view:性能与责任

在现代编程语言中,字符串处理是一个核心功能。C++语言在早期版本中对字符串的处理并不尽如人意,尤其是在性能和易用性方面。随着C++17标准的发布,引入了std::string_view,这是一个重要的改进,它提供了一种更高效的方式来处理字符串数据。本文将详细探讨std::string_view的功能、用法以及在实际编程中如何正确使用它。

std::string的局限性

std::string是C++中一个非常常用的字符串类,它提供了对字符数组的封装,使得字符串的存储、修改、迭代和显示变得更加方便。例如,可以轻松地比较字符串的前缀或后缀,或者提取子字符串。但是,std::string在某些情况下也存在一些不足,比如在进行子字符串操作时可能会产生不必要的内存分配。

std::string str = "My str"; std::string prefix = "My "; if (str.compare(0, prefix.size(), prefix) == 0) { std::cout << str.substr(prefix.size()); }

在上面的例子中,substr函数会返回一个新的std::string实例,这在某些情况下是不必要的,因为它并不修改原始字符串。为了避免这种情况,可以直接通过索引访问字符串中的字符。

for (size_t i = prefix.size(); i < str.size(); ++i) { std::cout << str[i]; }

std::string_view的引入

C++17引入了std::string_view,它是一个轻量级的字符串视图,允许查看已经分配的连续内存区域,而不需要为子字符串分配新的std::string实例。这不仅避免了C风格的字符串比较语法,还减少了不必要的内存分配。

std::string str = "My str"; std::string prefix = "My "; std::string_view str_v = str; if (str_v.substr(0, prefix.size()) == prefix) { std::cout << str_v.substr(prefix.size()); }

使用std::string_view,可以更简洁地编写代码,同时提高性能。例如,可以轻松地检查字符串是否以特定的前缀或后缀开始或结束,而不需要进行任何字符串分配。

bool validate(std::string_view str) { std::string start = "lstart", stop = "lstop"; return str.substr(0, start.size()) == start && str.substr(str.size() - stop.size()) == stop; }

std::string_view的工作原理

std::string_view实际上是一个结构体,它包含指向字符缓冲区起始位置的指针和大小信息。这些信息在构造函数中传递,并在substr函数中提取到新实例。从std::string实例构造std::string_view时,实际上是使用std::string::operator basic_string_view,然后从std::string_view构造std::string_view。

std::string_view的进一步应用

std::string_view还可以从char*实例或char*和size_t参数构造。这意味着,如果只需要查看和分析编译时字符串(这些字符串存储在二进制源代码中,因此它们的地址可以用于std::string_view),可以直接将它们分配给std::string_view实例,而不是首先构造std::string实例。

std::string_view str = "My str"; std::string_view prefix = "My "; if (str.substr(0, prefix.size()) == prefix) { std::cout << str.substr(prefix.size()); }

在C++20和C++23中,std::string_view和std::string对象引入了新的成员函数,如starts_with和ends_with(非常适合上述例子),以及C++23中的contains成员函数。

std::string_view str = "My str"; std::string_view prefix = "My "; if (str.starts_with(prefix)) { std::cout << str.substr(prefix.size()); }

constexpr的使用

所有上述函数都可以在constexpr上下文中使用或定义。由于std::string_view不分配任何新数据,它是编译时编程的开放窗口。

constexpr std::string_view str = "My str"; constexpr std::string_view prefix = "My "; if (str.starts_with(prefix)) { std::cout << str.substr(prefix.size()); }

不良实践和潜在问题

std::string_view旨在在分析字符串时提供更好的性能。然而,性能和安全性之间总是存在权衡,当处理std::string_view时,这种权衡尤为明显。

规则#1:永远不要返回std::string_view。返回std::string_view可能会导致不安全的内存访问,因为返回的std::string_view指向的内存在析构函数中被释放,这意味着返回的std::string_view现在指向已释放的内存。

std::string_view func() { std::string str; std::cin >> str; return str; }

规则#2:小心空终止符。如前所述,不推荐使用空终止符,使用std::string_view时应始终牢记这一点。

std::string_view str = "my cool str"; str.remove_prefix(str.find("\n")); str.remove_suffix(str.size() - str.rfind("\n")); std::cout << str; // "cool" - OK std::cout << str.data(); // "cool str" const char* get() { return new char[]{"my new str"}; } { std::string_view str = get(); // Here we can call: delete str.data() str.remove_prefix(1); // 内存泄漏! // delete str.data() // 无效调用。 // 指针不指向分配部分的头部。 }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485