C++ 从函数返回值时使用std::move()避免复制
考虑一个支持默认移动语义的类型T。还考虑以下功能:C++ 从函数返回值时使用std::move()避免复制,c++,C++,考虑一个支持默认移动语义的类型T。还考虑以下功能: T f() { T t; return t; } T o = f(); 在旧的C++03中,一些非最佳编译器可能会调用复制构造函数两次,一次用于“返回对象”,另一次用于o 在c++11中,由于tinsidef()是一个左值,这些编译器可能会像以前一样调用复制构造函数一次,然后为o调用移动构造函数 声明避免第一个“额外副本”的唯一方法是在返回时移动t是否正确 T f() { T t; return std::move(
T f() {
T t;
return t;
}
T o = f();
在旧的C++03中,一些非最佳编译器可能会调用复制构造函数两次,一次用于“返回对象”,另一次用于o
在c++11中,由于t
insidef()
是一个左值,这些编译器可能会像以前一样调用复制构造函数一次,然后为o调用移动构造函数
声明避免第一个“额外副本”的唯一方法是在返回时移动t
是否正确
T f() {
T t;
return std::move(t);
}
否。只要
return
语句中的局部变量符合复制省略的条件,它就会绑定到一个右值表达式,从而返回t代码>与返回std::move(t)相同代码>在您的示例中,哪些构造函数是合格的
但是请注意,返回std::move(t)
防止编译器执行复制省略,而返回t
;没有,因此后者是首选样式。[感谢@Johannes的纠正。]如果发生复制省略,是否使用移动构造的问题将成为一个没有意义的问题
参见本标准中的12.8(31,32)
还要注意,如果T
有一个可访问的副本,但有一个已删除的移动构造函数,则返回T代码>不会堆积,因为必须首先考虑移动构造函数;你必须说点什么,才能有效地返回static\u cast(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 &)
}
tf()
{
T;
返回t;//很可能完全省略
return std::move(t);//如果已定义,则使用t::t(t&&);如果已删除或无法访问,则返回错误
返回static_cast(t)//使用t::t(t const&)
}
好的,我想就此发表评论。这个问题(以及答案)让我相信没有必要在return语句中指定std::move
。然而,在处理我的代码时,我被认为是一个不同的教训
所以,我有一个函数(它实际上是一个专门化),它接受一个临时值并返回它。(通用函数模板执行其他操作,但专门化执行标识操作)
然后调用A
的move构造函数,我可以在那里进行一些优化
它可能与您的问题不完全匹配。但认为返回std::move(…)
从来都不是必需的想法是错误的。我以前也这么认为。不再是;-) 否。最佳做法是直接返回t代码>
如果类T
没有删除move构造函数,并且注意T
是一个局部变量,return T
符合复制省略条件,那么它move会像return std::move(T)一样构造返回的对象代码>可以。然而返回t
仍然有资格复制/移动省略,因此可以省略构造,而return std::move(t)
始终使用move构造函数构造返回值
如果类T
中的move构造函数被删除,但复制构造函数可用,返回std::move(T)
将不编译,而返回t代码>仍然使用复制构造函数编译。与前面提到的不同,t
没有绑定到rvalue引用。对于符合复制省略条件的返回值,有两个阶段的重载解析——首先尝试移动,然后尝试复制,移动和复制都可能被省略
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
}
标准中的12.8(32)描述了该过程
12.8[类别副本]
32当满足或将满足省略复制操作的条件时,除非源对象是函数参数,并且要复制的对象由左值指定,否则首先执行重载解析以选择复制的构造函数,就像对象由右值指定一样。如果重载解析失败,或者如果所选构造函数的第一个参数的类型不是对对象类型的右值引用(可能是cv限定),将再次执行重载解析,并将对象视为左值。[注意:无论是否会发生复制省略,都必须执行此两阶段重载解析。如果未执行省略,它将确定要调用的构造函数,并且即使省略调用,所选构造函数也必须可访问。-结束注意]
谢谢调用move构造函数的次数是多少?对于某些c++03 Compliant编译器,调用复制构造函数的次数可以是两个吗?这是不相同的。仅在是否可以调用move构造函数方面是相同的。如果你写返回std::move(t)如果编译器不知道move构造函数的作用,则必须调用它。如果你写返回t代码>移动构造函数调用可以省略,即使它可能有副作用。值得注意的是,这只适用于返回值本身,而不是在这样的情况下:返回std::make_pair(a,b)
,如果需要,此处应显式移动a
和b
。这是一个类似的问题,与原来的问题不同。最初的问题是关于返回x,其中x是一个局部变量。当x是局部变量时,返回x更好,因为编译器会将x视为返回中的右值,因为它知道x是局部变量。当x是引用时,编译器不会对其进行特殊处理。由于示例中“a”变量的类型是“a&”,您需要使用move将其更改为“a&”。“如果类T中的move构造函数被删除,但复制构造函数可用,…return T;
仍然使用复制构造函数编译”-这似乎是正确的。
Leaf_t make( A &&a) {
return std::move(a);
}
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
}
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
}