Why do we need to set rvalue reference to null in move constructor?

僤鯓⒐⒋嵵緔 提交于 2019-12-21 07:55:58

问题


//code from https://skillsmatter.com/skillscasts/2188-move-semanticsperfect-forwarding-and-rvalue-references
class Widget {
public:
    Widget(Widget&& rhs)
        : pds(rhs.pds) // take source’s value
    { 
        rhs.pds = nullptr;  // why??
    }

private:
    struct DataStructure;
    DataStructure *pds;
};

I can't understand the reason for setting rhd.pds to nullptr .

What will happen if we remove this line : rhs.pds = nullptr;


回答1:


Some details of the class have been removed. In particular, the constructor dynamically allocates the DataStructure object and the destructor deallocates it. If, during a move, you just copied the pointer from one Widget to another, both Widgets would have pointers to the same allocated DataStructure object. Then, when those objects are destroyed, they would both attempt to delete it. This would give undefined behaviour. To avoid this, the Widget that is being moved from has its internal pointer to set to nullptr.

This a standard pattern when implementing a move constructor. You want to move ownership of some dynamically allocated objects from one object to another, so you need to make sure the original object no longer owns those allocated objects.

Diagrammatically, you start off with this situation, wanting to move ownership of the DataStructure from one Widget to the other:

    ┌────────┐        ┌────────┐
    │ Widget │        │ Widget │
    └───╂────┘        └────────┘
        ┃
        ▼
 ┌───────────────┐
 │ DataStructure │
 └───────────────┘

If you just copied the pointer, you'd have:

    ┌────────┐        ┌────────┐
    │ Widget │        │ Widget │
    └───╂────┘        └───╂────┘
        ┗━━━━━━━━┳━━━━━━━┛
                  ▼
         ┌───────────────┐
         │ DataStructure │
         └───────────────┘

If you then set the original Widget pointer to nullptr, you have:

    ┌────────┐         ┌────────┐
    │ Widget │         │ Widget │
    └────────┘         └───╂────┘
                           ┃
                           ▼
                  ┌───────────────┐
                  │ DataStructure │
                  └───────────────┘

Ownership has successfully been transferred, and when both Widgets can be destroyed without causing undefined behaviour.




回答2:


The DataStructure object is likely "owned" by the Widget, and resetting the pointer prevents it from being accidentally deleted when the Widget is destroyed.

Alternately, it's conventional to reset objects to an "empty" or "default" state when they are moved-from, and resetting the pointer is a harmless way to follow the convention.




回答3:


class Widget {
  public:
    Widget(Widget&& rhs)
       : pds(rhs.pds) // take source’s value
    { 
        rhs.pds = nullptr;  // why??
    }
    ~Widget() {delete pds}; // <== added this line

private:
    struct DataStructure;
    DataStructure *pds;
};

I added a destructor in the above class.

Widget make_widget() {
    Widget a;
    // Do some stuff with it
    return std::move(a);
}

int main {
    Widget b = make_widget;
    return 0;
}

To illustrate what would happen if you remove the nullptr assignment, check the above methods. A widget a would be created in a helper function and assigned to widget b.

Since widget a goes out of scope its destructor its called, which deallocates memory, and you are left with widget b which is pointing to invalid memory address.

If you assign nullptr to rhs, a destructor is also called, but since delete nullptr does nothing all is good :)



来源:https://stackoverflow.com/questions/22114025/why-do-we-need-to-set-rvalue-reference-to-null-in-move-constructor

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!