RValue参考行为

时间:2014-07-19 11:30:29

标签: c++

我一直试图了解移动构造函数场景背后发生了什么,但我脑子里想不到什么。

   struct A {
     int s;
     A() : s(10) {}
     A(const A& o) : s(o.s) { std::cout << "copy contructor" << std::endl;}
     A(A&& o) : s(std::move(o.s)) {std::cout <<"move constructor" << std::endl;}
   };

   struct B  {
     std::string  s;
     B() : s("max") {;}
     B(const B& o) : s(o.s) {std::cout <<"copy contructor" << std::endl;}
     B(B&& o) : s(std::move(o.s)) {std::cout <<"move contructor" << std::endl;}
   };

   int main() {

   A a1;
   std::cout << " Before move " << " " << a1.s << std::endl;
   A a2 = std::move(a1); 
   std::cout << " After move" << " "  << a1.s << std::endl;

   B b1;
   std::cout << " Before move"  << " " << b1.s << std::endl;
   B b2 = std::move(b1);
   std::cout << " After move" << " "<< b1.s << std::endl;
   return 0;
 }

在一个对象上调用std :: move,应该明确表示尽管该对象在移动后仍然存在,但我们很乐意清空“。

The output is 
Before move 10  
move constructor
After move 10

Before move max
move constructor
After move ""

我有两个问题:

1)移动struct B的构造函数“窃取”资源并将b1.s留空(“”)。这对我来说非常有意义,为什么这不会发生在a1.s.它看起来像一个副本,而不是一个举动。

2)如果我从struct B的移动构造函数中移除std :: move,

 B(B&& o) : s(o.s) {std::cout <<"move contructor" << std::endl;}

我想应该调用一个拷贝构造函数。现在输出是:

Before move max
move constructor
After move max

这对我来说有点意义。我完全理解“最大移动后的资源”。现在它看起来像一个副本,但没有应该使用复制构造函数调用打印出来的“复制构造函数”的痕迹。

我心中肯定缺少某些东西,有人可以解释为什么吗? THX

2 个答案:

答案 0 :(得分:3)

  1. 从基本类型移动与副本完全相同。当您处理包含动态分配资源的对象时,移动才非常重要。例如,std::string将动态分配其字符串内容,这些内容将从原始std::string移动到新内容。

  2. 由于此处std::move

    ,仍然会选择移动构造函数
    B b2 = std::move(b1);
    

    表达式std::move(b1)是一个右值,因此将选择移动构造函数来构造b2。改变移动构造函数的作用不会改变它。你改变的是移动构造函数与复制构造函数的行为相同(除了它打印“使用移动构造函数”的事实)。由于您已将s(std::move(o.s))更改为s(o.s),因此只会复制内部字符串。

答案 1 :(得分:0)

我对这一点的理解也很模糊。因此,在以下情况下,我尝试根据我的理解进一步分解步骤:对于持有资源(int *)的类,在rvalue引用上进行全能交换

这就是目标(我放弃了您会在跟踪中看到的大多数格式...)

int main() {
    int a {1}; cout << "a:" << a << "_@_" << (&a) << "\n";
    int b {2}; cout << "b:" << b << "_@_" << (&b) << "\n";
    C ca {"ca",&a}; C cb {"cb",&b};
    ca.p();cb.p();
    swap(ca,cb);
    ca.p();cb.p();
}

现在使用右值引用的交换是“经典”

void swap(C& a, C& b){
    C tmp {"tmp",static_cast<C&&>(a)}; 
    a = static_cast<C&&>(b);
    b = static_cast<C&&>(tmp);
}

所以,这就是我对类的外观的理解

class C{
  private : string _id; int* _pi;

  public:

    //default construct
    C(string id):              
    _id(id), _pi(nullptr)            
    { cout << _id << ":C()_default_build\n"; }

    //construct from int
    C(string id, int* pi):     
    _id(id), _pi(new int(*pi))       
    { cout << _id << ":C(" << (*_pi) << "_@_" << _pi << ")_build_from_int\n"; }

    //contruct from other object, by copy
    C(string id, const C& other):
    _id(id), _pi(new int(*(other._pi)))  
    { cout << _id << ":C(" << (*_pi) << "_@_" << _pi << ")_build_as_copy_from(" <<    other._id << ")\n"; }

    //contruct from other object, by move    
    C(string id, C&& other):       
    _id(id), _pi( other._pi )  
    { 
        other._pi = nullptr;
        cout << _id << ":C(" << (*_pi) << "_@_" << _pi << ")_build_by_move_from(" << other._id << ")\n"; 
    }


    //destructor
    ~C() {
        cout << "~~ destroy " << _id;
        if (_pi){
            cout << " holding " << (*_pi) << " at " << _pi ;
            delete _pi; _pi = nullptr;
        }
        else
            cout << " __empty__ ";
        cout << " ~~\n";
    }

    //move assignment
    C& operator=(C&& other) noexcept{ 
        if (this != &other) {
            _pi = other._pi;
        other._pi = nullptr;
            cout << "## " << _id << " steals " << _pi << " from " << other._id << " ##\n";
        }
        return *this;
    }

    //display
    void p() { 
        cout << ">> " << _id;
        if (_pi)
            cout << " points to " << (*_pi) << " at " << _pi; 
        else
            cout << "__empty__";
        cout << " <<\n";
    }

};

现在,开火! ...痕迹

/**trace

--- simulate rvalue references ---

a:1_@_0x7ffd6a1af45c
b:2_@_0x7ffd6a1af458

ca:C(1_@_0x558eabb0c280)_build_from_int
cb:C(2_@_0x558eabb0c2a0)_build_from_int

>> ca points to 1 at 0x558eabb0c280 <<
>> cb points to 2 at 0x558eabb0c2a0 <<

tmp:C(1_@_0x558eabb0c280)_build_by_move_from(ca)
## ca steals 0x558eabb0c2a0 from cb ##
## cb steals 0x558eabb0c280 from tmp ##
~~ destroy tmp __empty__  ~~

>> ca points to 2 at 0x558eabb0c2a0 <<
>> cb points to 1 at 0x558eabb0c280 <<

~~ destroy cb holding 1 at 0x558eabb0c280 ~~
~~ destroy ca holding 2 at 0x558eabb0c2a0 ~~

*/

好的..对于这个例子,它可以工作...这是正确的答案吗?