構造函數不能聲明爲虛函數,析構函數可以聲明爲虛函數,而且有時是必須聲明爲虛函數。
不建議在構造函數和析構函數裏面調用虛函數。
構造函數不能聲明爲虛函數的原因是:
解釋一:所謂虛函數就是多態情況下只執行一個。而從繼承的概念來講,總是要先構造父類對象,然後才能是子類對象。如果構造函數設爲虛函數,那麼當你在構造父類的構造函數時就不得不顯示的調用構造。還有一個原因就是爲了防錯,試想如果你在子類中一不小心重寫了個跟父類構造函數一樣的函數,那麼你的父類的構造函數將被覆蓋,也即不能完成父類的構造.就會出錯。
解釋二:虛函數的主要意義在於被派生類繼承從而產生多態。派生類的構造函數中,編譯器會加入構造基類的代碼,如果基類的構造函數用到參數,則派生類在其構造函數的初始化列表中必須爲基類給出參數,就是這個原因。
虛函數的意思就是開啓動態綁定,程序會根據對象的動態類型來選擇要調用的方法。然而在構造函數運行的時候,這個對象的動態類型還不完整,沒有辦法確定它到底是什麼類型,故構造函數不能動態綁定。(動態綁定是根據對象的動態類型而不是函數名,在調用構造函數之前,這個對象根本就不存在,它怎麼動態綁定?)
編譯器在調用基類的構造函數的時候並不知道你要構造的是一個基類的對象還是一個派生類的對象。
析構函數設爲虛函數的作用:
解釋:在類的繼承中,如果有基類指針指向派生類,那麼用基類指針delete時,如果不定義成虛函數,派生類中派生的那部分無法析構。
例:
#include "stdafx.h"
#include "stdio.h"
class A
{
public:
A();
virtual ~A();
};
A::A()
{
}
A::~A()
{
printf("Delete class AP/n");
}
class B : public A
{
public:
B();
~B();
};
B::B()
{ }
B::~B()
{
printf("Delete class BP/n");
}
int main(int argc, char* argv[])
{
A *b=new B;
delete b;
return 0;
}
輸出結果爲:Delete class B
Delete class A
如果把A的virtual去掉:那就變成了Delete class A
因此在類的繼承體系中,基類的析構函數不聲明爲虛函數容易造成內存泄漏。所以如果你設計一定類可能是基類的話,必須要聲明其爲虛函數。正如Symbian中的CBase一樣。Note:
1. 如果我們定義了一個構造函數,編譯器就不會再爲我們生成默認構造函數了。
2. 編譯器生成的析構函數是非虛的,除非是一個子類,其父類有個虛析構,此時的函數虛特性來自父類。
3. 有虛函數的類,幾乎可以確定要有個虛析構函數。
4. 如果一個類不可能是基類就不要申明析構函數爲虛函數,虛函數是要耗費空間的。
5. 析構函數的異常退出會導致析構不完全,從而有內存泄露的問題。最好是提供一個管理類,在管理類中提供一個方法來析構,調用者再根據這個方法的結果決定下一步的操作。
6. 在構造函數不要調用虛函數。在基類構造的時候,虛函數是非虛,不會走到派生類中,既是採用的靜態綁定。顯然的是:當我們構造一個子類的對象時,先調用基類的構造函數,構造子類中基類部分,子類還沒有構造,還沒有初始化,如果在基類的構造中調用虛函數,如果可以的話就是調用一個還沒有被初始化的對象,那是很危險的,所以C++中是不可以在構造父類對象部分的時候調用子類的虛函數實現。但是不是說你不可以那麼寫程序,你這麼寫,編譯器也不會報錯。只是你如果這麼寫的話編譯器不會給你調用子類的實現,而是還是調用基類的實現。
7. 在析構函數中也不要調用虛函數。在析構的時候會首先調用子類的析構函數,析構掉對象中的子類部分,然後在調用基類的析構函數析構基類部分,如果在基類的析構函數裏面調用虛函數,會導致其調用已經析構了的子類對象裏面的函數,這是非常危險的。
8. 記得在寫派生類的拷貝函數時,調用基類的拷貝函數拷貝基類的部分,不能忘記了。
如果一個類是作爲基類使用,那麼他的虛構函數一定要是虛的,即用virtual關鍵字(參數爲零則爲純虛函數).
否則會有內存泄漏(很重要),因爲當用基類的指針刪除一個派生類的對象時,要調用派生類的析構函數.但是
其子類或者子子類可以的析構函數可以是虛函數,也可以不是虛函數.(不加virtual 關鍵字則不會調用派生
類的析構函數,而上面用了ClxBase *pTest = new ClxDerived;語句也就是new的ClxDerived對象沒有 銷燬,所以產生內存泄漏)
2.類中的虛函數,如果一個類中的函數被聲明成爲虛函數,那麼其子類不用在聲明爲虛函數(當子類還有子類時),
也可以聲明爲虛函數.結果是一樣的.同虛析構函數的道理是一樣的.當然,並不是要把所有類的析構函數都寫
成虛函數。因爲當類裏面有虛函數的時候,編譯器會給類添加一個虛函數表,裏面來存放虛函數指針,這樣就
會增加類的存儲空間。所以,只有當一個類被用來作爲基類的時候,才把析構函數寫成虛函數。
具體例子:
#include "iostream.h"
class ClxBase
{
public:
ClxBase() {};
virtual ~ClxBase() { cout << "Output from the destructor of class ClxBase!" << endl; };
virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};
class ClxDerived : public ClxBase
{
public:
ClxDerived() {};
virtual~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
//此處的virtual可以去掉
virtual void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
//此處的virtual可以去掉
};
class ClxThrived : public ClxDerived
{
public:
ClxThrived(){};
virtual ~ClxThrived(){cout << "Output from the destructor of class ClxThrived!" << endl;};
//此處的virtual可以去掉
virtual void DoSomething(){cout << "Do something in class ClxThrived!" << endl;}
//此處的virtual可以去掉
};
void main()
{
ClxBase *pTest1 = new ClxBase;
pTest1->DoSomething();
delete pTest1;//1
ClxBase *pTest2 = new ClxDerived;
pTest2->DoSomething();
delete pTest2;//2 用基類的指針刪除一個派生類的對象時
ClxDerived *pTest3 = new ClxDerived;
pTest3->DoSomething();
delete pTest3;//3
ClxBase *pTest4 = new ClxThrived;
pTest4->DoSomething();
delete pTest4;//4 用基類的指針刪除一個派生類的對象時
ClxDerived *pTest5 = new ClxThrived;
pTest5->DoSomething();
delete pTest5;//5 用基類的指針刪除一個派生類的對象時
ClxThrived *pTest6 = new ClxThrived;
pTest6->DoSomething();
delete pTest6;//6
}