2012-08-05 161 views
38

考虑支持默认移动语义的类型T.还要考虑以下功能:从函数返回值时使用std :: move()以避免复制

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

T o = f(); 

在旧的C++ 03,一些非最优的编译器可能会调用拷贝构造函数两次,一为“返回对象”,一个针对o

在C++ 11中,因为t里面的f()是一个左值,所以这些编译器可能会像以前一样调用复制构造函数,然后调用o的移动构造函数。

是否正确指出避免第一个“额外复制”的唯一方法是在返回时移动t

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

[here](http://stackoverflow.com/questions/9827183/why-am-i-allowed-to-copy-unique-ptr)是一个类似的问题 – 2012-08-05 18:30:23

+0

[相关FAQ] (http://stackoverflow.com/a/11540204/252000) – fredoverflow 2012-08-06 08:17:46

回答

42

号每当在return声明一个局部变量可享有复制省略,它结合一个rvalue重新­ FE ­伦斯,因此return t;相同return std::move(t);在例如相对于该构造是合格。

但是请注意,return std::move(t);可以防止从编译器执行copy elision,而return t;没有,因此后者是首选的风格。 [感谢@Johannes的回复。]如果发生复制删除,使用移动构造的问题就成了一个争议点。

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

还要注意的是,如果T具有可访问的禁止复制,但已删除的举动构造函数,然后return t;不会COM ­桩,因为移动构造函数必须首先考虑;你不得不说点什么到EF ­ FECT的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 &) 
} 
+0

谢谢。移动构造函数的调用次数如何?它可以是两个作为复制构造函数与一些C++ 03-抱怨编译器的调用次数吗? – Martin 2012-08-05 16:07:35

+12

它*不*相同。关于是否可以调用移动构造函数只是相同的。如果你写'return std :: move(t);'如果编译器不知道它的作用,就必须调用移动构造函数*。如果你写'return t;'移动构造函数调用可以被忽略,即使它可能有副作用。 – 2012-08-05 16:08:26

+0

@ JohannesSchaub-litb:好的,让我编辑一下。 – 2012-08-05 16:10:18

3

好吧,我想在这个下降的注释。这个问题(和答案)让我相信,没有必要在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(..)从不需要是错误的。我曾经这么认为。没有更多;-)

+0

这与原始问题不同。原来的问题是关于return x,其中x是局部变量。当x是局部变量时,返回x更好,因为编译器会将x视为返回值中的右值,因为它知道x是局部变量。当x是一个引用时,编译器不会给它特殊的处理。由于您的示例中“a”变量的类型是“A&”,因此您需要使用move将其更改为“A &&”。 – 2017-09-21 00:25:48

8

不。最佳做法是直接return t;

如果类T具有移动构造不会被删除,并通知t是一个局部变量return t可享有复制省略,其移动结构返回的对象,就像return std::move(t);一样。但是return t;仍然有资格复制/移动elision,因此可以省略构造,而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将是有益的。但请记住,最好的做法是使复制elision可能发生。

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-qualified),则将该对象视为左值,重新执行重载解析。 [注意:无论是否发生复制删除,都必须执行这种两阶段重载解析。如果不执行elision,它将确定要调用的构造函数,即使该函数没有被使用,所选的构造函数也必须是可访问的。 - 注意]