57

考虑支持默认移动语义的类型 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);
}
4

3 回答 3

55

不。只要语句中的局部变量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 &)
}
于 2012-08-05T16:04:57.243 回答
18

不,最佳做法是直接return t;.

如果类T有未删除的移动构造函数,并且 noticet是一个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 当满足或将满足复制操作省略的条件时,除了源对象是函数参数的事实,并且要复制的对象由左值指定时,重载决策以选择复制的构造函数就像对象由右值指定一样首先执行。如果重载决议失败,或者如果所选构造函数的第一个参数的类型不是对对象类型的右值引用(可能是 cv 限定的),则再次执行重载决议,将对象视为左值。[注意:无论是否会发生复制省略,都必须执行此两阶段重载解析。它确定如果不执行省略则要调用的构造函数,并且即使调用被省略,所选构造函数也必须是可访问的。——尾注]

于 2013-10-31T04:31:25.767 回答
3

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

所以,我有一个函数(它实际上是一个特化),它需要一个临时的并返回它。(通用函数模板做其他事情,但特化做恒等操作)。

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

A现在,这个版本在返回时调用复制构造函数。如果我将 return 语句更改为

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

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

它可能不是 100% 匹配您的问题。但认为这return std::move(..)从来没有必要是错误的。我以前也是这么想的。不再 ;-)

于 2012-11-05T13:33:33.963 回答