从函数返回值时使用std :: move()以避免复制

考虑一个支持默认移动语义的typesT. 还要考虑下面的function:

T f() { T t; return t; } T o = f(); 

在旧的C ++ 03中,一些非最优编译器可能会调用复制构造函数两次,一次是“返回对象”,一次是o

在c ++ 11中,由于f()中的t是一个左值,所以这些编译器可能会像以前一样调用复制构造函数,然后调用o的移动构造函数。

指出避免第一个“额外复制”的唯一方法是在返回时移动t是否正确?

 T f() { T t; return std::move(t); } 

不。只要return语句中的局部variables符合copy elision的条件,就会绑定到右值引用,从而return t;return std::move(t); 在你的例子中,哪些构造函数是合格的。

请注意, return std::move(t); 阻止编译器执行copy elision,同时return t ; 不,因此后者是首选的风格。 [感谢@Johannes的更正。]如果发生删节,是否使用移动构build的问题成为一个争论点。

见标准中的12.8(31,32)。

还要注意的是,如果T有一个可访问的拷贝 – 但是删除了一个移动构造函数,那么return t; 不会编译,因为移动构造函数必须首先考虑; 你不得不说, return static_cast<T&>(t); 使其工作:

 T f() { T t; return t; // most likely elided entirely return std::move(t); // uses T::T(T &&) if defined; error if deleted or inaccessible return static_cast<T&>(t) // uses T::T(T const &) } 

不。最好的做法是直接return t;

如果类T没有删除移动构造函数,并且注意到t是一个局部variables, return t有资格进行复制elision,它就像return std::move(t);构造返回的对象return std::move(t); 确实。 不过return t; 仍然有资格复制/移动elision,所以可以省略构造,而return std::move(t)总是使用移动构造函数构造返回值。

如果T类中的移动构造函数被删除,但复制构造函数可用,则return std::move(t); 不会编译,而return t; 仍然使用拷贝构造函数编译。 不像@Kerrek提到的, t不受右值引用的约束。 有一个两阶段重载parsing的返回值适用于复制elision – 尝试先移动,然后复制,移动和复制都可能被取消。

 class T { public: T () = default; T (T&& t) = delete; T (const T& t) = default; }; T foo() { T t; return t; // OK: copied, possibly elided return std::move(t); // error: move constructor deleted return static_cast<T&>(t); // OK: copied, never elided } 

如果returnexpression式是左值而不符合复制elision的条件(很可能你正在返回一个非局部variables或左值expression式),你仍然希望避免复制, std::move将会很有用。 但请记住,最好的做法是使复制elision可能发生。

 class T { public: T () = default; T (T&& t) = default; T (const T& t) = default; }; T bar(bool k) { T a, b; return k ? a : b; // lvalue expression, copied return std::move(k ? a : b); // moved if (k) return a; // moved, and possibly elided else return b; // moved, and possibly elided } 

标准中的12.8(32)描述了这个过程。

12.8 [class.copy]

32当满足或将满足复制操作的条件时,除了源对象是函数参数,要复制的对象由左值指定的情况下,重载parsing可select复制的构造函数首先执行,就好像对象是由右值指定的一样。 如果重载parsing失败,或者如果所选构造函数的第一个参数的types不是对象types的右值引用(可能是cv-qualified),则将该对象视为左值,重新执行重载parsing。 [注意:无论是否发生复制,都必须执行这个两阶段重载parsing。 它确定如果没有执行elision,那么将调用构造函数,即使该函数没有被使用,所选的构造函数也必须是可访问的。 – 注意]

好的,我想对此发表评论。 这个问题(和答案)让我相信,没有必要在return语句中指定std::move 。 然而,在处理我的代码时,我只是想了一个不同的教训。

所以,我有一个函数(它实际上是一个专业化),只需要临时返回它。 (一般function模板做其他的东西,但专业化做身份操作)。

 template<> struct CreateLeaf< A > { typedef A Leaf_t; inline static Leaf_t make( A &&a) { return a; } }; 

现在,这个版本在返回时调用A的拷贝构造函数。 如果我更改返回语句

 Leaf_t make( A &&a) { return std::move(a); } 

然后A的移动构造函数被调用,我可以在那里做一些优化。

这可能不是100%符合你的问题。 但是,认为return std::move(..)从来没有必要是错误的。 我曾经这样想。 不再 ;-)