建構函式為什麼不能為虛函數 & 基類的解構函式為什麼要為虛函數

來源:互聯網
上載者:User

標籤:c++

一、建構函式為什麼不能為虛函數
1. 從儲存空間角度,虛函數對應一個指向vtable虛函數表的指標,這大家都知道,可是這個指向vtable的指標其實是儲存在對象的記憶體空間的。問題出來了,如果建構函式是虛的,就需要通過 vtable來調用,可是對象還沒有執行個體化,也就是記憶體空間還沒有,怎麼找vtable呢?所以建構函式不能是虛函數。2. 從使用角度,虛函數主要用於在資訊不全的情況下,能使重載的函數得到對應的調用。建構函式本身就是要初始化執行個體,那使用虛函數也沒有實際意義呀。所以建構函式沒有必要是虛函數。虛函數的作用在於通過父類的指標或者引用來調用它的時候能夠變成調用子類的那個成員函數。而建構函式是在建立對象時自動調用的,不可能通過父類的指標或者引用去調用,因此也就規定建構函式不能是虛函數。3. 建構函式不需要是虛函數,也不允許是虛函數,因為建立一個對象時我們總是要明確指定對象的類型,儘管我們可能通過實驗室的基類的指標或引用去訪問它但析構卻不一定,我們往往通過基類的指標來銷毀對象。這時候如果解構函式不是虛函數,就不能正確識別物件類型從而不能正確調用解構函式。4. 從實現上看,vbtl在建構函式調用後才建立,因而建構函式不可能成為虛函數從實際含義上看,在調用建構函式時還不能確定對象的真實類型(因為子類會調父類的建構函式);而且建構函式的作用是提供初始化,在對象生命期只執行一次,不是對象的動態行為,也沒有必要成為虛函數。5. 當一個建構函式被調用時,它做的首要的事情之一是初始化它的VPTR。因此,它只能知道它是“當前”類的,而完全忽視這個對象後面是否還有繼承者。當編譯器為這個建構函式產生代碼時,它是為這個類的建構函式產生代碼——既不是為基類,也不是為它的衍生類別(因為類不知道誰繼承它)。所以它使用的VPTR必須是對於這個類的VTABLE。而且,只要它是最後的建構函式調用,那麼在這個對象的生命期內,VPTR將保持被初始化為指向這個VTABLE, 但如果接著還有一個更晚派生的建構函式被調用,這個建構函式又將設定VPTR指向它的 VTABLE,等.直到最後的建構函式結束。VPTR的狀態是由被最後調用的建構函式確定的。這就是為什麼建構函式調用是從基類到更加衍生類別順序的另一個理由。但是,當這一系列建構函式調用正發生時,每個建構函式都已經設定VPTR指向它自己的VTABLE。如果函數調用使用虛機制,它將只產生通過它自己的VTABLE的調用,而不是最後的VTABLE(所有建構函式被調用後才會有最後的VTABLE)。

虛函數與非虛函數對比  使用虛函數是有代價的,在記憶體和執行速度方面是有一定成本的,包括:

    l 每個對象都將增大,增大量為儲存虛函數表指標的大小;

    l 對於每個類,編譯器都建立一個虛函數地址表;

    l 對於每個函數調用,都需要執行一項額外的操作,即到虛函數表中尋找地址。

  雖然非虛函數比虛函數效率稍高,單不具備動態聯編能力


二、為什麼基類的解構函式是虛函數?

  在實現多態時,當用基類操作衍生類別,在析構時防止只析構基類而不析構衍生類別的狀況發生。

  下面轉自網路:源地址 http://blog.sina.com.cn/s/blog_7c773cc50100y9hz.html

  a.第一段代碼

  

#include<iostream>using namespace std;class ClxBase{public:    ClxBase() {};    ~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};    void DoSomething() { cout << "Do something in class ClxBase!" << endl; };};class ClxDerived : public ClxBase{public:    ClxDerived() {};    ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };    void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };};  int   main(){    ClxDerived *p =  new ClxDerived;  p->DoSomething();  delete p;  return 0;  }

 

  運行結果:

  Do something in class ClxDerived!            

  Output from the destructor of class ClxDerived!

  Output from the destructor of class ClxBase!  

  這段代碼中基類的解構函式不是虛函數,在main函數中用繼承類的指標去操作繼承類的成員,釋放指標P的過程是:先釋放繼承類的資源,再釋放基類資源. 

 

  b.第二段代碼

  

#include<iostream>using namespace std;class ClxBase{public:    ClxBase() {};    ~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};    void DoSomething() { cout << "Do something in class ClxBase!" << endl; };};class ClxDerived : public ClxBase{public:    ClxDerived() {};    ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };    void DoSomething() { cout << "Do something in class ClxDerived!" << endl; }};  int   main(){    ClxBase *p =  new ClxDerived;  p->DoSomething();  delete p;  return 0;  } 

 

  輸出結果:

  Do something in class ClxBase!
  Output from the destructor of class ClxBase!

    這段代碼中基類的解構函式同樣不是虛函數,不同的是在main函數中用基類的指標去操作繼承類的成員,釋放指標P的過程是:只是釋放了基類的資源,而沒有調用繼承類的解構函式.調用  dosomething()函數執行的也是基類定義的函數.

    一般情況下,這樣的刪除只能夠刪除基類對象,而不能刪除子類對象,形成了刪除一半形象,造成記憶體流失.

    在公有繼承中,基類對衍生類別及其對象的操作,只能影響到那些從基類繼承下來的成員.如果想要用基類對非繼承成員進行操作,則要把基類的這個函數定義為虛函數.

    解構函式自然也應該如此:如果它想析構子類中的重新定義或新的成員及對象,當然也應該聲明為虛的. 

 

  c.第三段代碼:

  

#include<iostream>using namespace std;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() {};    ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };    void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };};  int   main(){    ClxBase *p =  new ClxDerived;  p->DoSomething();  delete p;  return 0;  }  

 

  運行結果:

  Do something in class ClxDerived!
  Output from the destructor of class ClxDerived!
  Output from the destructor of class ClxBase!

    這段代碼中基類的解構函式被定義為虛函數,在main函數中用基類的指標去操作繼承類的成員,釋放指標P的過程是:只是釋放了繼承類的資源,再調用基類的解構函式.調用dosomething()函數執行的也是繼承類定義的函數.  

    如果不需要基類對衍生類別及對象進行操作,則不能定義虛函數,因為這樣會增加記憶體開銷.當類裡面有定義虛函數的時候,編譯器會給類添加一個虛函數表,裡面來存放虛函數指標,這樣就會增加類的儲存空間.所以,只有當一個類被用來作為基類的時候,才把解構函式寫成虛函數.


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.