172

我知道在 C++ 中为基类声明虚拟析构函数是一个好习惯,但是virtual即使对于充当接口的抽象类,声明析构函数也总是很重要吗?请提供一些原因和例子。

4

7 回答 7

205

对于界面而言,它甚至更为重要。您类的任何用户都可能持有指向接口的指针,而不是指向具体实现的指针。当他们来删除它时,如果析构函数是非虚拟的,他们将调用接口的析构函数(或编译器提供的默认值,如果你没有指定),而不是派生类的析构函数。即时内存泄漏。

例如

class Interface
{
   virtual void doSomething() = 0;
};

class Derived : public Interface
{
   Derived();
   ~Derived() 
   {
      // Do some important cleanup...
   }
};

void myFunc(void)
{
   Interface* p = new Derived();
   // The behaviour of the next line is undefined. It probably 
   // calls Interface::~Interface, not Derived::~Derived
   delete p; 
}
于 2008-11-07T01:01:13.157 回答
37

您的问题的答案通常是,但并非总是如此。如果您的抽象类禁止客户端在指向它的指针上调用 delete(或者如果它在其文档中这样说),您可以自由地不声明虚拟析构函数。

您可以通过保护其析构函数来禁止客户端对指向它的指针调用 delete。像这样工作,省略虚拟析构函数是完全安全和合理的。

您最终将没有虚拟方法表,并最终向您的客户表明您打算通过指向它的指针使其不可删除,因此在这些情况下您确实有理由不将其声明为虚拟。

[参见本文第 4 项:http ://www.gotw.ca/publications/mill18.htm ]

于 2008-11-07T02:39:47.980 回答
24

我决定做一些研究并尝试总结你的答案。以下问题将帮助您决定需要哪种析构函数:

  1. 您的类是否打算用作基类?
    • 否:声明公共非虚拟析构函数以避免类*的每个对象上的 v 指针。
    • 是:阅读下一个问题。
  2. 你的基类是抽象的吗?(即任何虚拟纯方法?)
    • 否:尝试通过重新设计类层次结构来使您的基类抽象
    • 是:阅读下一个问题。
  3. 你想允许通过基指针进行多态删除吗?
    • 否:声明受保护的虚拟析构函数以防止不必要的使用。
    • 是:声明公共虚拟析构函数(在这种情况下没有开销)。

我希望这有帮助。

*重要的是要注意,在 C++ 中没有办法将类标记为最终类(即不可子类化),因此如果您决定将析构函数声明为非虚拟和公共的,请记住明确警告您的程序员同事不要源自您的课程。

参考:

于 2011-07-09T11:18:21.487 回答
11

是的,它总是很重要。派生类可以分配内存或持有对在对象被销毁时需要清理的其他资源的引用。如果您不为接口/抽象类提供虚拟析构函数,那么每次通过基类句柄删除派生类实例时,都不会调用派生类的析构函数。

因此,您打开了内存泄漏的可能性

class IFoo
{
  public:
    virtual void DoFoo() = 0;
};

class Bar : public IFoo
{
  char* dooby = NULL;
  public:
    virtual void DoFoo() { dooby = new char[10]; }
    void ~Bar() { delete [] dooby; }
};

IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted
于 2008-11-07T01:01:39.147 回答
7

这并不总是必需的,但我发现这是一种很好的做法。它的作用是允许通过基类型的指针安全地删除派生对象。

例如:

Base *p = new Derived;
// use p as you see fit
delete p;

如果Base没有虚拟析构函数,则格式错误,因为它将尝试删除对象,就好像它是一个Base *.

于 2008-11-07T01:02:46.927 回答
5

这不仅是好的做法。这是任何类层次结构的规则#1。

  1. C ++中层次结构的最基类必须具有虚拟析构函数

现在来看看为什么。以典型的动物等级为例。虚拟析构函数像任何其他方法调用一样通过虚拟调度。举个例子。

Animal* pAnimal = GetAnimal();
delete pAnimal;

假设 Animal 是一个抽象类。C++ 知道要调用的正确析构函数的唯一方法是通过虚拟方法分派。如果析构函数不是虚拟的,那么它将简单地调用 Animal 的析构函数而不破坏派生类中的任何对象。

在基类中使析构函数为虚拟的原因是它只是从派生类中删除了选择。默认情况下,它们的析构函数变为虚拟的。

于 2008-11-07T01:03:13.213 回答
4

答案很简单,你需要它是虚拟的,否则基类将不是一个完整的多态类。

    Base *ptr = new Derived();
    delete ptr; // Here the call order of destructors: first Derived then Base.

您更喜欢上述删除,但如果基类的析构函数不是虚拟的,则只会调用基类的析构函数,派生类中的所有数据都将保持未删除状态。

于 2012-12-27T14:27:55.343 回答