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

u5rb5r59  于 2023-10-21  发布在  其他
关注(0)|答案(3)|浏览(153)

考虑一个支持默认移动语义的类型T。还考虑下面的函数:

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

T o = f();

在旧的C03中,一些非最佳编译器可能会调用复制构造函数两次,一次是为了“返回对象”,另一次是为了o
在c
11中,由于f()中的t是左值,这些编译器可能会像以前一样调用一次复制构造函数,然后调用o的移动构造函数。
如果说避免第一个“额外副本”的唯一方法是在返回时移动t,这是正确的吗?

T f() {
   T t;
   return std::move(t);
}
ubbxdtey

ubbxdtey1#

不是。只要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 &)
}
wh6knrhe

wh6knrhe2#

不,最佳做法是直接使用return t;
如果类T的move构造函数没有被删除,注意t是一个局部变量,return t有资格进行复制省略,它move构造返回的对象,就像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限定的),则再次执行重载解析,将对象视为左值。[注意:无论是否会发生复制省略,都必须执行此两阶段重载解析。它确定在不执行省略时要调用的构造函数,并且即使省略了调用,选定的构造函数也必须是可访问的。- 尾注]

inkz8wg9

inkz8wg93#

好吧,我想就此发表评论。这个问题(以及答案)让我相信没有必要在return语句中指定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(..)永远不必要是错误的。我以前也这么认为。不再是了;- )

相关问题