漫談繼承技術(八)

       如果沒有繼承,類只是具有一些相關行爲的數據結構,這只是對過程語言的一大改進,而繼承則開闢了完全不同的新天地。通過繼承,可以在已有類的基礎上創建新類。這樣,類就成爲可重用和可擴展的組件。博主的《漫談繼承技術》系列博文將講述各種利用繼承功能的方法。學習繼承的語法,和利用繼承的一些複製技術。本篇博文先給大家介紹一下virtual在繼承中的使用,希望對大家加深對繼承技術的理解有一定的幫助。

virtual

       在C++編譯類時,會創建一個包含類中所有方法的二進制對象。在非虛情況下,將控制交給正確方法的代碼是硬編碼,此時會根據編譯時的類型調用方法。如果方法聲明爲virtual,會使用名爲虛表(vtable)的特定內存區域調用正確的實現。每個具有一個或者多個虛方法的類都有一張虛表,這種類的每個對象都包含指向虛表的指針,這個虛表包含了指向虛方法實現的函數入口地址。通過這種方法,當使用某個對象調用方法時,指針也進入虛表,然後根據實際的對象類型執行正確版本的方法。

舉個栗子,咱們理解一下重寫技術。


       可能有的人會想:“我們爲什麼不把所有的函數都聲明爲virtual?那樣就不需要virtual關鍵字,編譯器直接隱藏這些細節,就像Java那樣該多好啊!”。這樣就會降低C++的性能,還會讓C++程序猿受到束縛。要調用virtual方法,程序需要執行一些附加操作,即對指向要執行的適當代碼的指針解除應用。在多數情況下,這樣做會輕微地影響性能,但是C++的設計者認爲,最好讓程序猿決定是否有必要影響性能。如果方法永遠不會被重寫,就沒有必要將其聲明爲virtual,從而影響性能。將析構函數聲明爲virtual永遠都是一個好習慣,唯一允許不把析構函數聲明爲virtual的例外情況是,類標記爲final。如果忘記了final的用法,那就回到《漫談繼承技術:一》再回顧一下吧!

       除非有特別原因,或者類標記爲final,否則強烈建議將所有方法(包括析構函數,構造函數除外)聲明爲virtual。構造函數不需要也無法聲明爲virtual,因爲在創建對象時,總會明確地指定類。

虛基類

      將共享基類設置爲虛基類,這時從不同的路勁繼承來的同名數據成員在內存中就只有一個副本,同一個函數名也只有一個映射。這樣就解決了同名成員的唯一標識問題。在多繼承情況下,虛基類關鍵字的作用範圍和繼承方式關鍵字一樣,只對緊跟其後的基類起作用。

       建立一個對象時,如果這個對象中含有從虛基類繼承來的成員,則虛基類的成員是由最遠派生類的構造函數通過調用虛基類的構造函數進行初始化。而且,只有最遠派生類的構造函數會調用虛基類的構造函數,該派生類的其他基類對虛基類構造函數的調用都自動被忽略

舉個栗子:

#include<iostream>

usingnamespacestd;

 

//公共基類Base0

classBase0

{

public:

    Base0(intvar= 0):var0(var){}

    intvar0;           //間接基類的數據成員

    voidfun0()         //間接基類的成員函數

    {

        cout<<"Base0: "<< var0 << endl;

    }

};

 

//Base0爲虛基類,派生Base1

classBase1:virtualpublicBase0

{

public:

    Base1(intvar= 1):Base0(var),var1(var){}

    voidfun()

    {

        cout<<"Base1: "<< var1 << endl;

    }

private:

    intvar1;       //直接基類的數據成員

};

 

//Base0爲虛基類,派生Base2

classBase2:virtualpublicBase0

{

public:

    Base2(intvar= 2):Base0(var),var1(var){}

 

private:

    intvar1;       //直接基類的數據成員

};

 

//最遠派生類

classBase3:publicBase1,public Base2

{

public:

    Base3(intvar= 3):Base0(var),Base1(var),Base2(var), var(var){}

 

private:

    intvar;            //最遠派生類新增的數據成員

};

 

 

int main()

{

    //實例化一個最遠派生類的對象

    Base3 b;       

    //實例化一個直接基類的對象,此時Base2也是最遠派生類。可以當做Base3不存在

    Base2 c(6);    

 

    b.fun0();      //輸出Base0: 3

    c.fun0();      //輸出Base0: 6

    b.fun();       //輸出Base1: 3

 

    //通過直接基類調用fun0函數,實際是映射到間接基類(映射和副本只有一份)

    b.Base1::fun0();   //輸出Base0: 3

    b.Base2::fun0();   //輸出Base0: 3

 

    return0;

}

 

程序運行結果:

 

虛基類的內存佈局:

 


         如果想了解更多關於繼承技術相關的知識,請關注博主《漫談繼承技術》系列博文,相信你能夠在那裏尋找到更多有助你快速成長和深入你對繼承相關的知識和一些複製的技術理解和掌握。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章