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);
}
T f() {
T t;
return t;
}
T o = f();
o
。f()
内的t
是一个左值,那些编译器可能会像以前一样调用一次拷贝构造函数,然后为o
调用移动构造函数。t
吗?T f() {
T t;
return std::move(t);
}
不。只要本地变量在return
语句中符合复制省略的条件,它就会绑定到一个右值引用上,因此在您的示例中,return t;
与return std::move(t);
相同,关于哪些构造函数是符合条件的。
但请注意,return std::move(t);
会防止编译器进行复制省略,而return t;
不会,因此后者是首选样式。[感谢@Johannes的更正。]如果发生复制省略,则使用移动构造的问题将成为无意义的问题。
请参见标准中的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 std::move(t);
,如果编译器不知道它的作用,那么移动构造函数 必须 被调用。如果你写return t;
,即使移动构造函数可能具有副作用,移动构造函数的调用也可以省略。 - Johannes Schaub - litbreturn std::make_pair(a, b);
,在这种情况下,如果需要,应明确移动a
和b
。 - GManNickG不是的,最佳实践是直接使用 return t;
。
如果类 T
具有未删除的移动构造函数,并注意到 t
是局部变量,则可以对 return t
进行拷贝省略,它会像 return std::move(t);
一样移动构造返回的对象。然而,return t;
仍然有资格进行拷贝/移动省略,因此构造可能会被省略,而 return std::move(t)
则总是使用移动构造函数构造返回值。
如果类 T
的移动构造函数被删除,但复制构造函数可用,则 return std::move(t);
将无法编译通过,而 return t;
仍然可以使用复制构造函数进行编译。与 @Kerrek 所提到的不同,t
并未绑定到右值引用。对于有资格进行拷贝省略的返回值,存在两个阶段的重载决议——先尝试移动构造,然后是复制构造,移动和复制都可能被省略。
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
表达式是左值且不符合复制省略条件(最可能的情况是返回一个非本地变量或左值表达式),但你仍然想避免复制,std::move
会很有用。但请记住,最佳实践是让复制省略尽可能发生。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 当满足复制操作省略的条件或除了源对象是函数参数之外,将满足这些条件,并且要复制的对象由lvalue指定时,首先进行重载分辨率以选择用于复制的构造函数,就好像对象由rvalue指定一样。如果重载分辨率失败,或所选构造函数的第一个参数的类型不是对象类型(可能是cv-qualified)的rvalue引用,则再次执行重载分辨率,将对象视为lvalue。[注意:无论是否发生复制省略,都必须执行这两个阶段的重载分辨率。它确定要调用的构造函数,即使调用被省略,也必须能够访问所选的构造函数。 ——注]
return t;
仍然可以使用复制构造函数进行编译。但是,这似乎是不正确的(http://cpp.sh/2jxhuy)。 - Sasha好的,我想对此发表评论。这个问题(以及答案)让我相信,在返回语句中不需要指明std::move
是没有必要的。然而,当我处理我的代码时,我刚刚学到了一个不同的教训。
因此,我有一个函数(实际上是一种专业化),它接受一个临时对象并将其返回。(通用函数模板做其他事情,但专业化执行身份操作)。
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
的移动构造函数,我可以在那里进行一些优化。
这可能并不完全符合您的问题。但是错误地认为永远不需要使用 return std::move(..)
是错误的。 我曾经也这样认为。不再了 ;-)