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

elv*_*kaj 166 c++ move return-value-optimization rvo c++11

我不明白何时应该使用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 );
}
Run Code Online (Sandbox Code Playgroud)

我应该使用哪个?

Jam*_*rey 117

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

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

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

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

[...]

示例:

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

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

Thing t2 = f();
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我并不是特别喜欢整个"编译器可以做X"的论点.这个问题不需要求助于任何编译器.这纯粹是关于语言.关于"一招"是否发生,没有任何"可选"或含糊的说法.语言非常清楚哪种构造函数参数可以绑定到返回值(这是一个xvalue); 其余的重载解决方案. (14认同)
  • 这不是编译器可以做什么,而是主要编译器*做的事情.明确地移动事物可能会妨碍编译器执行操作甚至比移动更好.任何足够高级以允许您显式移动的编译器几乎肯定都足够高级以自动移动返回值 - 因为与您可能想要显式移动的其他情况不同,返回值非常容易被编译器检测为一个好地方优化(因为任何返回都可以保证在返回的函数中不再使用该值). (4认同)
  • "_所有返回值已被移动或已优化out_"如果类型不匹配则不匹配:https://groups.google.com/a/isocpp.org/forum/#!msg/std-proposals/Tc1p52jg-Y/ntMVTa0F38cJ (4认同)
  • @Damon:好吧,排序.编译器*可以*移动返回值(并保存副本),但它们通常不会.相反,他们尽可能使用copy-ellison,这样可以保存副本**和**移动.它们只是直接分配给接收函数结果的变量,而不是返回并稍后分配的临时变量.手动移动变量永远不会比编译器更好,并且通常稍微(略微)差一些.编译器*在移动语义上退回*,但在可能的情况下宁愿使用RVO.至少,这是我的理解. (3认同)

Ker*_* SB 107

仅使用第一种方法:

Foo f()
{
  Foo result;
  mangle(result);
  return result;
}
Run Code Online (Sandbox Code Playgroud)

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

您的第二个版本主动禁止复制省略.第一个版本普遍更好.

  • 即使禁用复制省略(“-fno-elide-constructors”),移动构造函数也会被调用。 (5认同)

Okt*_*ist 51

这很简单.

return buffer;

如果你这样做,那么NRVO将会发生,或者不会发生.如果没有发生,那么buffer将被移除.

return std::move( buffer );

如果你这样做,那么NVRO 将不会发生,buffer并将被移除.

因此,std::move在这里使用并没有什么好处,而且还有很多损失.

这条规则有一个例外:

Buffer read(Buffer&& buffer) {
    //...
    return std::move( buffer );
}
Run Code Online (Sandbox Code Playgroud)

如果buffer是右值参考,那么您应该使用std::move.这是因为引用不符合NRVO的条件,因此如果没有std::move它将导致左值的副本.

这只是规则"始终是movervalue引用和forward通用引用" 的实例,它优先于规则"永远move不是返回值".

  • 非常重要的例外,谢谢.刚刚在我的代码中遇到过这个问题. (6认同)

Ada*_*son 26

如果要返回局部变量,请不要使用move().这将允许编译器使用NRVO,如果失败,编译器仍将被允许执行移动(局部变量在return语句中变为R值).move()在该上下文中使用将简单地禁止NRVO并强制编译器使用移动(或者如果移动不可用则复制).如果您返回的是局部变量以外的其他内容,则无论如何NRVO都不是一个选项,您应该使用move()if(并且仅当)您想要盗取该对象.

  • @ThomasLegris,好的,我看到你所看到的,但我有另一种解释.这个举动确实在进行,但被移动的是一个"vector <Noisy>"而不是"Noisy".`vector <>`的移动构造函数可以通过指针操作移动包含的对象,因此不必移动单个对象.如果你改变函数直接使用`Noisy`而不是`vector <Noisy>`,那么移动就会显示出来. (2认同)