的std :: string&安培; vs boost :: string_ref

时间:2016-09-01 06:33:25

标签: c++ c++11 boost

如果我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,使用移动语义,上面文章中提到的复制操作不会发生。那么哪一个更有效率?

5 个答案:

答案 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根本不涉及任何副本,而第二个{...}}。