為什么析構函數要聲明成virtual呢?
因為,如果delete一個基類的指針時, 如果它指向的是一個子類的對象,那么析構函數不為虛就會導致無法調用子類析構函數,從而導致資源泄露。
如果一個類要被使用成多態的,那么這個virtual是必須的。比如:
#include <iostream>
using namespace std;
class Animal
{
char* ap;
public:
Animal()
{
ap = new char;
std::cout << "Animal ctor" << std::endl;
}
virtual void foo()
{
std::cout << "Animal::foo" << std::endl;
}
virtual ~Animal()
{
std::cout << "Animal dtor" << std::endl;
delete ap;
}
};
class Dog : public Animal
{
char* dp;
public:
Dog()
{
dp = new char;
std::cout << "Dog ctor" << std::endl;
}
virtual void foo()
{
std::cout << "Dog::foo" << std::endl;
}
virtual ~Dog()
{
delete dp;
std::cout << "Dog dtor" << std::endl;
}
};
int main()
{
Animal* pa = new Dog();
pa->foo();
delete pa;
return 0;
}
delete pa 實際上相當於:
pa->~Animal();
釋放pa所指向的內存
在這里,因為~Animal()是virtual的,盡管是通過Animal類型的指針調用的,根據虛表v-table的信息,~Dog()被正確調用到。如果把virtual屬性去掉,那么被調用的是~Animal(),Dog類的構造函數被調用而析構函數未被調用,構造函數中分配的資源沒有釋放,從而產生了內存泄漏。析構函數缺省聲明為virtual,就可以避免這一問題。
如果基類析構函數不加virtual,運行效果如下:
Animal ctor
Dog ctor
Dog::foo
Animal dtor
可另一個問題是,有時virtual是不需要的。
如果一個類不會被繼承,比如一個utility類,該類完全是靜態方法;
或者一些類盡管可能會被繼承,但不會被使用成多態的,即除了析構函數外,沒有其他的方法是virtual的,這時就可以把virtual屬性去掉。
去掉析構函數的virtual屬性后,因為該類中沒有其他的virtual函數,所以編譯時不會生成v-table,這樣就節省了編譯時間,並減少了最終生成的程序的大小。更重要的是,遵從這一規則,給該類的維護者一個信息,即該類不應被當作多態類使用。
同樣,當作一個抽象時,如果你模仿Java的interface,聲明了如下的虛基類:
class AbstractBase
{
virtual method1() = 0;
virtual method2() = 0;
};
那么應該給它增加一個空的virtual析構函數:
virtual ~AbstractBase(){}
如果你對COM比較熟悉,可能會注意到,COM interface中並沒有這個virutal構造函數。這是因為,COM通過使用引用計數的機制來維護對象。當你使用完一個COM對象,調用Release()時,COM的內部實現檢查引用技術是否為零。如果是,則調用:
delete this;
因為Release()是virtual的,所以該COM對象對應的正確的派生類被調用,delete this會調用正確的析構函數,達到了使用virtual析構函數的效果。
