返回值优化或移动?
我不明白当我应该使用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 ); }
我应该用哪个?
专门使用第一种方法:
Foo f() { Foo result; mangle(result); return result; }
这将允许使用移动构造函数(如果有)。 事实上,当允许复制elision时,局部变量可以绑定到return
语句中的右值引用。
你的第二个版本积极禁止复制elision。 第一个版本更好。
所有返回值都已经moved
或优化了,所以不需要显式移动返回值。
编译器可以自动移动返回值(优化拷贝),甚至优化移动!
n3337标准草案 (C ++ 11) 第12.8节 :
当符合某些标准时,即使对象的复制/移动构造函数和/或析构函数具有副作用,也可以允许实现省略类对象的复制/移动构造。 在这种情况下,实现将被忽略的复制/移动操作的来源和目标作为简单地引用同一对象的两种不同方式来处理,并且在两个对象本来会在没有优化的情况下被销毁。这种复制/移动操作的缩写 (称为复制删除)在以下情况下是允许的(可以合并以消除多个副本):
[…]
例如 :
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
被移除的移动构造。 – 结束示例 ]当满足或将满足复制操作的条件时,除了源对象是函数参数,要复制的对象由左值指定的情况下,选择复制的构造函数的重载解析是首先执行好像对象是由右值指定的。 如果重载解析失败,或者如果所选构造函数的第一个参数的类型不是对象类型的右值引用(可能是cv-qualified),则将该对象视为左值,重新执行重载解析。
这很简单。
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
右值引用和forward
通用引用”规则的一个实例,它优先于“永不move
返回值”规则。
如果你正在返回一个局部变量,不要使用move()
。 这将允许编译器使用NRVO,否则,编译器仍然可以执行一个移动(局部变量在return
语句中变成R值)。 在这种情况下使用move()
会简单地禁止NRVO并强制编译器使用移动(如果移动不可用,则复制)。 如果你要返回的不是局部变量,NRVO也不是一个选项,你应该使用move()
(当且仅当)你打算盗取对象。