在C++ 11中使用虚拟成员的虚拟析构函数

Flo*_*oux 30 c++ virtual-destructor c++11

在这些关于C++ 11/14标准的幻灯片中,在幻灯片15中,作者写道"C++ 11中的许​​多经典编码规则不再适用".他提出了三个例子清单,我同意三条规则和内存管理.

然而他的第二个例子是"虚拟析构函数与虚拟成员"(就是这样).这是什么意思?我知道必须声明为基类析构函数的虚拟,以便在我们有类似的东西时调用正确的析构函数

Base *b = new Derived;
...
delete b;
Run Code Online (Sandbox Code Playgroud)

这里有很好的解释:什么时候使用虚拟析构函数?

但是,如果你有虚拟成员,现在在C++ 11中声明虚拟你的析构函数是没用的吗?

Pet*_*Som 36

作为幻灯片的作者,我将尝试澄清.

如果您编写代码显式分配Derived实例newdelete使用基类指针销毁它,那么您需要定义virtual析构函数,否则您最终会完全破坏Derived实例.但是,我建议弃用newdelete完全shared_ptr用于引用堆分配的多态对象,例如

shared_ptr<Base> pb=make_shared<Derived>();
Run Code Online (Sandbox Code Playgroud)

这样,共享指针跟踪要使用的原始析构函数,即使shared_ptr<Base>用于表示它.一旦,最后一个引用shared_ptr超出范围或被重置,~Derived()将被调用并释放内存.因此,您不需要进行~Base()虚拟.

unique_ptr<Base>并且make_unique<Derived>不提供此功能,因为它们不提供shared_ptr删除器相关的机制,因为唯一指针更简单并且旨在实现最低开销,因此不存储删除器所需的额外函数指针.由于unique_ptr删除函数是类型的一部分,因此带有删除器引用的uniqe_ptr与使用默认删除器的~Derived不兼容unique_ptr<Base>,如果~Base不是虚拟的话,这对于派生实例来说是错误的.

我提出的个别建议意味着易于遵循并一起遵循.他们尝试通过让库组件和编译器生成的代码完成所有资源管理来生成更简单的代码.

在类中定义(虚拟)析构函数将禁止编译器提供的移动构造函数/赋值运算符,并且可能在将来的C++版本中禁止编译器提供的复制构造函数/赋值运算符.恢复它们变得很容易=default,但仍然看起来像很多样板代码.最好的代码是您不必编写的代码,因为它不会出错(我知道该规则仍有例外).

总结"不要定义(虚拟)析构函数"作为我的"零度规则"的推论:

每当您在现代C++中设计多态(OO)类层次结构并希望/需要在堆上分配其实例并通过基类指针访问它们时,make_shared<Derived>()用于实例化它们并shared_ptr<Base>保持它们.这允许您保持"零度规则".

这并不意味着您必须在堆上分配所有多态对象.例如,定义一个带有(Base&)as参数的函数,可以使用局部Derived变量调用而没有问题,并且就虚拟成员函数而言,它将表现为多态Base.

在我看来,动态OO多态性在许多系统中严重过度使用.当我们使用C++时,我们不应该像Java一样编程,除非我们遇到问题,其中使用堆分配对象的动态多态是正确的解决方案.

  • 我认为不为一个旨在以多态方式使用的类定义虚拟析构函数会给类的用户带来很大的负担——他们被严格要求用 shared_ptr 来保存它们。但是 shared_ptr 非常不鼓励并被认为是过度使用的,应该尽可能用 unique_ptr 替换。所以我相信不定义虚拟析构函数会导致比接受必须将复制和移动构造函数和赋值运算符标记为 =default 的事实更糟糕的问题。我认为 C++11 没有改变何时以及如何使用虚拟析构函数。 (2认同)
  • 这似乎不是一个很好的建议 - 您在类声明中节省了微不足道的(心理)开销,以换取通过以一种相当意外的方式限制客户端使用来强加非微不足道的(心理)开销。当一个对象被销毁时,您还需要交换一次虚拟查找的小开销,而...一旦对象被销毁,您将进行一次小的虚拟查找。这对我来说似乎没什么帮助。 (2认同)