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

我知道在 C + + 中为基类声明虚析构函数是一种很好的做法,但是声明 virtual析构函数总是重要的吗,即使是对于作为接口的抽象类也是如此吗?请提供一些原因和例子为什么。

87576 次浏览

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

比如说

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

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

因此,您正在打开潜在的内存泄漏

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

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

例如:

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

如果 Base没有虚析构函数,那么它就是格式不正确的,因为它会试图删除对象,就好像它是 Base *一样。

这不仅是一个好的实践,而且是任何类层次结构的第一条规则。

  1. C + + 中层次结构的大多数基类必须有一个虚析构函数

现在说说为什么。以典型的动物等级制度为例。虚析构函数与任何其他方法调用一样通过虚分派。以下面的例子为例。

Animal* pAnimal = GetAnimal();
delete pAnimal;

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

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

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

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

最终,您将没有虚方法表,并且最终通过一个指向它的指针向您的客户发出信号,表明您希望使其不可删除,因此在这种情况下,您确实有理由不声明它为虚方法表。

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

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

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

希望这个能帮上忙。

需要注意的是,在 C + + 中没有办法将类标记为 final (即不可子类化) ,所以在你决定声明析构函数非虚和公共的情况下,记得明确警告你的程序员同事不要从你的类派生。

参考文献:

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

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

您更喜欢上面的删除,但是如果基类的析构函数不是虚的,那么将只调用基类的析构函数,并且派生类中的所有数据将保持不删除。