我最近听了Herb Sutter的一个演讲,他认为通过const &传递std::vector和std::string的原因已经基本消失了。他建议现在最好编写如下这样的函数:

std::string do_something ( std::string inval )
{
   std::string return_val;
   // ... do stuff ...
   return return_val;
}

我知道return_val在函数返回时将是一个右值,因此可以使用move语义返回,这非常便宜。然而,inval仍然比引用(通常实现为指针)的大小大得多。这是因为std::string有各种组件,包括指向堆的指针和用于短字符串优化的成员char[]。所以在我看来,通过引用传递仍然是一个好主意。

谁能解释一下赫伯为什么会这么说?


当前回答

除非你真的需要一个副本,否则使用const &仍然是合理的。例如:

bool isprint(std::string const &s) {
    return all_of(begin(s),end(s),(bool(*)(char))isprint);
}

如果你改变它,以按值获取字符串,那么你最终会移动或复制参数,这是没有必要的。复制/移动不仅成本更高,而且还会带来新的潜在失败;复制/移动可能会抛出异常(例如,复制期间的分配可能会失败),而引用现有值则不会。

如果你确实需要一个副本,那么通过值传递和返回通常是(总是?)最好的选择。事实上,在c++ 03中我通常不会担心这个问题,除非你发现额外的副本实际上会导致性能问题。复制省略在现代编译器上似乎相当可靠。我认为人们的怀疑和坚持,你必须检查你的编译器支持RVO的表,现在大部分已经过时了。


简而言之,c++ 11在这方面并没有真正改变任何东西,除了那些不相信复制省略的人。

其他回答

IMO使用c++引用std::string是一个快速而简短的局部优化,而使用传递值可能是(或不是)一个更好的全局优化。

所以答案是:这取决于环境:

如果你把所有的代码从外部写到内部函数,你知道代码是做什么的,你可以使用引用const std::string &。 如果您编写库代码,或者在传递字符串的地方大量使用库代码,那么通过信任std::string复制构造函数行为,您可能会获得更多全局意义上的好处。

string不是普通旧数据(POD),它的原始大小不是最相关的东西。例如,如果传入的字符串超过SSO的长度,并且分配在堆上,我希望复制构造函数不复制SSO存储。

推荐这样做的原因是,inval是从参数表达式构造的,因此总是被适当地移动或复制——假设您需要参数的所有权,这不会造成性能损失。如果不这样做,const引用仍然是更好的方法。

问题是“const”是一个非粒度限定符。“const string ref”通常的意思是“不要修改这个字符串”,而不是“不要修改引用计数”。在c++中,根本没有办法说哪些成员是“const”。它们要么都是,要么都不是。

为了解决这个语言问题,STL可以允许“C()”在你的例子中做一个移动语义复制,并在引用计数(可变)方面尽责地忽略“const”。只要它是指定好的,这就可以了。

因为STL没有,我有一个const_cast <>的字符串版本,去掉引用计数器(没有办法在类层次结构中追溯一些可变的东西),并且-你瞧-你可以自由地传递cmstring作为const引用,并在深层函数中复制它们,一整天,没有泄漏或问题。

由于c++在这里没有提供“派生类的const粒度”,编写一个好的规范并创建一个新的“const可移动字符串”(cmstring)对象是我见过的最好的解决方案。

除非你真的需要一个副本,否则使用const &仍然是合理的。例如:

bool isprint(std::string const &s) {
    return all_of(begin(s),end(s),(bool(*)(char))isprint);
}

如果你改变它,以按值获取字符串,那么你最终会移动或复制参数,这是没有必要的。复制/移动不仅成本更高,而且还会带来新的潜在失败;复制/移动可能会抛出异常(例如,复制期间的分配可能会失败),而引用现有值则不会。

如果你确实需要一个副本,那么通过值传递和返回通常是(总是?)最好的选择。事实上,在c++ 03中我通常不会担心这个问题,除非你发现额外的副本实际上会导致性能问题。复制省略在现代编译器上似乎相当可靠。我认为人们的怀疑和坚持,你必须检查你的编译器支持RVO的表,现在大部分已经过时了。


简而言之,c++ 11在这方面并没有真正改变任何东西,除了那些不相信复制省略的人。

这在很大程度上取决于编译器的实现。

然而,这也取决于你使用什么。

让我们考虑下一个函数:

bool foo1( const std::string v )
{
  return v.empty();
}
bool foo2( const std::string & v )
{
  return v.empty();
}

为了避免内联,这些函数在单独的编译单元中实现。然后: 1. 如果将一个字面值传递给这两个函数,将不会看到性能上的太大差异。在这两种情况下,都必须创建一个字符串对象 2. 如果传递另一个std::string对象,foo2将优于foo1,因为foo1将进行深度复制。

在我的PC上,使用g++ 4.6.1,我得到了这些结果:

参考变量:1000000000次迭代——>时间流逝:2.25912秒 变量值:1000000000次迭代—>时间流逝:27.2259秒 参考文字:100000000次迭代——>时间流逝:9.10319秒 字面值:100000000次迭代——>时间流逝:8.62659秒