为什么析构函数禁用隐式移动方法的生成?

Why does destructor disable generation of implicit move methods?

我试图通过阅读 this blog 来理解零规则的含义。 IMO,它说如果你声明自己的析构函数那么不要忘记将移动构造函数和移动赋值设置为默认值。

Example:

class Widget {
public:
  ~Widget();         // temporary destructor
  ...                // no copy or move functions
};

"The addition of the destructor has the side effect of disabling generation of the move functions, but because Widget is copyable, all the code that used to generate moves will now generate copies. In other words, adding a destructor to the class has caused presumably-efficient moves to be silently replaced with presumably-less-efficient copies".

Scott Meyers 的上述文字在引号内引起了我的一些疑问:

几乎总是,如果你有一个析构函数(即"does something"),你应该遵循"rule of three",如果你想要移动语义,它就会变成"rule of five"。

如果你的析构函数是空的,那么它就不需要了。所以这意味着一个非空的析构函数(因为如果不需要的话你不会有一个!),那么你也需要在复制和赋值操作中做同样的事情,并且大概需要移动构造和移动赋值到 "do something",而不仅仅是传输实际内容。

当然,也有可能不是这样,但编译器采用了"let's only apply the automatically generated move functions if the destructor is empty"的方法,因为那是"safe"的方法。

is declaring/defining Dtor only hide the move semantics or copy ctor/copy assignment as well hide the move semantics?

如果没有为 class 提供用户定义的 移动构造函数 ,则以下所有情况都为真:

  • 没有用户声明的复制构造函数
  • 没有用户声明的复制赋值运算符
  • 没有用户声明的移动赋值运算符
  • 没有用户声明的析构函数

然后编译器会将移动构造函数声明为其 class 的非显式内联 public 成员,其中包含 signature T::T(T&&).

因此,声明复制构造函数或赋值运算符也隐藏了隐式声明的移动构造函数。

"The Rule of Zero" 实际上不是关于生成什么特殊成员函数以及什么时候生成的。它是关于对 class 设计的某种态度。它鼓励您回答问题:

我的 class 管理资源吗?

如果是这样,应将每个资源移至其专用 class,以便您的 classes 仅管理资源(不做任何其他事情)或仅积累其他 classes and/or 执行相同的逻辑任务(但不管理资源)。

它是更一般的单一职责原则的特例。

当您应用它时,您会立即看到对于资源管理 classes 您将不得不手动定义移动构造函数、移动赋值和析构函数(您很少需要复制操作)。对于非资源 classes,您不需要(事实上您可能不应该)声明以下任何一项:移动 ctor/assignment、复制 ctor/assignment、析构函数。

因此名称中的 "zero":当您将 classes 与资源管理和其他分开时,在 "others" 中您需要提供零个特殊成员函数(它们将正确自动生成。

C++ 中有哪些定义(特殊成员函数)禁止哪些其他定义的规则,但它们只会分散您对零规则核心的理解。

有关详细信息,请参阅:

  1. https://akrzemi1.wordpress.com/2015/09/08/special-member-functions/
  2. https://akrzemi1.wordpress.com/2015/09/11/declaring-the-move-constructor/

首先,我想说 Mats Petersson 的回答比公认的更好,因为它提到了基本原理。

其次,作为补充,我想详细说明一下。

隐式声明(或默认)移动构造函数的行为

来自c++draft

The implicitly-defined copy/move constructor for a non-union class X performs a memberwise copy/move of its bases and members.

编译器隐式声明move ctor时的条件

来自cppreference

If no user-defined move constructors are provided for a class all of the following is true:

  • there are no user-declared copy constructors
  • there are no user-declared copy assignment operators
  • there are no user-declared move assignment operators
  • there are no user-declared destructors

then the compiler will declare a move constructor as a non-explicit inline public member of its class with the signature T::T(T&&).

为什么 dtor(和许多其他)阻止隐式声明的移动 ctor?

如果我们看上面的情况,不仅用户声明的析构函数可以防止隐式声明的移动构造函数,用户声明的复制构造函数,用户声明的复制赋值运算符和用户声明的移动赋值运算符都具有相同的防止效果。

正如 Mats Petersson 指出的那样,理由是:

如果编译器认为您可能需要在移动操作中执行成员移动以外的操作,那么假设您不需要它是不安全的。

  • 当有用户声明的析构函数时,这意味着需要做一些清理工作,那么您可能希望使用 moved-from对象.

  • 当有用户声明的移动赋值运算符时,因为它也是"moving"资源,你可能想在移动ctor中做同样的事情。

  • 当有用户声明的复制构造函数或复制赋值运算符时,这是最有趣的情况。我们知道,当没有提供move ctor时,那个move将"fall back"复制。在某种程度上,move 可以看作是 "optimized copy"。因此,如果复制操作需要我们做一些事情,很可能在移动操作中也需要类似的工作。

由于在上述情况下,可能需要执行成员移动以外的其他操作,编译器不会假定您不需要它,因此不会隐式声明移动构造函数。