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

时间:2015-11-26 07:29:38

标签: c++ c++11 destructor move rule-of-zero

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

Example

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

"添加析构函数会产生禁用的副作用   生成移动函数,但因为Widget是可复制的,所有   用于生成移动的代码现在将生成副本。在   换句话说,在类中添加了析构函数   可能有效的举动被默默地取代   可能不那么有效的副本"。

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

  • 为什么声明析构函数会隐藏移动语义?
  • 声明/ definig析构函数只隐藏移动语义或复制 构造函数和复制赋值也隐藏了移动语义?

4 个答案:

答案 0 :(得分:21)

"零规则"事实上,除了生成特殊成员函数以及何时生成其他内容之外。这是对课堂设计的一种态度。它鼓励你回答一个问题:

我班级管理资源吗?

如果是这样,应将每个资源移动到其专用类,以便您的类仅管理资源(并且不执行任何其他操作)或仅累积其他类和/或执行相同的逻辑任务(但不管理资源)。 / p>

这是一个更通用的单一责任原则的特例。

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

因此"零"在名称中:当您将类别与资源管理和其他人分开时,在"其他人中"你需要提供零特殊成员函数(它们将被正确地自动生成。

C ++中有一些规则(特殊成员函数的定义)抑制了其他定义,但它们只会让您分心于理解零规则的核心。

有关详细信息,请参阅:

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

答案 1 :(得分:4)

几乎总是,如果你有一个析构函数("做某事"),你应该遵循三个"的规则,然后变成"五个规则&# 34;如果你想要移动语义。

如果您的析构函数为空,则不需要它。所以暗示是一个非空的析构函数(因为如果不需要它就不会有一个!),那么你还需要在复制和赋值操作中做同样的事情,并且可能是移动建设和移动任务需要做某事",而不仅仅是转移实际内容。

当然,可能存在这种情况并非如此,但编译器采用"方法仅在析构函数为空时才应用自动生成的移动函数",因为是"安全"方法

答案 2 :(得分:1)

  

声明/定义Dtor只隐藏移动语义或复制   ctor / copy assignment以及隐藏移动语义?

如果没有为类提供用户定义的移动构造函数,则以下所有条件均为真:

  • 没有用户声明的复制构造函数
  • 没有用户声明的副本分配运算符
  • 没有用户声明的移动分配操作符
  • 没有用户声明的析构函数

然后编译器将使用signature T::T(T&&)将移动构造函数声明为其类的非显式内联公共成员。

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

答案 3 :(得分:0)

首先我要说Mats Petersson的回答比接受的回答要好,因为它提到了理由。

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

隐式声明(或默认)移动ctor

的行为

来自c++draft

  

非联合类X的隐式定义的复制/移动构造函数执行其基础和成员的成员复制/移动。

编译器隐式声明移动ctor

的条件

来自cppreference

  

如果没有为所有类提供用户定义的移动构造函数   以下是真的:

     
      
  • 没有用户声明的复制构造函数
  •   
  • 没有用户声明的副本分配运算符
  •   
  • 没有用户声明的移动分配操作符
  •   
  • 没有用户声明的析构函数
  •   
     

然后编译器将一个移动构造函数声明为其类的非显式内联公共成员,其签名为T::T(T&&)

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

如果我们查看上述条件,不仅用户声明的析构函数阻止隐式声明的移动ctor,用户声明的复制构造函数,用户声明的复制赋值运算符和用户声明的移动赋值运算符都具有相同的防止效果。

Mats Petersson指出的理由是:

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

  • 如果存在用户声明的析构函数,这意味着需要进行一些清理工作,那么您可能希望使用移动的对象来执行此操作。

    < / LI>
  • 当有用户声明的移动赋值运算符时,由于它也是“移动”资源,您可能希望在移动ctor中执行相同操作。

  • 当存在用户声明的复制构造函数或复制赋值运算符时,这是最有趣的情况。我们知道move semantics allows us to keep value semantics while gaining performance optimization,当移动ctor未提供时,该移动将“回退”以复制。在某种程度上,移动可以被视为“优化副本”。因此,如果复制操作要求我们做某事,移动操作中也可能需要类似的工作。

由于在上述条件下,可能需要执行除成员移动之外的其他操作,编译器不会认为您不需要它,因此不会隐式声明移动ctor。