CppCoreGuidelines C.21是否正确?

时间:2016-07-31 09:41:26

标签: c++ oop c++11 cpp-core-guidelines

在阅读Bjarne Stroustrup的CoreCppGuidelines时,我找到了一条与我的经历相矛盾的指南。

C.21需要以下内容:

  

如果您定义或=delete任何默认操作,请定义或=delete全部

出于以下原因:

  

特殊函数的语义密切相关,因此如果需要非默认函数,则其他人也需要修改。

根据我的经验,重新定义默认操作的两种最常见情况如下:

#1:使用默认主体定义虚拟析构函数以允许继承:

class C1
{
...
    virtual ~C1() = default;
}

#2:定义RAII类型成员的默认构造函数的定义:

class C2
{
public:
    int a; float b; std::string c; std::unique_ptr<int> x;

    C2() : a(0), b(1), c("2"), x(std::make_unique<int>(5))
    {}
}

根据我的经验,所有其他情况都很少见。

您如何看待这些例子?它们是C.21规则的例外,还是在这里定义所有默认操作更好?还有其他常见的例外吗?

2 个答案:

答案 0 :(得分:9)

我对本指南有很大的保留意见。即使知道它是指南,而不是规则,我仍然有保留。

假设您有一个类似于std::complex<double>std::chrono::seconds的用户编写的类。它只是一种价值类型。它没有任何资源,它意味着简单。让我们说它有一个非特殊成员构造函数。

class SimpleValue
{
    int value_;
public:
    explicit SimpleValue(int value);
};

好吧,我也希望SimpleValue是默认的可构造的,并且我通过提供另一个构造函数来禁止默认构造函数,所以我需要添加特殊成员:< / p>

class SimpleValue
{
    int value_;
public:
    SimpleValue();
    explicit SimpleValue(int value);
};

我担心人们会记住这个指南和理由:嗯,既然我已经提供了一个特殊的成员,我应该定义或删除其余成员,所以这里就是......

class SimpleValue
{
    int value_;
public:
    ~SimpleValue() = default;
    SimpleValue();
    SimpleValue(const SimpleValue&) = default;
    SimpleValue& operator=(const SimpleValue&) = default;

    explicit SimpleValue(int value);
};
嗯......我不需要移动成员,但我需要盲目跟随聪明人告诉我的内容,所以我只是删除那些:

class SimpleValue
{
    int value_;
public:
    ~SimpleValue() = default;
    SimpleValue();
    SimpleValue(const SimpleValue&) = default;
    SimpleValue& operator=(const SimpleValue&) = default;
    SimpleValue(SimpleValue&&) = delete;
    SimpleValue& operator=(SimpleValue&&) = delete;

    explicit SimpleValue(int value);
};

我担心CoreCppGuidelines C.21会导致大量代码看起来像这样。为什么那么糟糕?有几个原因:

1.这比这个正确的版本更难阅读:

class SimpleValue
{
    int value_;
public:
    SimpleValue();
    explicit SimpleValue(int value);
};

2. 已破坏。您第一次尝试按值从函数返回SimpleValue时会发现:

SimpleValue
make_SimpleValue(int i)
{
    // do some computations with i
    SimpleValue x{i};
    // do some more computations
    return x;
}

这不会编译。该错误消息将说明有关访问已删除的SimpleValue成员的信息。

我有更好的指导方针:

1.知道编译器何时为您默认或删除特殊成员,以及默认成员会做什么。

此图表可以帮助您:

enter image description here

如果此图表过于复杂,我理解。它复杂的。但是当它一次向你解释时,它更容易处理。 我希望希望在一周内更新这个答案,并附上解释此图表的视频链接。这是解释的链接,经过比我更长的延迟本来希望(道歉):https://www.youtube.com/watch?v=vLinb2fgkHk

2.当编译器的隐式操作不正确时,始终定义或删除特殊成员。

3.不要依赖弃用的行为(上图中的红框)。如果声明任何析构函数,复制构造函数或复制赋值运算符,则声明复制构造函数和复制赋值运算符。

4. 从不删除移动成员。如果你这样做,充其量就是多余的。在最坏的情况下,它会破坏你的课程(如上面的SimpleValue示例)。如果您确实删除了移动成员,并且这是多余的情况,那么您强迫读者不断审查您的课程,以确保它不是破案。

5.对6个特殊成员中的每一个给予温柔的关怀,即使结果是让编译器为你处理它(可能通过隐式地禁止或删除它们)。

6.将您的特殊成员放在课程顶部的一致顺序中(只有您要明确声明的那些),以便您的读者不必去搜索它们。我订购了我最喜欢的订单,如果您的首选订单不同,那很好。我首选的订单是我在SimpleValue示例中使用的订单。

答案 1 :(得分:6)

我想也许你的第二个例子是一个合理的例外,毕竟,指南确实说&#34;赔率是......&#34;,所以会有一些例外。

我想知道这张幻灯片是否有助于你的第一个例子:

Special Members

以下是幻灯片:https://accu.org/content/conf2014/Howard_Hinnant_Accu_2014.pdf

编辑:有关第一个案例的进一步信息,我发现了这一点:C++11 virtual destructors and auto generation of move special functions