何时使用= default vs = delete

时间:2014-12-01 16:37:52

标签: c++ c++14

据我所知,这些语义仅用于复制构造函数,移动构造函数,复制赋值,移动赋值和析构函数。使用= delete是为了禁止使用其中一个函数,如果你想明确编译器在哪里使用这些函数的默认值,则使用= default

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

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

3 个答案:

答案 0 :(得分:6)

好问题。

同样重要的是: 使用= default= delete

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

使用您的数据成员启动您的类声明:

class MyClass
{
    std::unique_ptr<OtherClass> ptr_;
    std::string                 name_;
    std::vector<double>         data_;
    // ...
};

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

  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. 移动赋值运算符
  7. 此订单的原因是:

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

    例如:

    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
        // ...
    };
    

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

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

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

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

    总之,我们还不知道MyClass究竟做了什么以及它将在此计划中扮演什么角色。然而,即使缺乏这些知识,我们已经对MyClass了解了很多。

    我们知道MyClass

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

    在10行左右的代码行中要知道很多。我们没有必要去寻找数百行代码,我确信这些代码是MyClass正确实现所有这些所需要的:因为它全部位于顶部且位于可预测的顺序。

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

    但事后看来(后见之明总是20/20),私有数据成员,即使远程代码无法访问(这是一件好事),决定并描述一种类型的基本行为当任何特殊成员都是编译器提供的时候。 了解一个班级的特殊成员所做的事情,是了解任何类型的最重要方面之一。

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

    每种类型都有这些问题的答案,最好是提出这些问题&amp;尽快回答的问题。然后你可以更容易地专注于使这种类型与其他类型不同的东西。

答案 1 :(得分:4)

此外,使用=default而不是手动滚动保留了POD的性质,正如此处详细描述的那样:Default constructors and POD

答案 2 :(得分:1)

当您尝试维护rule of 5以确保特殊成员函数按照您的意图行事时,您经常会看到= default,以便班级的读者可以看到您确实考虑过你希望这个功能如何表现。

如果您打算制作不可复制或不可移动的内容,则可以使用= delete。虽然我也看到人们delete是一个继承的函数,如果他们不希望那个特定的派生类具有这个功能,虽然我不是那个人的忠实粉丝,因为它往往指向糟糕的架构/设计