关于继承:为什么我要在C ++中为抽象类声明一个虚拟析构函数?

Why should I declare a virtual destructor for an abstract class in C++?

我知道在C++中声明基类的虚拟析构函数是一个很好的做法,但是声明EDCOX1和0个析构函数是否总是重要的,即使对于用作接口的抽象类也是如此吗?请提供一些原因和示例。


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

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}


你的问题的答案经常,但不总是。如果抽象类禁止客户机在指向它的指针上调用delete(或者如果它在文档中这样说),则可以不声明虚拟析构函数。

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

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

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


我决定做些调查,并试图总结你的答案。以下问题将帮助您决定需要哪种类型的析构函数:

  • 您的类是否打算用作基类?
    • 否:声明公共的非虚拟析构函数以避免类*的每个对象上出现V指针。
    • 是:阅读下一个问题。
  • 你的基类是抽象的吗?(即任何虚拟纯方法?)
    • 否:尝试通过重新设计类层次结构使基类抽象化。
    • 是:阅读下一个问题。
  • 是否允许通过基指针进行多态删除?
    • 否:声明受保护的虚拟析构函数以防止不需要的使用。
    • 是:声明公共虚拟析构函数(在本例中没有开销)。
  • 我希望这有帮助。

    *重要的是要注意,在C++中没有办法将一个类标记为最终的(即非子类),因此,在您决定声明析构函数非虚的和公共的情况下,请记住显式地警告您的程序员不要从类派生。

    参考文献:

    • "S. Meyers。更有效的C++,项目33 Addison Wesley,1996。
    • Herb Sutter,虚拟,2001年
    • C++ FAQ,20.7,"我的析构函数何时应该是虚拟的?"
    • 当然,这个问题的答案。


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

    例如:

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

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


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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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


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

  • C++中的最底层的层次结构必须有一个虚拟析构函数。
  • 为什么呢?以典型的动物等级为例。虚拟析构函数和其他方法调用一样执行虚拟调度。以下面的例子为例。

    1
    2
    Animal* pAnimal = GetAnimal();
    delete pAnimal;

    假设动物是一个抽象类。C++知道正确的析构函数调用的唯一方法是通过虚拟方法调度。如果析构函数不是虚拟的,那么它将简单地称为动物的析构函数,而不销毁派生类中的任何对象。

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


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

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

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