[英]Why do we need to set rvalue reference to null in 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;
该类的一些细节已被删除。 特别是,构造函数动态分配DataStructure
对象,析构函数释放它。 如果在移动过程中,您只是将指针从一个Widget
复制到另一个Widget
,则两个Widget
都将具有指向同一个分配的DataStructure
对象的指针。 然后,当这些对象被销毁时,它们都会尝试delete
它。 这将给出未定义的行为。 为了避免这种情况,正在移动的Widget
将其内部指针设置为nullptr
。
这是实现移动构造函数时的标准模式。 您希望将一些动态分配的对象的所有权从一个对象转移到另一个对象,因此您需要确保原始对象不再拥有这些分配的对象。
从图表上看,您从这种情况开始,希望将DataStructure
所有权从一个Widget
转移到另一个Widget
:
┌────────┐ ┌────────┐
│ Widget │ │ Widget │
└───╂────┘ └────────┘
┃
▼
┌───────────────┐
│ DataStructure │
└───────────────┘
如果你只是复制了指针,你会有:
┌────────┐ ┌────────┐
│ Widget │ │ Widget │
└───╂────┘ └───╂────┘
┗━━━━━━━━┳━━━━━━━┛
▼
┌───────────────┐
│ DataStructure │
└───────────────┘
如果您随后将原始Widget
指针设置为nullptr
,您将:
┌────────┐ ┌────────┐
│ Widget │ │ Widget │
└────────┘ └───╂────┘
┃
▼
┌───────────────┐
│ DataStructure │
└───────────────┘
所有权已成功转移,并且当两个Widget
都可以被销毁而不会导致未定义的行为时。
DataStructure
对象可能由Widget
“拥有”,重置指针可以防止它在Widget
被销毁时被意外删除。
或者,在移动对象时将对象重置为“空”或“默认”状态是惯例,并且重置指针是遵循约定的无害方式。
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。
由于小部件 a 超出了其调用的析构函数的作用域,它会释放内存,而小部件 b 则指向无效的内存地址。
如果将 nullptr 分配给 rhs,也会调用析构函数,但由于 delete nullptr 什么都不做,一切都很好:)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.