何时应该传递 const& std::string 而不是 std::string_view?

41

我理解使用std::string_view的动机;
它有助于避免函数参数中的不必要分配。

例如:
以下程序将从字符串文字创建一个std::string
这会导致不必要的动态分配,因为我们只关心观察字符。

#include <iostream>

void* operator new(std::size_t n)
{
    std::cout << "[allocating " << n << " bytes]\n";
    return malloc(n);
}

void observe_string(std::string const& str){}

int main(){
  observe_string("hello world"); //prints [allocating 36 bytes]
}

使用 string_view 将解决问题:

#include <iostream>
#include <experimental/string_view>

void* operator new(std::size_t n)
{
    std::cout << "[allocating " << n << " bytes]\n";
    return malloc(n);
}

void observe_string(std::experimental::string_view const& str){
}

int main(){
  observe_string("hello world"); //prints nothing
}
这使我有一个问题。
在函数参数中,何时会选择使用const& std::string而不是string_view?
看着std::string_view的接口,好像我可以将所有传递了const&std::string实例替换掉。这方面是否有任何反例呢?std::string_view是否旨在取代用于参数传递的std::string const&

我原本想说:如果函数要将参数存储到离散的std::string内部,那么复制构造比字符串到字符串视图再到字符串往返更便宜。但是我意识到basic_string_view可以记住它是从字符串构造的,并且只需从其to_string()方法返回相同的字符串。 - Sam Varshavchik
1
@SamVarshavchik:如果你要制作一个副本,你需要按值接受参数,这样它就可以被复制或移动构造。 - GManNickG
1
如果你要将参数传递给只接受字符串的函数,比如ifstream的构造函数或者operator+(参见这个问题),那么最好将其作为const string &传递。(我想这只是说“当你真正需要一个string时”的一种复杂的方式。) - Chris Hartman
4个回答

23
何时应该选择使用std::string const& 代替 string_view 作为函数参数?
您是否需要一个以 null 结尾的字符串?如果是,则应该使用 std::string const&,它可以给您这个保证。而 string_view 没有 - 它只是一系列const char
如果您不需要以 null 结尾的字符串,并且不需要拥有数据的所有权,则应该使用 string_view。如果您需要拥有数据的所有权,则可能情况下,按值传递的 stringstring_view 更好。

5
需要澄清的是:需要我的字符串以空字符结尾,这意味着std::string底层的char数组应该以\0结尾,对吗?很难想象在使用std::string时还需要这样做。这就像将C语言与现代C++混合在一起,或者我理解错了吗? - M. Winter
如果需要一个NTBS,我认为他们可以使用const char*作为参数类型。 - cpplearner
8
很容易想象...有很多需要以空字符结尾的字符串的API。 - Barry
是的,这是一个问题,因为许多东西使用std::string::c_str()并假设它将以“空终止符”结尾。 - undefined
@peterk 嗯?std::string::c_str 是以空字符结尾的。 - undefined

11

接受const std::string&而非string_view的一个可能原因是当您想要存储对字符串对象的引用以便稍后更改时。

如果您接受并存储string_view,则当string内部缓冲区重新分配时,它可能会变得无效。

如果您接受并存储对字符串本身的引用,则不会出现该问题,只要该对象存在(您可能想删除r-value引用重载,以避免临时值的明显问题)。


4

Andrei Alexandrescu曾经说过:"没有工作比有一些工作更好"。因此,在这种情况下应该使用const std::string&。因为std::string_view仍然涉及一些工作(复制指针和长度)。

当然,常量引用仍可能具有复制指针的成本;这几乎等同于std::string_view将要执行的操作。但是,std::string_view还需要进行额外的工作,即复制长度。

理论上如此,但在实践中,人们更喜欢使用基准测试来推断性能



15
如果使用字符串字面量调用函数,那么 const string& 版本将会做更多的工作。 - juanchopanza
1
虽然这并不是很重要,但对于一个string_view来说,它将会有两个指针或者一个指针和一个长度。因此,工作量将会正好翻倍。更重要的是要考虑16字节是否能够与其他参数一起适应CPU寄存器(相对于单个指针的8字节);而且,正如@juanchopanza所说,用户最终都会扫描他们的字符串。 - Hunter Kohler

3

虽然这不是你要求的内容,但有时为了性能原因,你需要按值传递std::string而不是std::string_view。这种情况发生在你需要在检查字符串之前修改它的情况下:

bool matches(std::string s)
{
  make_upper_case(s);
  return lib::test_if_matches(s);
}

您需要在某处使用可变字符串,因此可以将其声明为函数参数。如果您将其更改为 std::string_view,并且有人将 std::string 传递给函数 matches(),则首先会将 string 转换为 string_view,然后再将 string_view 转换为 string,从而导致分配两次。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接