CppCoreGuidelines C.21 是否正确?
Is CppCoreGuidelines C.21 correct?
在阅读 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 规则的例外还是最好在此处定义所有默认操作?还有其他常见的例外情况吗?
我想也许你的第二个例子是一个合理的例外,毕竟指南确实说了"the odds are...",所以会有一些例外。
我想知道这张幻灯片是否对您的第一个示例有帮助:
幻灯片如下:https://accu.org/content/conf2014/Howard_Hinnant_Accu_2014.pdf
编辑:有关第一个案例的更多信息,我后来发现了这个:
我对这条准则有很大的保留意见。即使知道是指南,而不是规则,我仍然有所保留
假设您有一个用户编写的 class 类似于 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);
};
嗯...我不需要move成员,但我需要盲目地按照聪明人告诉我的那样,所以我就删除那些:
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.
坏了。你会发现你第一次尝试 return a SimpleValue
从函数值:
SimpleValue
make_SimpleValue(int i)
{
// do some computations with i
SimpleValue x{i};
// do some more computations
return x;
}
这不会编译。错误消息将说明有关访问 SimpleValue
.
的已删除成员的内容
我有一些更好的指南:
1.
了解编译器何时为您默认或删除特殊成员,以及默认成员将做什么。
这张图表可以帮助解决这个问题:
如果这张图表 far 太复杂,我理解。它 是 复杂的。但是当它一次向你解释一点点时,处理起来就容易多了。 我会希望在一周内用link更新这个答案到我解释这个图表的视频。这是link 的解释,经过比我希望的更长的延迟(我的道歉):https://www.youtube.com/watch?v=vLinb2fgkHk
2.
当编译器的隐式操作不正确时,始终定义或删除特殊成员。
3.
不要依赖已弃用的行为(上图中的红框)。如果声明析构函数、复制构造函数或复制赋值运算符中的任何一个,则同时声明复制构造函数和复制赋值运算符。
4.
从不删除移动成员。如果这样做,充其量它是多余的。在最坏的情况下,它会破坏您的 class(如上面的 SimpleValue
示例所示)。如果您确实删除了 move 成员,并且它是多余的案例,那么您会迫使您的读者不断查看您的 class 以确保它不是损坏的案例。
5.
对6个特殊成员中的每一个都给予温柔的关爱,即使结果是让编译器为你处理(也许通过隐式禁止或删除它们)。
6.
将您的特殊成员以一致的顺序放在 class 的顶部(仅那些您要明确声明的成员),这样您的读者就不必去搜索它们.我有我最喜欢的订单,如果您的首选订单不同,没关系。我的首选顺序是我在 SimpleValue
示例中使用的顺序。
Here is a brief paper with more rationale for this style of class declaration.
在阅读 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 规则的例外还是最好在此处定义所有默认操作?还有其他常见的例外情况吗?
我想也许你的第二个例子是一个合理的例外,毕竟指南确实说了"the odds are...",所以会有一些例外。
我想知道这张幻灯片是否对您的第一个示例有帮助:
幻灯片如下:https://accu.org/content/conf2014/Howard_Hinnant_Accu_2014.pdf
编辑:有关第一个案例的更多信息,我后来发现了这个:
我对这条准则有很大的保留意见。即使知道是指南,而不是规则,我仍然有所保留
假设您有一个用户编写的 class 类似于 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);
};
嗯...我不需要move成员,但我需要盲目地按照聪明人告诉我的那样,所以我就删除那些:
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.
坏了。你会发现你第一次尝试 return a SimpleValue
从函数值:
SimpleValue
make_SimpleValue(int i)
{
// do some computations with i
SimpleValue x{i};
// do some more computations
return x;
}
这不会编译。错误消息将说明有关访问 SimpleValue
.
我有一些更好的指南:
1.
了解编译器何时为您默认或删除特殊成员,以及默认成员将做什么。
这张图表可以帮助解决这个问题:
如果这张图表 far 太复杂,我理解。它 是 复杂的。但是当它一次向你解释一点点时,处理起来就容易多了。 我会希望在一周内用link更新这个答案到我解释这个图表的视频。这是link 的解释,经过比我希望的更长的延迟(我的道歉):https://www.youtube.com/watch?v=vLinb2fgkHk
2.
当编译器的隐式操作不正确时,始终定义或删除特殊成员。
3.
不要依赖已弃用的行为(上图中的红框)。如果声明析构函数、复制构造函数或复制赋值运算符中的任何一个,则同时声明复制构造函数和复制赋值运算符。
4.
从不删除移动成员。如果这样做,充其量它是多余的。在最坏的情况下,它会破坏您的 class(如上面的 SimpleValue
示例所示)。如果您确实删除了 move 成员,并且它是多余的案例,那么您会迫使您的读者不断查看您的 class 以确保它不是损坏的案例。
5.
对6个特殊成员中的每一个都给予温柔的关爱,即使结果是让编译器为你处理(也许通过隐式禁止或删除它们)。
6.
将您的特殊成员以一致的顺序放在 class 的顶部(仅那些您要明确声明的成员),这样您的读者就不必去搜索它们.我有我最喜欢的订单,如果您的首选订单不同,没关系。我的首选顺序是我在 SimpleValue
示例中使用的顺序。
Here is a brief paper with more rationale for this style of class declaration.