199

我不明白什么时候应该使用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

4 回答 4

137

只使用第一种方法:

Foo f()
{
  Foo result;
  mangle(result);
  return result;
}

已经允许使用移动构造函数(如果有的话)。事实上,return当允许复制省略时,局部变量可以精确地绑定到语句中的右值引用。

您的第二个版本积极禁止复制省略。第一个版本普遍更好。

于 2013-07-04T15:27:46.560 回答
129

所有返回值都已经moved或已优化,因此无需显式移动返回值。

允许编译器自动移动返回值(优化副本),甚至优化移动!

n3337 标准草案(C++11) 的第 12.8 节:

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标简单地视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象本应被删除的较晚时间。在没有优化的情况下销毁。这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以结合起来消除多个副本):

[...]

示例

class Thing {
public:
Thing();
   ~Thing();
   Thing(const Thing&);
};

Thing f() {
   Thing t;
   return t;
}

Thing t2 = f();

这里可以结合省略的标准来消除对类的复制构造函数的两次调用Thing:将本地自动对象复制t到临时对象中以获取函数的返回值,f() 以及将临时对象复制到 objectt2中。实际上,本地对象的构造t 可以看作是直接初始化全局对象t2,并且该对象的销毁将在程序退出时发生。添加移动构造函数 toThing具有相同的效果,但t2省略了从临时对象到的移动构造。—结束示例]

当满足或将满足复制操作的省略标准时,除了源对象是函数参数,并且要复制的对象由左值指定之外,选择复制的构造函数的重载决策是首先执行好像对象是由右值指定的。如果重载决议失败,或者如果所选构造函数的第一个参数的类型不是对对象类型的右值引用(可能是 cv 限定的),则再次执行重载决议,将对象视为左值。

于 2013-07-04T15:27:40.773 回答
74

这很简单。

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返回值”。

* 从 C++20 开始,可以忘记此异常。现在,语句中的右值引用return已隐式移出。

于 2015-03-18T17:27:49.250 回答
31

如果要返回局部变量,请不要使用move(). 这将允许编译器使用 NRVO,否则,编译器仍将被允许执行移动(局部变量成为return语句中的 R 值)。在这种情况下使用move()只会禁止 NRVO 并强制编译器使用移动(如果移动不可用,则使用副本)。如果您返回的不是局部变量,那么 NRVO 无论如何都不是一个选项,您应该move()在(且仅当)您打算窃取该对象时使用。

于 2013-07-04T15:27:44.740 回答