我一直在阅读关于右值引用和std::move
的内容,我有一个性能问题。对于以下代码:
template<typename T>
void DoSomething()
{
T x = foo();
bar(std::move(x));
}
从性能角度来看,使用右值引用重写这个会更好吗?
template<typename T>
void DoSomething()
{
T&& x = foo();
bar(x);
}
如果我正确理解右值引用,那么就像评论者所指出的那样,就像调用bar(foo())
一样。但可能需要中间值;这样做有用吗?
答案 0 :(得分:4)
使用右值引用/移动语义的最佳方法,在很大程度上,不要太努力。按值返回函数locals。并通过这些函数的值来构造它们:
X foo();
template<typename T>
void DoSomething()
{
T x = foo();
...
如果您是class X
的作者,那么您可能希望确保X
具有有效的移动构造和移动分配。如果X
的拷贝构造函数和拷贝分配已经有效,那么根本就没有其他事情要做:
class X
{
int data_;
public:
// copy members just copy data_
};
如果您发现自己在X
的复制构造函数中分配内存,或者复制赋值,那么您应该考虑编写移动构造函数并移动赋值运算符。尽力做到noexcept
。
虽然规则总是有例外,但一般来说:
不要通过引用返回左值或右值引用,除非您希望客户端可以直接访问非函数局部变量。
不要通过引用,左值或右值捕获返回值。是的,有些情况下它可以为您节省一些东西,而不会让您遇到麻烦。甚至不要考虑这样做。如果您的性能测试非常激励您这样做,那么只能做这样的事情。
你(希望)学习的与左值引用无关的所有事情与rvalue引用一样危险(例如从函数返回一个悬空引用)。
第一个正确的程序。这包括编写广泛且易于运行的单元测试,涵盖API的常见和极端情况。 然后开始优化。当您开始尝试编写极其优化的代码(例如,通过引用返回)之前,您有一个正确且经过良好测试的解决方案,那么不可避免地会编写一个非常脆弱的代码,以至于第一个错误修复程序会进一步破坏代码,但往往是非常微妙的方式。这是一个即使是经验丰富的程序员(包括我自己)也必须一遍又一遍地学习的教训。
尽管我在(4)中说过,即使在第一次写入时,也要注意O(N)性能。即如果你的第一次尝试由于基本的整体设计缺陷或者算法很差而导致它在合理的时间内无法执行其基本功能,那么你需要的不仅仅是新的代码,还需要新的设计。在这篇文章中,我不是在谈论你是否已经通过右值引用获得了回报。我在谈论你是否已经创建了O(N ^ 2)算法,当O(N log N)或O(N)可以完成这项工作时。当需要完成的工作非常简单,或者代码太复杂而无法判断发生了什么时,这很容易做到。
答案 1 :(得分:1)
这里std :: move没有帮助 正如你已经制作了一个对象的副本(尽管elision可能有所帮助)。
template<typename T>
void DoSomething()
{
T x = foo();
bar(std::move(x));
}
这里左值参考没有帮助
因为变量x
是一个命名对象(因此不再是右值引用)。
template<typename T>
void DoSomething()
{
T&& x = foo();
bar(x);
}
最好用:
template<typename T>
void DoSomething()
{
bar(foo());
}
但是如果你必须在本地使用它:
template<typename T>
void DoSomething()
{
T&& x = foo();
// Do stuff with x
bar(std::move(x));
}
虽然我对上述内容并不是100%肯定,但我会喜欢一些反馈。