以下內容轉自:
http://blog.csdn.net/haoel/article/details/3081328
http://blog.csdn.net/haoel/article/details/3081385
http://blog.csdn.net/haoel/article/details/1948051#reply
一、多重繼承
以下程序展示:
1、沒有成員變量的虛擬函數在子類中的佈局
2、子類可以通過指針訪問父類中的private虛函數。
下圖中,我們在子類中覆蓋了父類的f()函數。
下面是對於子類實例中的虛函數表的圖:
- #include <iostream>
- using namespace std;
- class Base1 {
- public:
- virtual void f() { cout << "Base1::f" << endl; }
- virtual void g() { cout << "Base1::g" << endl; }
- virtual void h() { cout << "Base1::h" << endl; }
- private:
- virtual void i() { cout << "Base1::i" << endl; }
};- class Base2 {
- public:
- virtual void f() { cout << "Base2::f" << endl; }
- virtual void g() { cout << "Base2::g" << endl; }
- virtual void h() { cout << "Base2::h" << endl; }
- };
- class Base3 {
- public:
- virtual void f() { cout << "Base3::f" << endl; }
- virtual void g() { cout << "Base3::g" << endl; }
- virtual void h() { cout << "Base3::h" << endl; }
- };
- class Derive : public Base1, public Base2, public Base3 {
- public:
- virtual void f() { cout << "Derive::f" << endl; }
- virtual void g1() { cout << "Derive::g1" << endl; }
- };
- typedef void(*Fun)(void);
- int main()
- {
- Fun pFun = NULL;
- Derive d;
- int** pVtab = (int**)&d;
- //Base1's vtable
- pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);
- pFun();
- pFun = (Fun)pVtab[0][0];
- pFun();
- //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);
- pFun = (Fun)pVtab[0][1];
- pFun();
- //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);
- pFun = (Fun)pVtab[0][2];
- pFun();
- //Derive's vtable
- //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);
- pFun = (Fun)pVtab[0][3];
- pFun();
- //The tail of the vtable
- pFun = (Fun)pVtab[0][4];
- cout<<pFun<<endl;
- //Base2's vtable
- //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
- pFun = (Fun)pVtab[1][0];
- pFun();
- //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
- pFun = (Fun)*((int*)*((int*)&d + 1) + 1);
- //這裏其實就是(&d)[1][1] = *((&d)[1] + 1)
- // *((&d)[1] + 1) = *(*( &d + 1) + 1)
- //指針的運算需要指定類型,所以要在指針之前加上類型,因爲虛函數表的指針size和int的size一樣,
- //所以定義成int*比較方便。
- // (Fun)*((int*)*((int*)&d + 1) + 1);
- pFun = (Fun)pVtab[1][1];
- pFun();
- pFun = (Fun)pVtab[1][2];
- pFun();
- //The tail of the vtable
- pFun = (Fun)pVtab[1][3];
- cout<<pFun<<endl;
- //Base3's vtable
- //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
- pFun = (Fun)pVtab[2][0];
- pFun();
- //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
- pFun = (Fun)pVtab[2][1];
- pFun();
- pFun = (Fun)pVtab[2][2];
- pFun();
- //The tail of the vtable
- pFun = (Fun)pVtab[2][3];
- cout<<pFun<<endl;
- return 0;
- }
運行結果:
可以看見,最後一個虛函數數組的尾巴爲0,其他的爲1。
二、帶成員變量的多重繼承
下面,再讓我們來看看多重繼承中的情況,假設有下面這樣一個類的繼承關係。注意:子類只overwrite了父類的f()函數,而還有一個是自己的函數(我們這樣做的目的是爲了用g1()作爲一個標記來標明子類的虛函數表)。而且每個類中都有一個自己的成員變量:
內存分配如下:
我們可以看到:
1) 每個父類都有自己的虛表。
2) 子類的成員函數被放到了第一個父類的表中。
3) 內存佈局中,其父類佈局依次按聲明順序排列。
4) 每個父類的虛表中的f()函數都被overwrite成了子類的f()。這樣做就是爲了解決不同的父類類型的指針指向同一個子類實例,而能夠調用到實際的函數。
三、重複繼承
下圖是一個繼承圖,我們重載了父類的f()函數。
其類繼承的源代碼如下所示。其中,每個類都有兩個變量,一個是整形(4字節),一個是字符(1字節),而且還有自己的虛函數,自己overwrite父類的虛函數。如子類D中,f()覆蓋了超類的函數, f1() 和f2() 覆蓋了其父類的虛函數,Df()爲自己的虛函數。
下面是對於子類實例中的虛函數表的圖:
我們可以看見,最頂端的父類B其成員變量存在於B1和B2中,並被D給繼承下去了。而在D中,其有B1和B2的實例,於是B的成員在D的實例中存在兩份,一份是B1繼承而來的,另一份是B2繼承而來的。所以,如果我們使用以下語句,則會產生二義性編譯錯誤:
D d;
d.ib = 0; //二義性錯誤
d.B1::ib = 1; //正確
d.B2::ib = 2; //正確
注意,上面例程中的最後兩條語句存取的是兩個變量。雖然我們消除了二義性的編譯錯誤,但B類在D中還是有兩個實例,這種繼承造成了數據的重複,我們叫這種繼承爲重複繼承。重複的基類數據成員可能並不是我們想要的。所以,C++引入了虛基類的概念。
四、鑽石型多重虛擬繼承
虛擬繼承的出現就是爲了解決重複繼承中多個間接父類的問題的。鑽石型的結構是其最經典的結構。也是我們在這裏要討論的結構:
上述的“重複繼承”只需要把B1和B2繼承B的語法中加上virtual 關鍵,就成了虛擬繼承,其繼承圖如下所示:
上圖和前面的“重複繼承”中的類的內部數據和接口都是完全一樣的,只是我們採用了虛擬繼承:其省略後的源碼如下所示:
class B {……};
class B1 : virtual public B{……};
class B2: virtual public B{……};
class D : public B1, public B2{ …… };
1)無論是GCC還是VC++,除了一些細節上的不同,其大體上的對象佈局是一樣的。也就是說,先是B1(黃色),然後是B2(綠色),接着是D(灰色),而B這個超類(青藍色)的實例都放在最後的位置。
2)關於虛函數表,尤其是第一個虛表,GCC和VC++有很重大的不一樣。但仔細看下來,還是VC++的虛表比較清晰和有邏輯性。
3)VC++和GCC都把B這個超類放到了最後,而VC++有一個NULL分隔符把B和B1和B2的佈局分開。GCC則沒有。