最好看一下前一篇的c++虛函式表
簡而言之,我們乙個類可能會有如下的影響因素:
1)成員變數
2)虛函式(產生虛函式表)
3)單一繼承(只繼承於乙個類)
4)多重繼承(繼承多個類)
5)重複繼承(繼承的多個父類中其父類有相同的超類)
6)虛擬繼承(使用virtual方式繼承,為了保證繼承後父類的記憶體布局只會存在乙份)
上述的東西通常是c++這門語言在語義方面對物件內部的影響因素,當然,還會有編譯器的影響(比如優化),還有位元組對齊的影響。在這裡我們都不討論,我們只討論c++語言上的影響。
本篇文章著重討論下述幾個情況下的c++物件的記憶體布局情況。
1)單一的一般繼承(帶成員變數、虛函式、虛函式覆蓋)
2)單一的虛擬繼承(帶成員變數、虛函式、虛函式覆蓋)
3)多重繼承(帶成員變數、虛函式、虛函式覆蓋)
4)重複多重繼承(帶成員變數、虛函式、虛函式覆蓋)
5)鑽石型的虛擬多重繼承(帶成員變數、虛函式、虛函式覆蓋)
我們的目標就是,讓事情越來越複雜。
知識複習
typedef
void(*fun)(void);
base b;
fun pfun = null;
cout
<< (int*)(&b) << endl;
cout
<< (int*)*(int*)(&b) << endl;
// invoke the first virtual function
pfun = (fun)*((int*)*(int*)(&b));
pfun();
我們同樣可以用這種方式來取得整個物件例項的記憶體布局。因為這些東西在記憶體中都是連續分布的,我們只需要使用適當的位址偏移量,我們就可以獲得整個記憶體物件的布局。
單一的一般繼承
下面,我們假設有如下所示的乙個繼承關係:
請注意,在這個繼承關係中,父類,子類,孫子類都有自己的乙個成員變數。而了類覆蓋了父類的f()方法,孫子類覆蓋了子類的g_child()及其超類的f()。
我們的源程式如下所示:
class parent
virtual void f()
virtual void g()
virtual void h()
class child : public parent
virtual void f()
virtual void g_child()
virtual void h_child()
};class grandchild : public child
virtual void f()
virtual void g_child()
virtual void h_grandchild()
}; 我們使用以下程式作為測試程式:(下面程式中,我使用了乙個int** pvtab 來作為遍歷物件記憶體布局的指標,這樣,我就可以方便地像使用陣列一樣來遍歷所有的成員包括其虛函式表了,在後面的程式中,我也是用這樣的方法的,請不必感到奇怪,)
typedef void(*fun)(void);
grandchild gc;
int** pvtab = (int**)&gc;
cout << "[0] grandchild::_vptr->" << endl;
for (int i=0; (fun)pvtab[0][i]!=null; i++)
virtual void f()
virtual void g()
virtual void h()
class base2
virtual void f()
virtual void g()
virtual void h()
};class base3
virtual void f()
virtual void g()
virtual void h()
};class derive : public base1, public base2, public base3
virtual void f()
virtual void g1()
};我們通過下面的程式來檢視子類例項的記憶體布局:下面程式中,注意我使用了乙個s變數,其中用到了sizof(base)來找下乙個類的偏移量。(因為我宣告的是int成員,所以是4個位元組,所以沒有對齊問題。關於記憶體的對齊問題,大家可以自行試驗,我在這裡就不多說了)
typedef void(*fun)(void);
derive d;
int** pvtab = (int**)&d;
cout << "[0] base1::_vptr->" << endl;
pfun = (fun)pvtab[0][0];
cout << " [0] ";
pfun();
pfun = (fun)pvtab[0][1];
cout << " [1] ";pfun();
pfun = (fun)pvtab[0][2];
cout << " [2] ";pfun();
pfun = (fun)pvtab[0][3];
cout << " [3] "; pfun();
pfun = (fun)pvtab[0][4];
cout << " [4] "; cout
[0] base1::_vptr->
[0] derive::f()
[1] base1::g()
[2] base1::h()
[3] driver::g1()
[4] 00000000 注意:在gcc下,這裡是1
[1] base1.ibase1 = 10
[2] base2::_vptr->
[0] derive::f()
[1] base2::g()
[2] base2::h()
[3] 00000000 注意:在gcc下,這裡是1
[3] base2.ibase2 = 20
[4] base3::_vptr->
[0] derive::f()
[1] base3::g()
[2] base3::h()
[3] 00000000
[5] base3.ibase3 = 30
[6] derive.iderive = 100
使用表示是下面這個樣子:
我們可以看到:
1) 每個父類都有自己的虛表。
2) 子類的成員函式被放到了第乙個父類的表中。
3) 記憶體布局中,其父類布局依次按宣告順序排列。
4) 每個父類的虛表中的f()函式都被overwrite成了子類的f()。這樣做就是為了解決不同的父類型別的指標指向同乙個子類例項,而能夠呼叫到實際的函式。
虛繼承與虛基類的記憶體布局
1.多重繼承的記憶體布局 struct a struct b public a struct c public a struct d 多重繼承的情況,如果父類有共同的祖父類,則祖父類物件被拷貝了多次。在該例中,b的記憶體布局為 假設從上往下為位址增加方向 int a a int b b 則d的記憶體...
C 的虛基類
虛基類 當在多條繼承路徑上有乙個公共的基類,在這些路徑中的某幾條匯合處,這個公共的基類就會產生多個例項 或多個副本 若只想儲存這個基類的乙個例項,可以將這個公共基類說明為 虛基類 虛基類.在繼承中產生歧義的原因有可能基類是繼承類繼承了基類多次,從而產生了多個拷貝,即不止一次的通過多個路徑繼承類在記憶...
c 的類 虛基類 六
什麼是虛基類 就是他的派生類有兩個或者以上的派生類 通過虛繼承,這樣就可以避免派生類有多個基類的副本 從而減少記憶體消耗 關於繼承可以看我的這一篇新增鏈結描述 include include using namespace std class person class partymember vir...