2

在过去,如果我想的对象A的字符串表示,我会写一些与签名void to_string(const A& a, string& out),以避免额外的副本。这仍然是C++ 11中的最佳实践,包含移动语义和所有?在C返回当地的价值最优途径++ 11

我已经在暗示依托RVO,而是写string to_string(const A& a)其他情况下阅读了若干意见。但RVO不保证发生!那么,作为to_string的程序员,我怎样才能保证字符串不会被不必要地(独立于编译器)复制?

+5

这并不是长时间的最佳实践(甚至在移动之前)。 –

+2

如果RVO无法使用,那么编译器将先尝试移动,如果默认返回到复制。别担心,自然而然地写东西。 – syam

+2

如果你必须保证它,那么你必须使用一个参考。但是,我知道没有没有实现RVO的编译器。如果这样的野兽确实存在,它生成的代码的性能就不太可能重要。无论哪种方式,只需使用RVO。 – Nevin

回答

0

这里是我的反馈和其他资源收集的答案:

通过值直截了当的回报是习惯用法,因为:

    在实践中复制
  • /移动省音将于大部分的时间;
  • 移动ctor将用于后备;
  • 防止副本实际发生不大可能的情况下,是不值得的少可读的代码
  • 传递引用要求已经创建 对象
    • 并不总是可行的(例如,有可能是没有默认构造函数)和
    • 一个初始化太多也必须考虑到,如果问题是性能

但是,如果典型用法预计到是像

std::string s; 
while (i_need_to) 
{ 
    to_string(get_A(), s); 
    process(s); 
    update(i_need_to); 
} 

和如果有问题的类型有一个默认的构造函数*,那么它可能仍然是有意义的传递应该持有引用返回的对象。

*考虑串这里只作为一个例子,但问题和答案可以推广

6

假设在你的函数的代码的形式为:

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);这是荒谬的。为什么整数创建和对象创建如此不同?不应该作为一个程序员关心是否太多副本正在发生的返回值或什么。你只需要简单,明白而且很好理解的方式来做事情。

+3

+1 *“出于性能考虑,您最好花时间升级编译器而不是转向将值返回给引用。“*说得好! :) – Ali

+0

我没有真正担心任何特定的性能情况,也不是我使用一个奇怪的编译器。简单地试图理解,一般来说,这个习语是什么,为什么。我确实对循环展开的性能影响感兴趣,或者为什么++我比i ++更好。当然,一旦我理解了一个推理,我可以理解我只是使用它,而不是每次都仔细考虑所有细节。但是我更喜欢首先了解这些事情,因此问题 – ricab

+1

我从答案,评论和我在别处看到的内容可以理解的是,直接的价值回报是成语,因为:在实践中,复制/移动省略将发生在大多数情况下的时间;此举将在后退时使用;防止实际发生副本不可能的情况是不值得的...麻烦?较少可读代码?究竟是什么?在简单的回报方面重量是多少?因为如果你传递一个非const参数,代码看起来不那么可读,也不是很麻烦... – ricab

4

早在旧时代(1970-1980),你可以非常计数浮点分歧预测算法的性能。

今天不再这样了。然而也有类似的规则,你可以用它来估计今天的表现:

计数前往堆的数量:既new/mallocdelete/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); 
} 

我知道stringcapacity(),而且容量可以在上面循环中回收了许多用途。最糟糕的情况是我每次迭代仍然有1次分配。最好的情况是,第一次迭代将创建足够大的容量来处理所有其他迭代,并且整个循环只会执行1次分配。

事实真相可能存在于最糟糕的情况和最好的情况之间。

最好的API将取决于使用情况下,你认为你的功能将最有可能是。

计数分配估计性能。然后测量你编码的内容。在std::string的情况下,可能会有一个短的字符串缓冲区,可能会影响您的决定。在libc++的情况下,在64位平台上,std::string在存储堆之前将最多存储22个char(加上终止空值)。

+0

伟大的论证,非常感谢,完成了我的理解 – ricab