为什么要在C ++中为抽象类声明虚拟析构函数?

时间:2008-11-07 00:55:30

标签: c++ inheritance virtual-destructor

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

7 个答案:

答案 0 :(得分:182)

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

例如

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; 
}

答案 1 :(得分:37)

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

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

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

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

答案 2 :(得分:22)

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

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

    * 重要的是要注意,在C ++中没有办法将类标记为final(即非子类),因此在您决定声明析构函数为非虚拟的情况下公开,记得要明确警告你的同事不要从你的班级派生。

    参考文献:

答案 3 :(得分:7)

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

因此,您正在开启内存泄漏的可能性

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

答案 4 :(得分:7)

这不是总是,但我觉得这是一个好习惯。它的作用是,它允许通过基类型的指针安全地删除派生对象。

例如:

Base *p = new Derived;
// use p as you see fit
delete p;
如果Base没有虚拟析构函数,则

格式不正确,因为它会尝试删除该对象,就好像它是Base *一样。

答案 5 :(得分:5)

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

  1. C ++中最基本的层次结构必须具有虚拟析构函数
  2. 现在为什么。采取典型的动物等级。虚拟析构函数就像任何其他方法调用一样进行虚拟调度。以下面的例子为例。

    Animal* pAnimal = GetAnimal();
    delete pAnimal;
    

    假设Animal是一个抽象类。 C ++知道要调用的正确析构函数的唯一方法是通过虚方法调度。如果析构函数不是虚拟的,那么它只会调用Animal的析构函数而不会销毁派生类中的任何对象。

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

答案 6 :(得分:3)

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

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

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