何时使用= default vs = delete

fla*_*kes 6 c++ c++14

据我所知,这些语义仅用于复制构造函数,移动构造函数,复制赋值,移动赋值和析构函数.使用= delete是禁止使用其中一个函数,= default如果您希望在编译器上明确使用这些函数的默认值,则使用它.

在上课时使用这些关键字的最佳做法是什么?或者更确切地说,在开发课程时如何注意这些?

例如,如果我不知道我是否会使用其中一种功能,最好是禁止使用它delete还是允许它使用default

How*_*ant 6

好问题.

同样重要的是: 在哪里使用= default= delete.

我对此有一些有争议的建议.它与我们学到的(包括我自己)C++ 98/03 相矛盾.

与您的数据成员一起开始您的课程声明:

class MyClass
{
    std::unique_ptr<OtherClass> ptr_;
    std::string                 name_;
    std::vector<double>         data_;
    // ...
};
Run Code Online (Sandbox Code Playgroud)

然后,尽可能接近,列出要显式声明的所有六个特殊成员,并以可预测的顺序列出(并且不列出您希望编译器处理的那些成员).我更喜欢的顺序是:

  1. 析构函数 // this tells me the very most important things about this class.
  2. 默认构造函数
  3. 复制构造函数 // I like to see my copy members together
  4. 复制赋值运算符
  5. 移动构造函数 // I like to see my move members together
  6. 移动赋值运算符

这个订单的原因是:

  • 无论您默认哪些特殊成员,读者都更有可能了解默认值,如果他们知道数据成员是什么.
  • 通过将特殊成员列在顶部附近的一致位置,并以一致的顺序列出,读者更有可能快速意识到哪些特殊成员未被显式声明 - 因此要么隐式声明,要么根本不存在.
  • 通常,两个复制成员(构造函数和赋值)都是相似的.两者都将被隐式默认或删除,显式默认或删除,或明确提供.很高兴在两行代码中彼此相邻地确认这一点.
  • 通常两个移动成员(构造函数和赋值)都相似......

例如:

class MyClass
{
    std::unique_ptr<OtherClass> ptr_;
    std::string                 name_;
    std::vector<double>         data_;
public:
    MyClass() = default;
    MyClass(const MyClass& other);
    MyClass& operator=(const MyClass& other);
    MyClass(MyClass&&) = default;
    MyClass& operator=(MyClass&&) = default;
    // Other constructors...
    // Other public member functions
    // friend functions
    // friend types
    // private member functions
    // ...
};
Run Code Online (Sandbox Code Playgroud)

知道了约定,人们可以很快地看到,而不必检查~MyClass()隐式默认的整个类声明,并且附近的数据成员很容易看到编译器声明和提供的析构函数的作用.

接下来我们可以看到它MyClass有一个显式默认的默认构造函数,并且附近声明了数据成员,很容易看到编译器提供的默认构造函数的作用.很容易理解为什么默认构造函数已被显式声明:因为我们需要一个用户定义的复制构造函数,如果没有明确默认,这将禁止编译器提供的默认构造函数.

接下来我们看到有一个用户提供的复制构造函数和复制赋值运算符.为什么?好吧,随着附近的数据成员,很容易推测可能需要深层次的数据unique_ptr ptr_.我们当然不知道当然没有检查复制成员的定义.但即使没有这些定义也很方便,我们已经非常了解情况.

对于用户声明的复制成员,如果我们什么也不做,则隐式不会移动成员.但是在这里我们很容易看到(因为所有内容都可以预见地在MyClass声明的顶部进行分组和排序),我们已明确默认移动成员.再次,因为数据成员在附近,我们可以立即看到这些编译器提供的移动成员将做什么.

总之,我们还没有确切知道MyClass它将在这个计划中扮演什么角色和角色.然而,即使缺乏这些知识,我们已经知道了很多MyClass.

我们知道MyClass:

  • 拥有一个独特的指针指向某些(可能是多态的)OtherClass.
  • 保存一个用作名称的字符串.
  • 持有一堆双打切断作为某种数据.
  • 在不泄漏任何东西的情况下会妥善破坏自己.
  • 默认情况下将构造自身为null ptr_,空name_data_.
  • 将复制自己,而不是正确的如何,但有一个可能的算法,我们可以轻松检查其他地方.
  • 通过移动三个数据成员中的每一个来有效地(并且正确地)移动自身.

在10行左右的代码中可以了解很多.而且我们不必去寻找数百行代码,我肯定需要这些代码才能正确实现MyClass以学习所有这些:因为它全部位于顶层且可预测的顺序.

有人可能想要调整此配方,以便在数据成员之前放置嵌套类型,以便可以根据嵌套类型声明数据成员.但是,这一建议的精神是宣布私人数据成员和特殊成员,尽可能接近顶端,并尽可能接近彼此.这与过去给出的建议(可能甚至是我自己)相反,私有数据成员是一个实现细节,不足以成为类声明的顶端.

但事后看来(后见之明总是20/20),私有数据成员,即使远程代码无法访问(这是一件好事),当任何特殊成员编译器提供时,它指示和描述类型的基本行为. .并且知道班级的特殊成员所做的事情,是理解任何类型的最重要方面之一.

  • 它是可以破坏的吗?
  • 它是默认构造的吗?
  • 它是可复制的吗?
  • 它可以移动吗?
  • 它是否具有值语义或引用语义?

每种类型都有这些问题的答案,最好尽快解决这些问题和答案.然后你可以更容易地专注于使这种类型与其他类型不同的东西.