std::string& vs boost::string_ref
std::string& vs boost::string_ref
如果我使用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 以来,使用移动语义,上面文章中提到的复制操作不会发生。那么,哪一个更有效?
> string_ref
(或最近的 Boost 和 C++17 中的string_view
(的用例是子字符串引用。
这种情况
- 源字符串恰好是
std::string
- 并引用源字符串的完整长度
是一种(非典型(特例,它确实类似于std::string const&
。
另请注意,对string_ref
的操作(如sref.substring(...)
(会自动返回更多string_ref
对象,而不是分配新的std::string
。
我从未使用过它,因为在我看来,它的目的是提供一个类似于std::string
的接口,但不必分配一个字符串进行操作。以extract_part()
为例:它被赋予一个硬编码的C数组"ABCDEFG"
,但是因为初始函数取std::string
所以发生了分配(std::string
将有自己的"ABCDEFG"
版本(。使用 string_ref
,不发生分配,它使用对初始"ABCDEFG"
的引用。约束是字符串是只读的。
这个答案使用新名称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
时需要小心,它所引用的内存在其整个生命周期中仍然有效。
坚持std::string
没关系,但boost::string_ref
也支持const char*
。也就是说,您是否打算仅使用 std::string
调用字符串处理函数foo
?
void foo(const std::string&);
foo("won't work"); // no support for `const char*`
由于boost::string_ref
可以从const char*
构造,因此它更灵活,因为它适用于const char*
和std::string
。
提案 N3442 可能会有所帮助。
简而言之: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
,这将更好地工作。但是现在有一个问题:如果你有一个以 null 结尾的 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
!
另一个问题是,用旧的以 null 结尾的字符串编写函数而不是使用其方便的方法std::string
对象很烦人。例如,您是否注意到此函数会查看字符串的整个长度,即使只有前几个字符很重要?它隐藏在 std::strlen
中,它会遍历所有字符以查找空终止符。我们可以用手动检查前三个字符不为 null 来替换它,但您可以看到这比其他版本方便得多。
步入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 语句不做复制,因为使用 move 语义(好像我们做了std::move(result)
(,或者更可能使用返回值优化。
第一个版本可以更好的原因是,如果调用方移动参数,它实际上可以执行零副本:
std::string something = getMyString();
std::string other = changeThird(std::move(something), "x");
在这种情况下,第一个changeThird
根本不涉及任何副本,而第二个则涉及。
- 对RValue对象调用的LValue ref限定成员函数
- 将Ref对象作为类成员
- cppcheck在const std::string[]上引发警告
- 将std::string传递给WriteConsole API
- 为什么我的 std::ref 无法按预期工作?
- 为std::string的某个索引赋值
- std中有类似find_last_of的函数,而string中没有
- 使用 std::string () const 函数启动线程或未来
- 使用char类型将decimal转换为string,将string转换为decimal
- 迭代和比较映射<字符串、矢量<string>> c++ 中的值
- 当我们进行一些操作时,应该使用什么'std::string'或'std::stringstream'?
- 如何将 Eigen::Ref 与 pybind11 一起使用?
- 将向量解析<string>为字符串
- 'string.assign(string.data(), 5)' 是明确定义的还是 UB?
- 如何@ref同一方法的不同变体?
- 如何更改大小(std::string)
- "string.h"在构建适用于iOS的qt应用程序中找不到消息
- C++:如何将 unix 时间的字符串转换为 *tm?(使用时间错误:"cannot convert 'String' to 'tm*' ")
- 如何 SWIG std::string& to C# ref string
- 在 GCC 4.x / C++11 中是否对 std::string ref 计数