为什么我们需要在move构造函数中将右值引用设置为null

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

本文关键字:引用 设置 null 我们 构造函数 move 为什么      更新时间:2023-10-16
//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都将具有指向相同分配的DataStructure对象的指针。然后,当这些对象被销毁时,它们都会尝试delete它。这将给出未定义的行为。为了避免这种情况,要从中移动的Widget将其内部指针设置为nullptr

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

从图表上看,您从这种情况开始,希望将DataStructure的所有权从一个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,则还会调用析构函数,但由于删除nullptr什么都不做都好:)