c ++ 11返回值优化还是移动?

时间:2013-07-04 15:19:10

标签: c++ c++11 move return-value-optimization rvo

我不明白何时应该使用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 );
}

我应该使用哪个?

4 个答案:

答案 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()