为什么移动返回一个rvalue引用参数需要用std :: move()包装它?

时间:2016-10-22 02:22:14

标签: c++ c++11 c++14

我正在阅读有效的现代C ++第25项,第172页,它有一个例子来证明,如果你想移动返回一个右值引用参数,你需要用std :: move(param)包装它。因为参数本身总是一个左值,如果没有std :: move(),它将被复制返回。

我不明白。如果std :: move(param)只是将它引入的参数转换为右值引用,那么当param已经是右值引用时,差异是什么呢?

如下面的代码所示:

#include <string>
#include <iostream>
#include <utility>

template<typename T>
class TD;

class Widget {
public:
    explicit Widget(const std::string& name) : name(name) {
        std::cout << "Widget created with name: " << name << ".\n";
    }

    Widget(const Widget& w) : name(w.name) {
        std::cout << "Widget " << name << " just got copied.\n";
    }

    Widget(Widget&& w) : name(std::move(w.name)) {
        std::cout << "Widget " << name << " just got moved.\n";
    }

private:
    std::string name;
};

Widget passThroughMove(Widget&& w) {
    // TD<decltype(w)> wType;
    // TD<decltype(std::move(w))> mwType;
    return std::move(w);
}

Widget passThrough(Widget&& w) {
    return w;
}

int main() {
    Widget w1("w1");
    Widget w2("w2");

    Widget wt1 = passThroughMove(std::move(w1));
    Widget wt2 = passThrough(std::move(w2));

    return 0;
}

输出:

Widget created with name: w1.
Widget created with name: w2.
Widget w1 just got moved.
Widget w2 just got copied.

在passThroughMove(Widget&amp;&amp; w)中,w的类型已经是右值引用,std :: move(w)再次将其转换为右值引用。如果我取消注释TD线,我可以看到decltype(w)和decltype(std :: move(w))都是Widget&amp;&amp;:

move_parameter.cpp:27:21: error: implicit instantiation of undefined template 'TD<Widget &&>'
    TD<decltype(w)> wType;
                    ^
move_parameter.cpp:28:32: error: implicit instantiation of undefined template 'TD<Widget &&>'
    TD<decltype(std::move(w))> mwType;
                               ^

由于w和std :: move(w)都是相同的右值引用类型,为什么&#34;返回std :: move(w)&#34;移动w,而&#34;返回w&#34;只复制?

编辑:感谢您的回答和评论。我现在有了更好的理解,但不确定它是否准确。所以std :: move(w)返回一个右值引用,就像w本身一样。但是std :: move(w)作为一个函数调用,它本身就是一个rvalue,所以可以移动它。虽然w作为一个命名变量,但它本身就是一个左值,虽然它的类型是右值引用,所以它不能被移动。

2 个答案:

答案 0 :(得分:3)

表达式的类型与变量的类型不同,decltype同时执行这两种操作。

decltype(w)

是变量w。

decltype((w))

是表达式w的类型(以及(w),但它们是相同的)。

如果你有一个foo&&类型的变量,当在表达式中使用它的类型是foo&时 - 它被命名,因此是一个左值。

这有点道理。 foo&&只是意味着它可以绑定到临时的。一旦绑定,它就有一个名称,可以多次使用。

任何可以多次使用的东西都不应该隐式移动。

这个命名事物是左值的规则的唯一例外是对返回规则的隐含移动。在少数情况下,如果出现elision但由于某种原因而被阻止,则会隐式移动值。这些例外不适用于此。

答案 1 :(得分:1)

  

在passThroughMove(Widget&amp;&amp; w)中,w的类型已经是右值引用,std :: move(w)只是再次将其转换为右值引用。

     

所以std :: move(w)返回一个右值引用,就像w本身一样。

不,std::move(w)投射到右值,而右值参照是左值。

函数passThroughMovepassThrough都按值返回。 然而,它们在内部创造这种回报价值的方式上有所不同。 在内部,passThroughMove通过移动创建其返回值。一个新的Widget对象(返回值)是通过移入它来创建的,这是std::move对返回值的影响。另一方面,passThrough通过副本创建自己的返回值。

分配的事实

Widget wt2 = passThrough(std::move(w2));

是从rvalue完成的,不会改变passThrough被强制通过复制创建其返回值的事实。

在代码的输出中,您可以看到上述语义加上RVO的效果。如果没有RVO,两个分配都应该产生两个额外的移动结构,这些结构被优化掉了。

相关问题