为什么我们需要在移动构造函数中将rvalue引用设置为null?

时间:2014-03-01 11:54:36

标签: c++ c++11 move-semantics rvalue-reference move-constructor

//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;
};

我无法理解将rhd.pds设置为nullptr的原因。

如果我们删除此行,会发生什么:rhs.pds = nullptr;

3 个答案:

答案 0 :(得分:14)

该课程的一些细节已被删除。特别是,构造函数动态分配DataStructure对象,析构函数释放它。如果在移动过程中,您只是将指针从一个Widget复制到另一个Widget,则两个DataStructure都会指向同一个已分配的delete对象。然后,当这些对象被销毁时,他们都会尝试Widget它。这会产生未定义的行为。为避免这种情况,正在移动的nullptr将其内部指针设置为DataStructure

这是实现移动构造函数时的标准模式。您希望将一些动态分配的对象的所有权从一个对象移动到另一个对象,因此您需要确保原始对象不再拥有这些已分配的对象。

从图解的角度来看,您从这种情况开始,希望将Widget的所有权从一个 ┌────────┐ ┌────────┐ │ Widget │ │ Widget │ └───╂────┘ └────────┘ ┃ ▼ ┌───────────────┐ │ DataStructure │ └───────────────┘ 转移到另一个{

}:

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

如果您刚刚复制了指针,那么您将拥有:

Widget

如果您将原始nullptr指针设置为 ┌────────┐ ┌────────┐ │ Widget │ │ Widget │ └────────┘ └───╂────┘ ┃ ▼ ┌───────────────┐ │ DataStructure │ └───────────────┘ ,则您有:

Widget

已成功转移所有权,并且可以销毁{{1}}两个而不会导致未定义的行为。

答案 1 :(得分:2)

DataStructure对象可能由Widget“拥有”,重置指针可防止在Widget被销毁时意外删除它。

或者,传统上将对象移动到“空”或“默认”状态是常规的,重置指针是遵循约定的无害方式。

答案 2 :(得分:1)

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;
};

我在上面的类中添加了一个析构函数。

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

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

为了说明如果删除nullptr赋值会发生什么,请检查上述方法。小部件a将在辅助函数中创建并分配给小部件b。

由于widget a超出了范围,它的析构函数被调用,它释放了内存,而你留下了一个指向无效内存地址的小部件b。

如果将nullptr分配给rhs,也会调用析构函数,但是从那以后     删除nullptr 没有什么是好的:)