简而言之:std::string_view 优于 const std::string& 的主要优点是您可以同时传递 const char* 和 std::string 对象而无需进行复制。正如其他人所说,它还允许您在不复制的情况下传递子字符串,尽管(根据我的经验)这不太重要。
考虑以下(愚蠢的)函数(是的,我知道你可以直接调用 s.at(2)):
char getThird(std::string s)
{
if (s.size() < 3) throw std::runtime_error("String too short");
return s[2];
}
此函数有效,但字符串是按值传递的。这意味着即使我们没有查看所有字符串,也会复制字符串的整个长度,并且它也(通常)会导致动态内存分配。在一个紧密的循环中执行此操作可能非常昂贵。一种解决方案是通过 const 引用传递字符串:
char getThird(const std::string& s);
如果您有一个std::string 变量并将其作为参数传递给getThird,这会更好。但是现在有一个问题:如果你有一个以空结尾的const char* 字符串怎么办?当您调用此函数时,将构造一个临时的std::string,因此您仍然可以获得副本和动态内存分配。
这是另一个尝试:
char getThird(const char* s)
{
if (std::strlen(s) < 3) throw std::runtime_error("String too short");
return s[2];
}
这显然现在适用于const char* 变量。它也适用于std::string 变量,但调用它有点尴尬:getThird(myStr.c_str())。更重要的是,std::string 支持嵌入的空字符,getThird 会将字符串误解为以第一个字符结尾。在最坏的情况下,这可能会导致安全漏洞 - 想象一下,如果该函数被称为 checkStringForBadHacks!
另一个问题是,用旧的空终止字符串而不是std::string 对象及其方便的方法编写函数很烦人。例如,您是否注意到,即使只有前几个字符很重要,此函数也会查看字符串的整个长度?它隐藏在std::strlen 中,它遍历所有字符以寻找空终止符。我们可以将其替换为手动检查前三个字符是否为空,但您会发现这比其他版本要方便得多。
进入std::string_view(或boost::string_view,以前称为boost::string_ref):
char getThird(std::string_view s)
{
if (s.size() < 3) throw std::runtime_error("String too short");
return s[2];
}
这为您提供了您期望从适当的字符串类(如.size())获得的好方法,并且它适用于上述两种情况,以及另一种情况:
- 它适用于
std::string 对象,可以隐式转换为std::string_view 对象。
- 它适用于
const char*以null结尾的字符串,也可以隐式转换为std::string_view对象。
- 这确实有一个潜在的缺点,即构造
std::string_view 需要遍历整个字符串以找到长度,即使使用它的函数从不需要它(就像这里的情况一样)。但是,如果调用者使用 const char* 作为多个函数(或循环中的一个函数)的参数,这些函数采用 std::string_view 对象,它总是可以事先手动构造该对象。这甚至可以提高性能,因为如果该函数确实需要长度,那么它会被预先计算一次并重复使用。
- 正如其他答案所提到的,当您只想传递子字符串时,它也会避免复制。例如,这在解析中非常有用。但是
std::string_view 即使没有这个功能也是合理的。
值得注意的是,在某些情况下,原始函数签名(按值取 std::string)实际上可能比 std::string_view 更好。无论如何,这就是您要复制字符串的地方,例如存储在其他变量中或从函数返回。想象一下这个函数:
std::string changeThird(std::string s, char c)
{
if (s.size() < 3) throw std::runtime_error("String too short");
s[2] = c;
return s;
}
// vs.
std::string changeThird(std::string_view s, char c)
{
if (s.size() < 3) throw std::runtime_error("String too short");
std::string result = s;
result[2] = c;
return result;
}
请注意,这两个都只涉及一个副本:在第一种情况下,当参数s 由传入的任何内容构造时(包括如果它是另一个std::string),这是隐式完成的。在第二种情况下,我们在创建result 时明确地执行此操作。但是 return 语句不做复制,因为使用了移动语义(就像我们做了std::move(result)),或者更有可能使用return value optimisation。
第一个版本可能更好的原因是它实际上可以执行零拷贝,如果调用者移动参数:
std::string something = getMyString();
std::string other = changeThird(std::move(something), "x");
在这种情况下,第一个 changeThird 根本不涉及任何副本,而第二个则涉及。