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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17473753/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 21:13:04  来源:igfitidea点击:

c++11 Return value optimization or move?

c++c++11movereturn-value-optimizationrvo

提问by elvis.dukaj

I don't understand when I should use std::moveand when I should let the compiler optimize... for example:

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

Which should I use?

我应该使用哪个?

采纳答案by Kerrek SB

Use exclusively the first method:

仅使用第一种方法:

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

This will alreadyallow the use of the move constructor, if one is available. In fact, a local variable can bind to an rvalue reference in a returnstatement precisely when copy elision is allowed.

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

Your second version actively prohibits copy elision. The first version is universally better.

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

回答by Jamin Grey

All return values are either already movedor optimized out, so there is no need to explicitly move with return values.

所有返回值要么已经被moved优化掉,要么被优化掉,所以不需要显式地移动返回值。

Compilers are allowed to automatically move the return value (to optimize out the copy), and even optimize out the move!

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

Section 12.8 of n3337 standard draft(C++11):

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

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization.This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

[...]

Example:

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

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

Thing t2 = f();

Here the criteria for elision can be combined to eliminate two calls to the copy constructor of class Thing: the copying of the local automatic object tinto the temporary object for the return value of function f()and the copying of that temporary object into object t2. Effectively, the construction of the local object tcan be viewed as directly initializing the global object t2, and that object's destruction will occur at program exit. Adding a move constructor to Thinghas the same effect, but it is the move construction from the temporary object to t2that is elided. — end example]

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue.

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

[...]

示例

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

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

Thing t2 = f();

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

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

回答by Oktalist

It's quite simple.

这很简单。

return buffer;

return buffer;

If you do this, then either NRVO will happen or it won't. If it doesn't happen then bufferwill be moved from.

如果您这样做,那么 NRVO 要么会发生,要么不会。如果它没有发生,那么buffer将被移出。

return std::move( buffer );

return std::move( buffer );

If you do this, then NVRO will nothappen, and bufferwill be moved from.

如果你这样做,那么 NVRO就不会发生,并且buffer会被移走。

So there is nothing to gain by using std::movehere, and much to lose.

所以在std::move这里使用没有什么好处,也有很多损失。

There is one exception to this rule:

此规则有一个例外:

Buffer read(Buffer&& buffer) {
    //...
    return std::move( buffer );
}

If bufferis an rvalue reference, then you should use std::move. This is because references are not eligible for NRVO, so without std::moveit would result in a copy from an lvalue.

如果buffer是右值引用,则应使用std::move. 这是因为引用不符合 NRVO 的条件,因此没有std::move它会导致从左值复制。

This is just an instance of the rule "always movervalue references and forwarduniversal references", which takes precedence over the rule "never movea return value".

这只是“总是move右值引用和forward通用引用”规则的一个实例,它优先于“从不move返回值”规则。

回答by Adam H. Peterson

If you're returning a local variable, don't use move(). This will allow the compiler to use NRVO, and failing that, the compiler will still be allowed to perform a move (local variables become R-values within a returnstatement). Using move()in that context would simply inhibit NRVO and force the compiler to use a move (or a copy if move is unavailable). If you're returning something other than a local variable, NRVO isn't an option anyway and you should use move()if (and only if) you intend to pilfer the object.

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