从函数返回值时使用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 }
如果return
expression式是左值而不符合复制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(..)
从来没有必要是错误的。 我曾经这样想。 不再 ;-)