我知道在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; }
您的问题的答案通常是,但并非总是如此.如果您的抽象类禁止客户端在指向它的指针上调用delete(或者如果它在其文档中这样说),则可以自由地声明虚拟析构函数.
您可以禁止客户端通过使其析构函数受到保护来调用指向它的指针上的delete.像这样工作,省略虚拟析构函数是非常安全和合理的.
您最终将没有虚拟方法表,并最终通过指向它的方式向您的客户发出使其无法删除的意图,因此您确实有理由在这些情况下不将其声明为虚拟.
[见本文第4项:http://www.gotw.ca/publications/mill18.htm ]
我决定做一些研究并尝试总结你的答案.以下问题将帮助您确定所需的析构函数类型:
您的课程是否打算用作基类?
否:声明公共非虚拟析构函数以避免类*的每个对象上的v指针.
是的:阅读下一个问题.
你的基类是抽象的吗?(即任何虚拟纯方法?)
否:尝试通过重新设计类层次结构来使您的基类抽象化
是的:阅读下一个问题.
你想通过基指针允许多态删除吗?
否:声明受保护的虚拟析构函数以防止不必要的使用.
是:声明公共虚拟析构函数(在这种情况下没有开销).
我希望这有帮助.
*重要的是要注意,在C++中没有办法将类标记为final(即非子类),因此在您决定将析构函数声明为非虚拟和公共的情况下,请记住明确警告您的同事程序员来自你的班级.
参考文献:
"S. Meyers.更有效的C++,项目33 Addison-Wesley,1996."
Herb Sutter,Virtuality,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
它并不总是必需的,但我发现这是一种很好的做法.它的作用是允许通过基类型的指针安全地删除派生对象.
例如:
Base *p = new Derived; // use p as you see fit delete p;
如果Base没有虚拟析构函数,则格式错误,因为它会尝试删除对象,就像它是a一样Base
.
这不仅是良好的做法.任何类层次结构都是规则#1.
C++中最基本的层次结构必须具有虚拟析构函数
现在为什么.采取典型的动物等级.虚拟析构函数就像任何其他方法调用一样进行虚拟调度.以下面的例子为例.
Animal* pAnimal = GetAnimal(); delete pAnimal;
假设Animal是一个抽象类.C++知道要调用的正确析构函数的唯一方法是通过虚方法调度.如果析构函数不是虚拟的,那么它只会调用Animal的析构函数而不会破坏派生类中的任何对象.
在基类中使析构函数成为虚拟的原因是它只是从派生类中删除了选择.他们的析构函数默认变为虚拟.