我不明白何时应该使用std::move
以及何时应该让编译器优化...例如:
using SerialBuffer = vector< unsigned char >;
// let compiler optimize it
SerialBuffer read( size_t size ) const
{
SerialBuffer buffer( size );
read( begin( buffer ), end( buffer ) );
// Return Value Optimization
return buffer;
}
// explicit move
SerialBuffer read( size_t size ) const
{
SerialBuffer buffer( size );
read( begin( buffer ), end( buffer ) );
return move( buffer );
}
我应该使用哪个?
答案 0 :(得分:114)
所有返回值都已经moved
或已优化,因此无需使用返回值显式移动。
允许编译器自动移动返回值(以优化副本),甚至优化移动!
n3337标准草案第12.8节(C ++ 11):
当满足某些条件时,允许省略实现 复制/移动类对象的构造,即使复制/移动 对象的构造函数和/或析构函数具有副作用。在 在这种情况下,实施处理的来源和目标 省略了复制/移动操作,只是两种不同的引用方式 对于同一个对象,该对象的破坏发生在 稍后两个物体被摧毁的时间 没有优化。复制/移动操作的省略, 在下列情况下允许使用 copy elision (可以合并以消除多个副本):
[...]
实施例:
class Thing { public: Thing(); ~Thing(); Thing(const Thing&); }; Thing f() { Thing t; return t; } Thing t2 = f();
这里可以合并elision的标准,以消除对类
Thing
的复制构造函数的两次调用: 将本地自动对象t
复制到临时对象中以获取函数f()
的返回值 并将该临时对象复制到对象t2
中。实际上,构造了本地对象t
可以看作是直接初始化全局对象t2
,并且该对象的破坏将在程序中发生 出口。向Thing
添加移动构造函数具有相同的效果,但它是移动构造 被遗漏的t2
临时对象。 - 结束示例]当满足或将满足复制操作的省略标准时,除了源的事实 object是一个函数参数,要复制的对象由左值,重载决策指定 选择首先执行复制的构造函数,就好像该对象是由rvalue指定的一样。如果过载 分辨率失败,或者所选构造函数的第一个参数的类型不是rvalue引用 对象的类型(可能是cv-qualified),再次执行重载决策,将对象视为 左值。
答案 1 :(得分:97)
专门使用第一种方法:
Foo f()
{
Foo result;
mangle(result);
return result;
}
这将已经允许使用移动构造函数(如果有)。实际上,当允许复制省略时,局部变量可以精确地绑定到return
语句中的右值引用。
您的第二个版本主动禁止复制省略。第一个版本普遍更好。
答案 2 :(得分:42)
这很简单。
return buffer;
如果你这样做,那么NRVO将会发生,或者不会发生。如果没有发生,则buffer
将被移除。
return std::move( buffer );
如果您这样做,则NVRO 将不会发生,buffer
将被移除。
因此,在这里使用std::move
并没有什么好处,而且还有很多东西要失去。
此规则有一个例外:
Buffer read(Buffer&& buffer) {
//...
return std::move( buffer );
}
如果buffer
是左值参考,那么您应该使用std::move
。这是因为引用不符合NRVO的条件,因此如果没有std::move
,它将导致来自左值的副本。
这只是规则“始终move
rvalue引用和forward
通用引用”的实例,它优先于规则“never move
返回值”。
答案 3 :(得分:24)
如果您要返回本地变量,请不要使用move()
。这将允许编译器使用NRVO,如果失败,编译器仍将被允许执行移动(局部变量在return
语句中变为R值)。在该上下文中使用move()
将简单地禁止NRVO并强制编译器使用移动(或者如果移动不可用则复制)。如果您返回的是局部变量以外的其他内容,则无论如何NRVO都不是一个选项,如果(并且仅当)您打算窃取该对象,则应使用move()
。