ale*_*in0 13 c++ oop c++11 cpp-core-guidelines
在阅读Bjarne Stroustrup的CoreCppGuidelines时,我找到了一条与我的经历相矛盾的指南.
该C.21要求如下:
如果您定义或
=delete任何默认操作,请定义或=delete全部
原因如下:
特殊函数的语义密切相关,因此如果需要非默认函数,则其他人也需要修改.
根据我的经验,重新定义默认操作的两种最常见情况如下:
#1:使用默认主体定义虚拟析构函数以允许继承:
class C1
{
...
virtual ~C1() = default;
}
Run Code Online (Sandbox Code Playgroud)
#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))
{}
}
Run Code Online (Sandbox Code Playgroud)
根据我的经验,所有其他情况都很少见.
您如何看待这些例子?它们是C.21规则的例外,还是最好在这里定义所有默认操作?还有其他常见的例外吗?
我对本指南有很大的保留意见.即使知道这是一个指导方针,而不是一个规则,我仍然有所保留.
假设您有一个类似于std::complex<double>或的用户编写的类std::chrono::seconds.它只是一种价值类型.它没有任何资源,它意味着简单.假设它有一个非特殊成员构造函数.
class SimpleValue
{
int value_;
public:
explicit SimpleValue(int value);
};
Run Code Online (Sandbox Code Playgroud)
好吧,我也想SimpleValue成为默认构造,并且我通过提供另一个构造函数来禁止默认构造函数,所以我需要添加该特殊成员:
class SimpleValue
{
int value_;
public:
SimpleValue();
explicit SimpleValue(int value);
};
Run Code Online (Sandbox Code Playgroud)
我担心人们会记住这个指导原则:因为我提供了一个特殊的成员,我应该定义或删除其余的,所以这里......
class SimpleValue
{
int value_;
public:
~SimpleValue() = default;
SimpleValue();
SimpleValue(const SimpleValue&) = default;
SimpleValue& operator=(const SimpleValue&) = default;
explicit SimpleValue(int value);
};
Run Code Online (Sandbox Code Playgroud)
嗯......我不需要移动成员,但我需要盲目跟随聪明人告诉我的内容,所以我只是删除那些:
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);
};
Run Code Online (Sandbox Code Playgroud)
我担心CoreCppGuidelines C.21将导致大量代码看起来像这样.为什么那么糟糕?有几个原因:
1. 这比正确版本更难阅读:
class SimpleValue
{
int value_;
public:
SimpleValue();
explicit SimpleValue(int value);
};
Run Code Online (Sandbox Code Playgroud)
2. 它坏了.您第一次尝试SimpleValue按值返回函数时会发现:
SimpleValue
make_SimpleValue(int i)
{
// do some computations with i
SimpleValue x{i};
// do some more computations
return x;
}
Run Code Online (Sandbox Code Playgroud)
这不会编译.该错误消息将说明访问已删除的成员SimpleValue.
我有一些更好的指导方针:
1. 知道编译器何时为您默认或删除特殊成员,以及默认成员将执行哪些操作.
此图表可以帮助:

如果这张图是远太复杂了,我明白了.这很复杂.但是当它一次向你解释时,它更容易处理. 我希望能在一周之内更新这个答案,并附上一个解释此图表的视频链接. 这是解释的链接,经过比我想要的更长的延迟(我的道歉):https://www.youtube.com/watch?v = vLinb2fgkHk
2. 当编译器的隐式操作不正确时,始终定义或删除特殊成员.
3. 不要依赖于弃用的行为(上图中的红色框).如果声明任何析构函数,复制构造函数或复制赋值运算符,则声明复制构造函数和复制赋值运算符.
4. 永远不要删除移动成员.如果你这样做,充其量就是多余的.在最坏的情况下,它会打破你的课程(如上SimpleValue例所示).如果你确实删除了移动成员,并且这是多余的情况,那么你强迫读者不断审查你的课程,以确保它不是破案.
5. 即使结果是让编译器为您处理它(可能通过隐式地禁止或删除它们),也要对6个特殊成员中的每一个给予温柔的关爱.
6. 将您的特殊成员放在班级顶部的一致顺序中(只有您要明确声明的那些),以便您的读者不必去搜索它们.我有我最喜欢的订单,如果您的首选订单不同,那很好.我首选的顺序是我在SimpleValue示例中使用的顺序.
我想也许你的第二个例子是一个合理的例外,毕竟,指南确实说"赔率是......",所以会有一些例外.
我想知道这张幻灯片是否有助于你的第一个例子:
以下是幻灯片:https://accu.org/content/conf2014/Howard_Hinnant_Accu_2014.pdf
编辑:有关第一种情况的进一步信息,我发现这一点:C++ 11虚拟析构函数和移动特殊函数的自动生成