std::string& vs boost::string_ref

std::string& vs boost::string_ref

本文关键字:string ref boost std vs      更新时间:2023-10-16

如果我使用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根本不涉及任何副本,而第二个则涉及。