提问人:alexeykuzmin0 提问时间:7/31/2016 最后编辑:user3840170alexeykuzmin0 更新时间:2/10/2022 访问量:1841
CppCoreGuidelines C.21 是否正确?
Is CppCoreGuidelines C.21 correct?
问:
在阅读 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 规则的例外,还是最好在此处定义所有默认操作?还有其他常见的例外情况吗?
答:
我认为也许你的第二个例子是一个合理的例外,毕竟,指南确实说“可能性是......”,所以会有一些例外。
我想知道这张幻灯片是否有助于您的第一个示例:
以下是幻灯片: https://accu.org/content/conf2014/Howard_Hinnant_Accu_2014.pdf
编辑:有关第一种情况的更多信息,我后来发现了这个:C++11虚拟析构函数和移动特殊函数的自动生成
评论
我对这一准则有很大的保留意见。即使知道这是一个指导方针,而不是一个规则,我仍然有所保留。
假设您有一个类似于 或 的用户编写的类。它只是一种值类型。它不拥有任何资源,它意味着简单。假设它有一个非特殊成员构造函数。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.
它坏了。您会发现第一次尝试按值从函数返回 a 时:SimpleValue
SimpleValue
make_SimpleValue(int i)
{
// do some computations with i
SimpleValue x{i};
// do some more computations
return x;
}
这不会编译。错误消息将说明有关访问 的已删除成员的信息。SimpleValue
我有一些更好的指导方针:
1.
了解编译器何时默认或删除特殊成员,以及默认成员将执行哪些操作。
此图表可以帮助解决以下问题:
如果这张图表太复杂了,我理解。这很复杂。但是,当一次向您解释一点点时,处理起来就容易多了。我希望能在一周内更新这个答案,并附上我解释这张图表的视频链接。这是解释的链接,经过比我想要的更长的时间(我很抱歉):https://www.youtube.com/watch?v=vLinb2fgkHk
2.
当编译器的隐式操作不正确时,始终定义或删除特殊成员。
3.
不要依赖于已弃用的行为(上图中的红色框)。如果声明任何析构函数、复制构造函数或复制赋值运算符,则同时声明复制构造函数和复制赋值运算符。
4.
切勿删除移动成员。如果你这样做,充其量是多余的。在最坏的情况下,它会破坏你的类(如上面的例子)。如果你确实删除了移动成员,并且是多余的大小写,那么你就会强迫你的读者不断查看你的类,以确保它不是损坏的大小写。SimpleValue
5.
对 6 个特殊成员中的每一个都给予温柔的关怀,即使结果是让编译器为你处理它(也许是通过隐式地抑制或删除它们)。
6.
将你的特殊成员按一致的顺序放在你的班级的顶部(只有那些你想明确声明的成员),这样你的读者就不必去搜索他们了。我有我最喜欢的订单,如果你喜欢的订单不同,很好。我的首选顺序是我在示例中使用的顺序。SimpleValue
评论
SimpleValue() = default;
int value_ = 42;
SimpleValue() = default;
SimpleValue
SimpleValue() = default;
value_
int value_ = 0;
SimpleValue
SimpleValue x;
x.value_
SimpleValue x{};
std::chrono::duration
define
=default
=default
define
评论
std::unique_ptr
std::unique_ptr<int>
x