In these slides,在幻灯片15中,作者写道“C ++ 11中有许多经典编码规则不再适用”。他提出了三个例子的清单,我同意三条规则和记忆管理。
然而他的第二个例子是“虚拟析构函数与虚拟成员”(就是这样)。 这是什么意思?我知道必须将基类析构函数声明为虚拟,以便在我们有类似
的情况下调用正确的析构函数Base *b = new Derived;
...
delete b;
这里有很好的解释:When to use virtual destructors?
但是如果你有虚拟成员,现在在C ++ 11中声明虚拟你的析构函数是没用的吗?
答案 0 :(得分:36)
作为幻灯片的作者,我将尝试澄清。
如果编写代码使用Derived
显式分配new
实例并使用基类指针使用delete
销毁它,则需要定义virtual
析构函数,否则你最终完全破坏了Derived
实例。但是,我建议完全弃用new
和delete
并仅使用shared_ptr
来引用堆分配的多态对象,例如
shared_ptr<Base> pb=make_shared<Derived>();
这样,共享指针会跟踪要使用的原始析构函数,即使使用shared_ptr<Base>
来表示它也是如此。一旦,最后一个引用shared_ptr
超出范围或被重置,将调用~Derived()
并释放内存。因此,您不需要~Base()
虚拟。
unique_ptr<Base>
和make_unique<Derived>
不提供此功能,因为他们没有提供shared_ptr
关于删除的机制,因为唯一指针更简单,目标是最低开销,因此不存储删除器所需的额外函数指针。使用unique_ptr
,删除函数是类型的一部分,因此带有引用~Derived
的删除符的uniqe_ptr与使用默认删除符的unique_ptr<Base>
不兼容,这对于无论如何,如果~Base
不是虚拟的,那么派生实例。
我提出的个别建议意味着易于遵循并一起遵循。他们尝试通过库组件和编译器生成的代码完成所有资源管理来生成更简单的代码。
在类中定义(虚拟)析构函数将禁止编译器提供的移动构造函数/赋值运算符,并且可能在将来的C ++版本中禁止编译器提供的复制构造函数/赋值运算符。使用=default
可以轻松恢复它们,但仍然看起来像很多样板代码。最好的代码是你不必编写的代码,因为它不会出错(我知道该规则仍有例外)。
总结&#34;不要定义(虚拟)析构函数&#34;作为我的&#34;零度规则&#34;:
的必然结果每当您在现代C ++中设计多态(OO)类层次结构并希望/需要在堆上分配其实例并通过基类指针访问它们时,使用make_shared<Derived>()
来实例化它们并shared_ptr<Base>
保持他们周围。这使您可以保持&#34;零规则&#34;。
这并不意味着你必须分配堆上的所有多态对象。例如,定义一个以(Base&)
为参数的函数,可以使用本地Derived
变量调用而没有问题,并且就Base
的虚拟成员函数而言,它将表现为多态。
在我看来,动态OO多态性在许多系统中严重过度使用。当我们使用C ++时,我们不应该像Java一样编程,除非我们遇到问题,其中使用堆分配对象的动态多态是正确的解决方案。
答案 1 :(得分:2)
我认为这与演示文稿中其他地方提到的“零规则”有关。
如果你只有自动成员变量(即使用shared_ptr
或unique_ptr
作为原始指针的成员),那么你不需要编写自己的副本或移动构造函数或赋值运算符 - 编译器提供的默认值将是最佳的。使用类内初始化,您也不需要默认构造函数。最后,您根本不需要编写析构函数,无论是否虚拟。
答案 2 :(得分:0)
链接的论文显示了相关代码:
std::unique_ptr<Derived> { new Derived };
存储的删除器为std::default_delete<Derived>
,不需要Base::~Base
为虚拟。
<击>
现在,您可以将移动到unique_ptr<Base>
,它也会移动std::default_delete<Derived>
而不将其转换为std::default_delete<Base>
。
击>
答案 3 :(得分:0)
要回答特定问题...
但是,如果您有虚拟成员,那么在C ++ 11中现在将虚拟声明为析构函数是否没用?
在C ++ 11核心语言中,对虚拟析构函数的需求并未改变。如果要使用基本指针删除派生对象,则必须将析构函数声明为虚拟。
幻灯片中的语句给人的印象是C ++ 11以某种方式改变了有关虚拟析构函数的行为-事实并非如此。正如作者所阐明的那样,它仅在使用shared_ptr
时适用。但是,在冗长的解释中,仍然需要使用虚拟析构函数(使用shared_ptr
除外)这一事实。