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

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。还考虑以下功能:

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

T o = f();
在旧的C++03中,一些非最佳编译器可能会调用复制构造函数两次,一次用于“返回对象”,另一次用于
o

在c++11中,由于
t
inside
f()
是一个左值,这些编译器可能会像以前一样调用复制构造函数一次,然后为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)返回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
}