← 模式

五法则

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253#include <utility> class resource { int x = 0; }; class foo { public: foo

() : p{new resource{}} { } foo(const foo& other) : p{new resource{*(other.p)}} { } foo(foo&& other) : p{other.p} { other.p = nullptr; } foo& operator=(const foo& other) { if (&other != this) { delete p; p = nullptr; p = new resource{*(other.p)}; } return *this; } foo& operator=(foo&& other) { if (&other != this) { delete p; p = other.p; other.p = nullptr; } return *this; } ~foo() { delete p; } private: resource* p; };

此模式采用 CC0 公共领域贡献 许可。

需要 c++11 或更新版本。

意图

安全高效地实现 RAII,以封装对动态分配资源的管理。

描述

五法则是对三法则的现代扩展。首先,三法则规定,如果一个类实现了以下任一函数,它就应该实现所有这些函数:

  • 拷贝构造函数
  • 拷贝赋值运算符
  • 析构函数

这些函数通常只在一个类手动管理动态分配的资源时才需要,因此必须全部实现以安全地管理该资源。

此外,五法则指出,通常也应该提供以下函数,以允许从临时对象进行优化的拷贝:

  • 移动构造函数
  • 移动赋值运算符

foo第 7-53 行中,在其构造函数中动态分配了一个 resource 对象。foo 的拷贝构造函数(第 14-16 行)、拷贝赋值运算符(第 24-33 行)和析构函数(第 46-49 行)的实现确保了该资源的生命周期由包含它的 foo 对象安全管理,即使在发生异常的情况下也是如此。

我们还实现了一个移动构造函数(第 18-22 行)和移动赋值运算符(第 35-44 行),它们提供了从临时对象进行优化的拷贝。它们不是拷贝资源,而是从原始的 foo 对象中接管资源,并将其内部指针设置为 nullptr,从而有效地“窃取”了资源。

请注意,赋值运算符(第 24-44 行)检查了自我赋值,以确保对资源的安全管理。

注意:示例代码中的拷贝和移动赋值运算符仅提供基本的异常安全性。它们也可以用“拷贝并交换”手法来实现,这种方法以一定的优化成本提供了强异常安全性。

注意:我们通常可以通过使用零法则来完全避免手动内存管理以及编写拷贝构造函数、赋值运算符和析构函数。

贡献者

  • Joseph Mansfield

最后更新

2018年8月27日

来源

在 GitHub 上 Fork 此模式

分享