为什么要在C ++中声明一个抽象类的虚析构函数?
我知道在C ++中为基类声明虚析构函数是一个很好的习惯,但是对于作为接口起作用的抽象类声明virtual
析构函数总是很重要的。 请提供一些原因和示例。
接口更重要。 你的类的任何用户可能会持有一个指向接口的指针,而不是指向具体实现的指针。 当它们删除它时,如果析构函数是非虚的,它们将调用接口的析构函数(或者编译器提供的默认值,如果没有指定的话),而不是派生类的析构函数。 即时内存泄漏。
例如
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; }
你的问题的答案往往是,但并非总是如此。 如果你的抽象类禁止客户调用指向它的指针(或者如果它在文档中这样说),你可以自由地声明一个虚拟析构函数。
您可以禁止客户端通过使其析构函数受到保护来调用指向它的指针。 像这样工作,省略虚拟析构函数是完全安全合理的。
你最终将没有虚拟方法表,并最终告诉你的客户你的意图是通过一个指针来使它不可删除,所以你确实有理由不在这些情况下声明它是虚拟的。
[见本文第4条: http : //www.gotw.ca/publications/mill18.htm ]
我决定做一些研究,并试图总结你的答案。 下面的问题将帮助你决定你需要什么样的析构函数:
- 你的类是否被用作基类?
- 否:声明公共的非虚拟析构函数,以避免类的每个对象上的v指针* 。
- 是的:阅读下一个问题。
- 你的基类是抽象的吗? (即任何虚拟的纯粹的方法?)
- 否:尝试通过重新devise您的类层次结构来使您的基类抽象化
- 是的:阅读下一个问题。
- 你想通过基指针来允许多态删除吗?
- 否:声明受保护的虚拟析构函数以防止不需要的使用。
- 是的:声明公共虚拟析构函数(在这种情况下没有开销)。
我希望这有帮助。
*重要的是要注意,在C ++中没有办法将类标记为final(即非子类),所以如果您决定将析构函数声明为非虚拟的和公共的,请记得明确地警告您的程序员派生自你的class级。
参考文献:
- “S. Meyers。More Effective C ++,Item 33 Addison-Wesley,1996.”
- 香草萨特,虚拟,2001年
- C ++ Faq,20.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
这不仅是好的做法。 任何类层次结构都是规则1。
- C ++中最基本的层次结构必须具有虚拟析构函数
现在为什么。 以典型的动物层次结构。 虚拟析构函数像其他方法调用一样通过虚拟调度。 以下面的例子。
Animal* pAnimal = GetAnimal(); delete pAnimal;
假设动物是一个抽象类。 C ++知道正确的析构函数调用的唯一方法是通过虚拟方法调度。 如果析构函数不是虚拟的,那么它将简单地调用Animal的析构函数,而不会销毁派生类中的任何对象。
在基类中使析构函数虚拟化的原因是,它只是从派生类中删除select。 它们的析构函数默认为虚拟的。
凯文先生,请不要在我们以前的讨论中亲自跳过你的评论,我也不打算这么苛刻地下台。 无论如何,这个问题的主要答案。
这并不总是要求,但我觉得这是一个很好的做法。 它所做的是允许派生的对象通过基types的指针被安全地删除。
举个例子:
Base *p = new Derived; // use p as you see fit delete p;
如果Base没有一个虚拟的析构函数,那么它是不合格的,因为它会试图删除这个对象,就好像它是一个Base *
。
答案很简单,你需要它是虚拟的,否则基类不会是一个完整的多态类。
Base *ptr = new Derived(); delete ptr; // Here the call order of destructors: first Derived then Base.
你会喜欢上面的删除,但是如果基类的析构函数不是虚拟的,那么只有基类的析构函数会被调用,派生类中的所有数据都将被取消删除。
不总是:
class Interface { virtual void doSomething(void) = 0; // uncomment this if derived classes allocate memory... // ...or have members with destructors. //CHECK: virtual ~Interface() {} }; class Derived : public Interface { virtual void doSomething(void) { variable = 100; } int variable; }; class DerivedOther : public Interface { DerivedOther(const Something &ref) : ref(ref) {} virtual void doSomething(void) { ref.member = 100; } Something &ref; }; void myFunc(void) { Interface* p = new Derived(); delete p; // calls Interface::~Interface, not Derived::~Derived (not a problem) }