1. 程式人生 > >建構函式和解構函式能否宣告為虛擬函式?

建構函式和解構函式能否宣告為虛擬函式?

建構函式不能宣告為虛擬函式,解構函式可以宣告為虛擬函式,而且有時是必須宣告為虛擬函式。

不建議在建構函式和解構函式裡面呼叫虛擬函式。

建構函式不能宣告為虛擬函式的原因是:

解釋一:所謂虛擬函式就是多型情況下只執行一個。而從繼承的概念來講,總是要先構造父類物件,然後才能是子類物件。如果建構函式設為虛擬函式,那麼當你在構造父類的建構函式時就不得不顯示的呼叫構造。還有一個原因就是為了防錯,試想如果你在子類中一不小心重寫了個跟父類建構函式一樣的函式,那麼你的父類的建構函式將被覆蓋,也即不能完成父類的構造.就會出錯。

解釋二:虛擬函式的主要意義在於被派生類繼承從而產生多型。派生類的建構函式中,編譯器會加入構造基類的程式碼,如果基類的建構函式用到引數,則派生類在其建構函式的初始化列表中必須為基類給出引數,就是這個原因。

虛擬函式的意思就是開啟動態繫結,程式會根據物件的動態型別來選擇要呼叫的方法。然而在建構函式執行的時候,這個物件的動態型別還不完整,沒有辦法確定它到底是什麼型別,故建構函式不能動態繫結。(動態繫結是根據物件的動態型別而不是函式名,在呼叫建構函式之前,這個物件根本就不存在,它怎麼動態繫結?)

編譯器在呼叫基類的建構函式的時候並不知道你要構造的是一個基類的物件還是一個派生類的物件。

解構函式設為虛擬函式的作用:    

解釋:在類的繼承中,如果有基類指標指向派生類,那麼用基類指標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 
}