在过去,如果我想的对象A
的字符串表示,我会写一些与签名void to_string(const A& a, string& out)
,以避免额外的副本。这仍然是C++ 11中的最佳实践,包含移动语义和所有?在C返回当地的价值最优途径++ 11
我已经在暗示依托RVO,而是写string to_string(const A& a)
其他情况下阅读了若干意见。但RVO不保证发生!那么,作为to_string的程序员,我怎样才能保证字符串不会被不必要地(独立于编译器)复制?
在过去,如果我想的对象A
的字符串表示,我会写一些与签名void to_string(const A& a, string& out)
,以避免额外的副本。这仍然是C++ 11中的最佳实践,包含移动语义和所有?在C返回当地的价值最优途径++ 11
我已经在暗示依托RVO,而是写string to_string(const A& a)
其他情况下阅读了若干意见。但RVO不保证发生!那么,作为to_string的程序员,我怎样才能保证字符串不会被不必要地(独立于编译器)复制?
这里是我的反馈和其他资源收集的答案:
通过值直截了当的回报是习惯用法,因为:
但是,如果典型用法预计到是像
std::string s;
while (i_need_to)
{
to_string(get_A(), s);
process(s);
update(i_need_to);
}
和如果有问题的类型有一个默认的构造函数*,那么它可能仍然是有意义的传递应该持有引用返回的对象。
*考虑串这里只作为一个例子,但问题和答案可以推广
假设在你的函数的代码的形式为:
std::string data = ...;
//do some processing.
return data;
那么这是需要调用std::string
的移动构造函数,如果省音不可用。所以最坏的情况是,你可以从内部字符串中移出。
如果您不能负担一个移动操作的成本,那么你就必须把它作为一个参考。
这就是说......你担心编译器不能内联短功能吗?你是否担心小型包装纸是否不会被正确优化?编译器没有优化for
循环等的可能性会打扰你吗?你认为if(x < y)
是否比if(x - y < 0)
更快?
如果不是......那你为什么在乎复制/移动省音(技术术语为“返回值优化”,因为它在更多的地方使用比)?如果您使用的编译器不支持复制elision,那么您使用的编译器恐怕不能支持大量的其他优化。出于性能的考虑,最好花时间升级编译器,而不是将返回值转换为引用。
防止实际发生副本不可能的情况是不值得...麻烦?较少可读代码?究竟是什么?在简单的回报方面重量是多少?
“额外的东西” 是这样的:
std::string aString = to_string(a);
比这更易读:
std::string aString;
to_string(a, aString);
在第一种情况下,立即明显,to_string
正在初始化一个字符串。第二,它不是;你必须查看to_string
的签名,看看它是否参考非const
参考。
第一个案例甚至不是“惯用”;这就是每个人通常会写它的方式。你永远不会看到一个整数的调用to_int(a, someInt)
;这是荒谬的。为什么整数创建和对象创建如此不同?不应该作为一个程序员关心是否太多副本正在发生的返回值或什么。你只需要简单,明白而且很好理解的方式来做事情。
+1 *“出于性能考虑,您最好花时间升级编译器而不是转向将值返回给引用。“*说得好! :) – Ali
我没有真正担心任何特定的性能情况,也不是我使用一个奇怪的编译器。简单地试图理解,一般来说,这个习语是什么,为什么。我确实对循环展开的性能影响感兴趣,或者为什么++我比i ++更好。当然,一旦我理解了一个推理,我可以理解我只是使用它,而不是每次都仔细考虑所有细节。但是我更喜欢首先了解这些事情,因此问题 – ricab
我从答案,评论和我在别处看到的内容可以理解的是,直接的价值回报是成语,因为:在实践中,复制/移动省略将发生在大多数情况下的时间;此举将在后退时使用;防止实际发生副本不可能的情况是不值得的...麻烦?较少可读代码?究竟是什么?在简单的回报方面重量是多少?因为如果你传递一个非const参数,代码看起来不那么可读,也不是很麻烦... – ricab
早在旧时代(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();
我算1个新的,假设你不重新分配s
内部to_string()
。在您将数据放入s
时完成一次分配。我知道std::string
有一个快速(无分配)的移动构造函数。因此,RVO是否发生与估计to_string()
的表现无关。在to_string()
之外创建s
将会有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
(加上终止空值)。
伟大的论证,非常感谢,完成了我的理解 – ricab
这并不是长时间的最佳实践(甚至在移动之前)。 –
如果RVO无法使用,那么编译器将先尝试移动,如果默认返回到复制。别担心,自然而然地写东西。 – syam
如果你必须保证它,那么你必须使用一个参考。但是,我知道没有没有实现RVO的编译器。如果这样的野兽确实存在,它生成的代码的性能就不太可能重要。无论哪种方式,只需使用RVO。 – Nevin