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

时间:2022-09-10 23:48:29

Consider a type T supporting the default move semantics. Also consider the function below:

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

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

T o = f();

In the old C++03, some non-optimal compilers might call the copy constructor twice, one for the "return object" and one for o.

在旧的c++ 03中,一些非最优编译器可能会调用复制构造函数两次,一次用于“返回对象”,一次用于o。

In c++11, since t inside f() is an lvalue, those compilers might call the copy constructor one time as before, and then call the move constructor for o.

在c++11中,由于f()中的t是一个lvalue,这些编译器可能会像以前一样调用复制构造函数,然后为o调用move构造函数。

Is it correct to state that the only way to avoid the first "extra copy" is to move t when returning?

说明避免第一个“额外拷贝”的唯一方法是在返回时移动t是正确的吗?

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

3 个解决方案

#1


42  

No. Whenever a local variable in a return statement is eligible for copy elision, it binds to an rvalue re­fe­rence, and thus return t; is identical to return std::move(t); in your example with respect to which constructors are eligible.

不。每当一个局部变量的返回语句可用于复制省略,它绑定到一个右值再保险­­菲芮妮,因此返回t;与返回std相同::move(t);在您的示例中,关于哪些构造函数是合格的。

Note however that return std::move(t); prevents the compiler from exercising copy elision, while return t; does not, and thus the latter is the preferred style. [Thanks to @Johannes for the cor­rect­ion.] If copy elision happens, the question of whether or not move construction is used becomes a moot point.

但请注意返回std::move(t);防止编译器执行复制elision,而返回t;没有,因此后者是首选样式。(感谢@Johannes软木­­矩形离子。如果发生复制省略,是否使用移动结构的问题就成了一个争论点。

See 12.8(31, 32) in the standard.

参见标准中的12.8(31,32)。

Note also that if T has an accessible copy- but a deleted move-constructor, then return t; will not com­pile, because the move constructor must be considered first; you'd have to say something to the ef­fect of return static_cast<T&>(t); to make it work:

还要注意,如果T有一个可访问的副本——但有一个已删除的移动构造函数,那么返回T;不会com­桩,因为构造函数必须首先考虑移动;你不得不说点什么对ef­效应的回报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 &)
}

#2


11  

No. The best practice is directly return t;.

不。最好的做法是直接返回t;

In case class T has move constructor not deleted, and notice t is a local variable that return t is eligible for copy elision, it move constructs the returned object just like return std::move(t); does. However return t; is still eligible to copy/move elision, so the construction may be omitted, while return std::move(t) always constructs the return value using move constructor.

在case类T有移动构造函数未删除的情况下,注意T是一个本地变量,返回T符合复制elision的条件,它移动构造返回的对象,就像return std::move(T);所做的事。然而返回t;仍然有资格复制/移动省略,因此可以省略构造,而返回std:::move(t)总是使用move构造函数构造返回值。

In case move constructor in class T is deleted but copy constructor available, return std::move(t); will not compile, while return t; still compiles using copy constructor. Unlike @Kerrek mentioned, t is not bound to an rvalue reference. There's a two-stage overload resolution for return values that eligible for copy elision -- try move first, then copy, and both move and copy is possibly elided.

如果删除类T中的move构造函数,但复制构造函数可用,则返回std::move(T);不编译,返回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
}

If the return expression is lvalue and not eligible for copy elision (most likely you are returning a non-local variable or lvalue expression) and you still would like to avoid copy, std::move will be useful. But keep in mind that the best practice is make copy elision possible to happen.

如果返回表达式为lvalue,且不符合复制elision(很可能您正在返回一个非本地变量或lvalue表达式),您仍然希望避免复制,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) in the standard describes the process.

标准中的12.8(32)描述了这个过程。

12.8 [class.copy]

12.8[class.copy]

32 When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]

32当省略的复制操作的标准达到或将遇到除了源对象是一个函数的参数,并指定的对象复制是一个左值,选择的构造函数重载决议首先执行复制的对象被指定一个右值。如果重载解析失败,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv限定的),则再次执行重载解析,将对象视为lvalue。[注意:无论是否会发生拷贝省略,都必须执行两阶段的重载解析。如果不执行省略,它将确定要调用的构造函数,并且即使调用被省略,所选的构造函数也必须是可访问的。端注)

#3


3  

Ok, I would like to drop a comment on this. This question (and the answer) made me believe that it is not necessary to specify std::move on the return statement. However I was just thought a different lesson while dealing with my code.

好吧,我想就此发表点评论。这个问题(和答案)让我相信没有必要指定std::继续返回语句。然而,当我处理我的代码时,我被认为是一个不同的教训。

So, I have a function (it's actually a specialization) that takes a temporary and just returns it. (The general function template does other stuff, but the specialization does the identity operation).

所以,我有一个函数(它实际上是一个专门化)它取一个临时函数并返回它。(通用函数模板做其他事情,但是专门化做标识操作)。

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

Now, this version calls the copy constructor of A upon returning. If I change the return statement to

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

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

Then the move constructor of A gets called and I can do some optimizations there.

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

It might not be 100% matching your question. But it is false to think that return std::move(..) is never necessary. I used to think so. Not any more ;-)

