作为幻灯片的作者,我将尝试澄清。
如果您编写代码明确分配使用基类指针销毁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
已经变得很容易恢复它们,但看起来仍然像是大量的样板代码。最好的代码是你不必写的代码,因为它不会出错(我知道这个规则仍然有例外)。
总结下我的“零规则”的“不要定义(虚拟)析构函数”为推论:
每当您在现代 C++ 中设计多态(OO)类层次结构并希望/需要在堆上分配其实例并通过基类指针访问它们时,请使用 make_shared<Derived>()
实例化它们并使用 shared_ptr<Base>
使它们保持活动状态。这允许您保持“零规则”。
这并不意味着您必须在堆上分配所有多态对象。例如,定义一个以 (Base&)
作为参数的函数,可以使用本地 Derived
变量调用而没有问题,并且对于 Base
的虚拟成员函数,它将表现出多态行为。
在我看来,动态 OO 多态性在许多系统中被过度使用。我们不应该像 Java 一样编程,除非我们有一个问题,需要使用堆分配的对象进行动态多态性解决方案。
shared_ptr
时很容易实现。 - juanchopanzaFoo(Foo&&) = default;
,它就可以正常工作了。并且因为你的所有成员都会自我清理,所以你也可以默认析构函数。这需要一种不同的类设计方法,但这正是Sommerlad教授在这些幻灯片中提倡的方法。(虚拟位我不确定,我会问他。) - Jonathan Wakely