如果我boost::string_ref
使用std::string&
,那会不会有问题?我的意思是,在处理字符串时,使用boost::string_ref
比std版本更有效吗?我没有真正得到这里提供的解释:http://www.boost.org/doc/libs/1_61_0/libs/utility/doc/html/string_ref.html。让我感到困惑的是std::string
也是一个仅指向已分配内存的句柄类,而且由于c ++ 11,使用移动语义,上面文章中提到的复制操作不会发生。那么哪一个更有效率?
答案 0 :(得分:5)
string_ref
(或最近的Boost和C ++ 17中的string_view
)的用例适用于子字符串引用。
的情况
std::string
是一个(典型的)特殊情况,它确实类似于std::string const&
。
注意也 string_ref
上的操作(如sref.substring(...)
)会自动返回更多string_ref
个对象,而不是分配新的std::string
。< / p>
答案 1 :(得分:4)
我从来没有用它,在我看来它的目的是提供类似于std::string
的接口,但不必为操作分配字符串。以extract_part()
给出的示例:给出一个硬编码的C数组"ABCDEFG"
,但因为初始函数需要std::string
,所以会发生std::string
的分配自己的"ABCDEFG"
版本。使用string_ref
时,不会发生分配,它会使用对初始"ABCDEFG"
的引用。约束条件是字符串是只读的。
答案 2 :(得分:2)
此回答使用新名称string_view
表示与string_ref
相同。
真正令我困惑的是
std::string
也是一个仅指向已分配内存的句柄类
string
分配,拥有和管理自己的内存。 string_view
是已分配的某些内存的句柄。内存由其他机制管理,与string_view
无关。
如果您已经有一些文本数据,例如在char
数组中,则构造string
所涉及的额外内存分配可能是多余的。 string_view
可能更有效,因为它允许您直接对char
数组中的原始数据进行操作。但是,它不允许修改数据; string_view
不允许非const
访问,因为它不拥有所引用的数据。
从c ++ 11开始,使用移动语义,上面文章中提到的复制操作不会发生。
您只能从准备丢弃的对象移动。复制仍然有用,在许多情况下是必要的。
本文中的示例构造了两个新的string
(不是副本),并构造了现有string
的两个副本。在C ++ 98中,RVO已经可以省略副本而没有移动语义,因此它们并不是什么大问题。通过使用string_view
,它可以避免构建两个新的string
。移动语义在这里无关紧要。
在对extract_part("ABCDEFG")
的调用中,构造了string_view
,它引用了由字符串文字表示的char
数组。在这里构造string
将涉及内存分配和char
数组的副本。
在对bar.substr(2,3)
的调用中,构建了string_view
,它引用了第一个string_view
已经引用的部分数据。在这里使用string
将涉及另一个内存分配和部分数据的复制。
那么,哪一个效率更高?
这有点像询问锤子是否比螺丝刀更有效。它们用于不同的目的,因此取决于你想要实现的目标。
使用string_view
时,您需要注意的是,它所引用的内存在其生命周期内保持有效。
答案 3 :(得分:1)
如果您坚持使用std::string
并不重要,但boost::string_ref
也支持const char*
。也就是说,您打算仅使用foo
调用字符串处理函数std::string
吗?
void foo(const std::string&);
foo("won't work"); // no support for `const char*`
由于boost::string_ref
可以从const char*
构建,因此它更灵活,因为它适用于const char*
和std::string
。
提案N3442可能会有所帮助。
答案 4 :(得分:0)
简而言之: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
根本不涉及任何副本,而第二个{...}}。