这可能不是100%符合你的问题。但是认为返回std:::move(.. .)是不必要的,这是错误的。我以前也这么认为。不再;-)

#1


42  

No. Whenever a local variable in a return statement is eligible for copy elision, it binds to an rvalue re­fe­rence, and thus return t; is identical to return std::move(t); in your example with respect to which constructors are eligible.

不。每当一个局部变量的返回语句可用于复制省略,它绑定到一个右值再保险­­菲芮妮,因此返回t;与返回std相同::move(t);在您的示例中,关于哪些构造函数是合格的。

Note however that return std::move(t); prevents the compiler from exercising copy elision, while return t; does not, and thus the latter is the preferred style. [Thanks to @Johannes for the cor­rect­ion.] If copy elision happens, the question of whether or not move construction is used becomes a moot point.

但请注意返回std::move(t);防止编译器执行复制elision,而返回t;没有,因此后者是首选样式。(感谢@Johannes软木­­矩形离子。如果发生复制省略,是否使用移动结构的问题就成了一个争论点。

See 12.8(31, 32) in the standard.

参见标准中的12.8(31,32)。

Note also that if T has an accessible copy- but a deleted move-constructor, then return t; will not com­pile, because the move constructor must be considered first; you'd have to say something to the ef­fect of return static_cast<T&>(t); to make it work:

还要注意,如果T有一个可访问的副本——但有一个已删除的移动构造函数,那么返回T;不会com­桩,因为构造函数必须首先考虑移动;你不得不说点什么对ef­效应的回报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 &)
}

#2


11  

No. The best practice is directly return t;.

不。最好的做法是直接返回t;

In case class T has move constructor not deleted, and notice t is a local variable that return t is eligible for copy elision, it move constructs the returned object just like return std::move(t); does. However return t; is still eligible to copy/move elision, so the construction may be omitted, while return std::move(t) always constructs the return value using move constructor.

在case类T有移动构造函数未删除的情况下,注意T是一个本地变量,返回T符合复制elision的条件,它移动构造返回的对象,就像return std::move(T);所做的事。然而返回t;仍然有资格复制/移动省略,因此可以省略构造,而返回std:::move(t)总是使用move构造函数构造返回值。

In case move constructor in class T is deleted but copy constructor available, return std::move(t); will not compile, while return t; still compiles using copy constructor. Unlike @Kerrek mentioned, t is not bound to an rvalue reference. There's a two-stage overload resolution for return values that eligible for copy elision -- try move first, then copy, and both move and copy is possibly elided.

如果删除类T中的move构造函数,但复制构造函数可用,则返回std::move(T);不编译,返回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
}

If the return expression is lvalue and not eligible for copy elision (most likely you are returning a non-local variable or lvalue expression) and you still would like to avoid copy, std::move will be useful. But keep in mind that the best practice is make copy elision possible to happen.

如果返回表达式为lvalue,且不符合复制elision(很可能您正在返回一个非本地变量或lvalue表达式),您仍然希望避免复制,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) in the standard describes the process.

标准中的12.8(32)描述了这个过程。

12.8 [class.copy]

12.8[class.copy]

32 When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]

32当省略的复制操作的标准达到或将遇到除了源对象是一个函数的参数,并指定的对象复制是一个左值,选择的构造函数重载决议首先执行复制的对象被指定一个右值。如果重载解析失败,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv限定的),则再次执行重载解析,将对象视为lvalue。[注意:无论是否会发生拷贝省略,都必须执行两阶段的重载解析。如果不执行省略,它将确定要调用的构造函数,并且即使调用被省略,所选的构造函数也必须是可访问的。端注)

#3


3  

Ok, I would like to drop a comment on this. This question (and the answer) made me believe that it is not necessary to specify std::move on the return statement. However I was just thought a different lesson while dealing with my code.

好吧,我想就此发表点评论。这个问题(和答案)让我相信没有必要指定std::继续返回语句。然而,当我处理我的代码时,我被认为是一个不同的教训。

So, I have a function (it's actually a specialization) that takes a temporary and just returns it. (The general function template does other stuff, but the specialization does the identity operation).

所以,我有一个函数(它实际上是一个专门化)它取一个临时函数并返回它。(通用函数模板做其他事情,但是专门化做标识操作)。

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

Now, this version calls the copy constructor of A upon returning. If I change the return statement to

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

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

Then the move constructor of A gets called and I can do some optimizations there.

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

It might not be 100% matching your question. But it is false to think that return std::move(..) is never necessary. I used to think so. Not any more ;-)

这可能不是100%符合你的问题。但是认为返回std:::move(.. .)是不必要的,这是错误的。我以前也这么认为。不再;-)