c++ - CppCoreGuidelines C.21 是否正确?

标签 c++ c++11 cpp-core-guidelines rule-of-three rule-of-five

在阅读 Bjarne Stroustrup 的 CoreCppGuidelines 时,我发现了一个与我的经验相矛盾的指南。

C.21需要以下内容:

If you define or =delete any default operation, define or =delete them all

原因如下:

The semantics of the special functions are closely related, so if one needs to be non-default, the odds are that others need modification too.

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

#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 规则的异常(exception),还是最好在此处定义所有默认操作?还有其他常见的异常(exception)吗?

最佳答案

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

假设您有一个类似于 std::complex<double> 的用户编写的类, 或 std::chrono::seconds .它只是一个值类型。它不拥有任何资源,它很简单。假设它有一个非特殊成员的构造函数。

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

嗯,我也想要SimpleValue是默认可构造的,并且我通过提供另一个构造函数来禁止默认构造函数,所以我需要添加那个特殊成员:

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. 从不删除移动成员。如果这样做,充其量只是多余的。在最坏的情况下,它会破坏你的类(class)(如上面的 SimpleValue 示例)。如果您确实删除了 move 成员,并且这是多余的案例,那么您会迫使您的读者不断审查您的类(class),以确保它不是损坏的案例。

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

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

Here is a brief paper with more rationale for this style of class declaration.

关于c++ - CppCoreGuidelines C.21 是否正确?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38682675/

相关文章:

c++ - 覆盖 c 字符串时发生访问冲突

c++ - 用其他东西替换宏中的 goto

c++ - 使用 std::mutex 关闭头文件的 clr 选项

c++ - 我应该在新的 C++ 项目中使用指南支持库 (GSL) 吗?

c++ - gsl::array_view<const gsl::cstring_view<>> 来自 std::vector<std::string>

c++ - 写入输出文件

c++ - 使用 decltype() 和三元运算符有条件地选择类型

c++ - 模板继承层次结构中的循环依赖

c++ - fatal error C1001 : An internal error has occurred in the compiler

c++ - 快速算法 : No Corner Detection in Rectangular shapes