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

Mar*_*tin 49 c++

考虑支持默认移动语义的类型T. 还要考虑以下功能:

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

T o = f();
Run Code Online (Sandbox Code Playgroud)

在旧的C++ 03中,一些非最佳编译器可能会调用两次复制构造函数,一个用于"返回对象",另一个用于o.

在c ++ 11中,由于tinside f()是左值,那些编译器可能像以前一样调用复制构造函数,然后调用o的移动构造函数.

是否正确说明避免第一次"额外复制"的唯一方法是t在返回时移动?

T f() {
   T t;
   return std::move(t);
}
Run Code Online (Sandbox Code Playgroud)

Ker*_* SB 48

号每当在一个局部变量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 &)
}
Run Code Online (Sandbox Code Playgroud)

  • 它不是*相同的.关于是否可以调用移动构造函数,它只是相同的.如果你写`return std :: move(t);`如果编译器不知道它的作用,则必须调用move构造函数*.如果你写`return t;`移动构造函数调用可以省略,即使它可能有副作用. (14认同)

小智 14

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

如果类T没有删除移动构造函数,并且notice t是一个return t有资格进行复制省略的局部变量,那么它会移动构造返回的对象return std::move(t);.但是return t;仍然有资格复制/移动elision,因此可以省略return std::move(t)构造,同时始终使用move构造函数构造返回值.

如果类中的移动构造函数T被删除但复制构造函数可用,return std::move(t);则不会编译,同时return t;仍使用复制构造函数进行编译.与@Kerrek提到的不同,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
}
Run Code Online (Sandbox Code Playgroud)

如果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
}
Run Code Online (Sandbox Code Playgroud)

标准中的12.8(32)描述了该过程.

12.8 [class.copy]

32当满足或将满足复制操作的省略标准时,除了源对象是函数参数这一事实,并且要复制的对象由左值指定,重载决策选择复制的构造函数首先执行,好像对象是由右值指定的.如果重载决策失败,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv-qualified),则再次执行重载决策,将对象视为左值.[注意:无论是否发生复制省略,都必须执行此两阶段重载决策.如果未执行elision,它将确定要调用的构造函数,并且即使调用被省略,也必须可以访问所选的构造函数. - 尾注]