在过去,如果我想要一个对象A
的字符串表示,我会写一些带有签名void to_string(const A& a, string& out)
的东西,以避免额外的副本。这仍然是C ++ 11中的最佳实践,具有移动语义和所有?
我已经阅读了一些关于其他上下文的评论,这些评论建议依赖于RVO,而是编写string to_string(const A& a)
。但RVO并不能保证会发生!那么,作为to_string的程序员,我怎么能保证不会不必要地复制字符串(独立于编译器)?
答案 0 :(得分:6)
假设你的函数中的代码是以下形式:
std::string data = ...;
//do some processing.
return data;
如果elision不可用,则需要调用std::string
的移动构造函数。最糟糕的情况是,你可以从你的内部字符串中移动。
如果您负担不起移动操作的费用,那么您必须将其作为参考传递。
话虽如此......你是否担心编译器无法内联短函数?您是否担心小包装是否会被无法正确优化?编译器不能优化for
循环等的可能性是否打扰了你?您是否考虑if(x < y)
是否比if(x - y < 0)
更快?
如果不是......那你为什么要关心复制/移动省略(“返回值优化”的技术术语,因为它在更多地方使用)?如果您使用的是不支持复制省略的编译器,那么您使用的是可怕的编译器,可能无法支持大量其他优化。出于性能考虑,最好将时间花在升级编译器上,而不是将返回值转换为引用。
防止副本实际发生的不可能的情况不值得......麻烦?代码不太可读?到底是什么?在简单回报方面加权的额外因素是什么?
“额外的事情”就是:
std::string aString = to_string(a);
比这更具可读性:
std::string aString;
to_string(a, aString);
在第一种情况下,立即显然to_string
正在初始化字符串。在第二个,它不是;您必须查找to_string
的签名才能看到它正在使用非const
参考。
第一种情况甚至不是“惯用的”;这就是每个人通常会写它的方式。你永远不会看到to_int(a, someInt)
整数调用;这是荒谬的。为什么整数创建和对象创建如此不同?作为程序员,你不应该不得不关心是否有太多的副本发生了返回值或其他事情。你只需要做简单,明显和易于理解的方式。
答案 1 :(得分:4)
在过去(1970-1980),您可以通过计算浮点除法来预测算法的性能。
今天不再如此。但是,您可以使用类似的规则来估算当前的效果:
计算到堆的行程数:
new/malloc
和delete/free
。
假设:
std::string
to_string(const A& a)
{
std::string s;
// fill it up
return s;
}
std::string s = test();
假设您没有将s
内部重新分配到to_string()
,我会计为1。在将数据放入s
时完成一次分配。我知道std::string
有一个快速(无分配)移动构造函数。因此,RVO是否发生与估计to_string()
的性能无关。在s
之外创建to_string()
时,将有1个分配。
现在考虑:
void
to_string(const A& a, string& out)
{
out = ...
}
std::string s;
to_string(a, s);
正如我所写,它仍然消耗1个内存分配。因此,这与返回值版本的速度大致相同。
现在考虑一个新的用例:
while (i_need_to)
{
std::string s = to_string(get_A());
process(s);
update(i_need_to);
}
根据我们之前的分析,上面每次迭代将进行1次分配。现在考虑一下:
std::string s;
while (i_need_to)
{
to_string(get_A(), s);
process(s);
update(i_need_to);
}
我知道string
有capacity()
,并且该容量可以通过上述循环中的多种用途进行回收。最糟糕的情况是每次迭代我仍然有1个分配。最好的情况是第一次迭代将创建足够大的容量来处理所有其他迭代,并且整个循环只会进行1次分配。
事实可能介于最糟糕和最佳情况之间。
最好的API将取决于您认为您的功能最有可能的用例。
计算分配以估算效果。然后衡量你编码的内容。在std::string
的情况下,可能会有一个短字符串缓冲区,可能会影响您的决定。对于libc++,在64位平台上,std::string
将在到达堆之前存储最多22个char
(加上终止空值)。
答案 2 :(得分:0)
以下是我从反馈和其他资源中收集到的答案:
按价值直接回归是成语,因为:
但是,如果预计典型用法类似于
std::string s;
while (i_need_to)
{
to_string(get_A(), s);
process(s);
update(i_need_to);
}
如果有问题的类型有默认构造函数*,那么传递应该通过引用返回的对象仍然是有意义的。
*仅考虑字符串作为示例,但问题和答案可以概括