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 );
}

我应该用哪一种?

121872 次浏览

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

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

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

当满足某些条件时,实现被允许省略 类对象的copy/move结构,即使copy/move 对象的构造函数和/或析构函数有副作用。在 在这种情况下,实现处理源和目标 省略了复制/移动操作作为简单的两种不同的引用方式 同一物体,该物体的破坏发生在 后来这两件物品被摧毁的时间 没有优化。这种复制/移动操作的省略, 复制省略,在以下情况下是允许的 (可以合并以消除多个副本):

[…]

例子:

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


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


Thing t2 = f();
这里省略的条件可以组合在一起,以消除对Thing类的复制构造函数的两次调用: 将本地自动对象t复制到函数f()的返回值的临时对象中 以及将该临时对象复制到对象t2。实际上,局部对象t . xml的构造 可以被视为直接初始化全局对象t2,该对象的销毁将发生在程序 退出。向Thing添加一个move构造函数具有相同的效果,但它是从 被省略的t2的临时对象。- 最后的例子]

当拷贝操作的省略条件被满足或将被满足时,除非源 对象是一个函数形参,要复制的对象由一个左值指定,重载解析到 首先执行复制的构造函数选择,就好像对象是由右值指定的一样。如果过载 解析失败,或者所选构造函数的第一个参数的类型不是右值引用 对象的类型(可能是cv限定的),则再次执行重载解析,将对象视为 左值。< / p >

如果你返回一个局部变量,不要使用move()。这将允许编译器使用NRVO,如果不这样做,编译器仍将被允许执行移动(局部变量在return语句中变成r -value)。在该上下文中使用move()将简单地抑制NRVO并强制编译器使用move(或如果move不可用则使用复制)。如果返回的不是局部变量,无论如何NRVO都不是一个选项,当(且仅当)你打算窃取对象时,应该使用move()

只使用第一种方法:

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

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

第二个版本会主动禁止复制省略。第一个版本普遍更好。

这很简单。

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将导致从左值的复制 这只是规则"always move右值引用的一个实例 和forward通用引用",它优先于

. rule "从不move返回值"

从c++ 20开始,这个异常可以被遗忘。return语句中的右值引用现在被隐式地从。