測試一、虛繼承與繼承的區別 1.1 單個繼承,不帶虛函數 1>class B size(8): 1> + 1> 0 | + (base class A) 1> 0 | | _ia //4B 1> | + 1> 4 | _ib //4B 有兩個int類型數據成員,占8B,基類邏輯存在前面 1.2、單個 ...
測試一、虛繼承與繼承的區別
1.1 單個繼承,不帶虛函數 1>class B size(8): 1> +--- 1> 0 | +--- (base class A) 1> 0 | | _ia //4B 1> | +--- 1> 4 | _ib //4B
有兩個int類型數據成員,占8B,基類邏輯存在前面
1.2、單個虛繼承,不帶虛函數 1>class B size(12): 1> +--- 1> 0 | {vbptr} //虛基指針(指向虛基表) 1> 4 | _ib //派生類放到前面 1> +--- 1> +--- (virtual base A) //虛基類 1> 8 | _ia 1> +--- 1>B::$vbtable@: //虛基表 1> 0 | 0 // 虛基指針距離派生類對象偏移0B 1> 1 | 8 (Bd(B+0)A) // 虛基指針向下偏移8B找到虛基類
虛繼承多一個虛基指針,共12B,虛擬繼承會將派生類的邏輯存到前面;
虛基表中存放的內容:(1)虛基指針距離派生類對象首地址的偏移信息(2)虛基類的偏移信息
測試二、單個虛繼承,帶虛函數
2.1、單個繼承,帶虛函數 1>class B size(12): 1> +--- 1> 0 | +--- (base class A) 1> 0 | | {vfptr} //虛函數指針 1> 4 | | _ia 1> | +--- 1> 8 | _ib 1> +--- 1>B::$vftable@: //虛表 1> | &B_meta 1> | 0 1> 0 | &B::f // f 和 fb2 入虛表,fb不是虛函數,不入虛表 1> 1 | &B::fb2 // 派生類新增虛函數直接放在基類虛表中
帶虛函數的話,多一個虛函數指針,指向虛表,所以共占12B,派生類新增的虛函數放入基類虛表
2.3、單個虛繼承,帶虛函數,派生類不新增 8/16 1>class B size(16): 1> +--- 1> 0 | {vbptr} //有虛繼承的時候就多一個虛基指針,虛基指針指向虛基表 1> 4 | _ib //有虛函數的時候就產生一個虛函數指針,虛函數指針指向虛函數表 1> +--- 1> +--- (virtual base A) 1> 8 | {vfptr} 1>12 | _ia 1> +--- 1>B::$vbtable@: //虛基表 1> 0 | 0 // 虛基指針距離派生類對象偏移0B 1> 1 | 8 (Bd(B+0)A) // 虛基指針向下偏移8B找到虛基類 1>B::$vftable@: //虛函數表 1> | -8 1> 0 | &B::f
兩個 int 型變數,一個虛函數指針,一個虛基指針,共占16B;
虛擬繼承使得派生類邏輯存在基類前面;
(虛擬繼承後,基類在派生類後面,虛函數指針也在下麵,派生類要找到虛函數表,向後偏移8B)
2.2 單個虛繼承,帶虛函數 (自己新增) 1>class B size(20): 1> +--- 1> 0 | {vfptr} //虛函數指針 1> 4 | {vbptr} //虛基指針 (虛繼承多一個) {虛擬繼承,派生類在前面} 1> 8 | _ib 1> +--- 1> +--- (virtual base A) 1>12 | {vfptr} //虛函數指針 1>16 | _ia 1> +--- 1>B::$vftable@B@: //虛表 1> | &B_meta 1> | 0 1> 0 | &B::fb2 //派生類新增虛函數,放在最前面,訪問新增虛函數快一些,不用偏移 ,多一個虛函數指針,指向新的虛表 1>B::$vbtable@: //虛基表 1> 0 | -4 //虛基指針距離派生類對象首地址的偏移信息 1> 1 | 8 (Bd(B+4)A) //找到虛基類的偏移信息 1>B::$vftable@A@: //虛表 1> | -12 1> 0 | &B::f 基類佈局在最後面
派生類中新增一個虛函數指針,指向一張新的虛表,存放派生類新增的虛函數,可以更快的訪問到
所以,兩個虛函數指針,一個虛基指針,兩個int類型變數,共20B
測試三:多重繼承(帶虛函數)
3.1、普通多重繼承,帶虛函數,自己有新增虛函數 28 //Base1中 f() g() h() , Base2中 f() g() h() , Base3中 f() g() h() Derived 中 f() g1() 1>class Derived size(28): 1> +--- 1> 0 | +--- (base class Base1) //基類有自己的虛函數表,基類的佈局按照被繼承時的順序排列 1> 0 | | {vfptr} // 3個虛函數指針指向不同虛表 1> 4 | | _iBase1 1> | +--- 1> 8 | +--- (base class Base2) 1> 8 | | {vfptr} 1>12 | | _iBase2 1> | +--- 1>16 | +--- (base class Base3) 1>16 | | {vfptr} 1>20 | | _iBase3 1> | +--- 1>24 | _iDerived 1> +--- 1>Derived::$vftable@Base1@: 1> | &Derived_meta 1> | 0 1> 0 | &Derived::f(虛函數的覆蓋) //第一個虛函數表中存放真實的被覆蓋的虛函數地址,其他虛函數表中存放跳轉地址 1> 1 | &Base1::g 1> 2 | &Base1::h 1> 3 | &Derived::g1 (新的虛函數,直接放在基類之後,加快查找速度) 1>Derived::$vftable@Base2@: 1> | -8 1> 0 | &thunk: this-=8; goto Derived::f //虛函數表還可以存放跳轉指令 1> 1 | &Base2::g 1> 2 | &Base2::h 1>Derived::$vftable@Base3@: 1> | -16 1> 0 | &thunk: this-=16; goto Derived::f 1> 1 | &Base3::g 1> 2 | &Base3::h
Base1、Base2、Base3中各有一個虛函數指針指向自己的虛表,有4個int類型的數據成員,共占28B
第一個虛函數表中存放真實的被覆蓋的虛函數地址,其他虛函數表中存放跳轉地址
3.2、虛擬多重繼承,帶虛函數,自己有新增虛函數(只有第一個是虛繼承) 32 Base1是虛繼承 1>class Derived size(32): //多一個虛基指針 1> +--- 1> 0 | +--- (base class Base2) 1> 0 | | {vfptr} 1> 4 | | _iBase2 1> | +--- 1> 8 | +--- (base class Base3) 1> 8 | | {vfptr} 1>12 | | _iBase3 1> | +--- 1>16 | {vbptr} 1>20 | _iDerived 1> +--- 1> +--- (virtual base Base1) 1>24 | {vfptr} 1>28 | _iBase1 1> +--- 1>Derived::$vftable@Base2@: 1> | &Derived_meta 1> | 0 1> 0 | &Derived::f //第一個虛函數表中存放真實的被覆蓋的虛函數地址,其他虛函數表中存放跳轉地址 1> 1 | &Base2::g 1> 2 | &Base2::h 1> 3 | &Derived::g1 1>Derived::$vftable@Base3@: 1> | -8 //去找Derived::f 1> 0 | &thunk: this-=8; goto Derived::f 1> 1 | &Base3::g 1> 2 | &Base3::h 1>Derived::$vbtable@: //虛基表 1> 0 | -16 1> 1 | 8 (Derivedd(Derived+16)Base1) 1>Derived::$vftable@Base1@: 1> | -24 1> 0 | &thunk: this-=24; goto Derived::f 1> 1 | &Base1::g 1> 2 | &Base1::h
虛擬繼承會將派生類的邏輯存到前面,Base1是虛繼承,所以記憶體中的存放順序為 Base2、Base3、Derived、Base1
所占空間大小,在上面一個例子基礎上,多一個虛基指針,所以占32B
虛基指針向上偏移16B得到派生類對象首地址,向下偏移8B找到虛基類
3.3、虛擬多重繼承,帶虛函數,自己有新增虛函數(三個都是虛繼承) 36 1>class Derived size(36): //多一張虛表 1> +--- 1> 0 | {vfptr} //以空間換時間 新增虛函數,多張虛表 1> 4 | {vbptr} 1> 8 | _iDerived 1> +--- 1> +--- (virtual base Base1) 1>12 | {vfptr} 1>16 | _iBase1 1> +--- 1> +--- (virtual base Base2) 1>20 | {vfptr} 1>24 | _iBase2 1> +--- 1> +--- (virtual base Base3) 1>28 | {vfptr} 1>32 | _iBase3 1> +--- 1>Derived::$vftable@Derived@: 1> | &Derived_meta 1> | 0 1> 0 | &Derived::g1 1>Derived::$vbtable@: 1> 0 | -4 1> 1 | 8 (Derivedd(Derived+4)Base1) //vbptr偏移8B找到虛基類Base1 1> 2 | 16 (Derivedd(Derived+4)Base2) // vbptr偏移16B找到虛基類Base2 1> 3 | 24 (Derivedd(Derived+4)Base3) 1>Derived::$vftable@Base1@: 1> | -12 1> 0 | &Derived::f 1> 1 | &Base1::g 1> 2 | &Base1::h 1>Derived::$vftable@Base2@: 1> | -20 1> 0 | &thunk: this-=8; goto Derived::f 1> 1 | &Base2::g 1> 2 | &Base2::h 1>Derived::$vftable@Base3@: 1> | -28 1> 0 | &thunk: this-=16; goto Derived::f 1> 1 | &Base3::g 1> 2 | &Base3::h
虛擬繼承會將派生類的邏輯存到前面,3個Base都是虛繼承,所以記憶體中的存放順序為Derived、Base1、 Base2、Base3
在上一個例子的基礎上,多一張虛表,所以占36B
測試四、菱形虛繼承
4.1、菱形普通繼承(存儲二義性) 48 B1、B2繼承B;D繼承B1、B2 class D size(48): 1> +--- 1> 0 | +--- (base class B1) 1> 0 | | +--- (base class B) 1> 0 | | | {vfptr} 1> 4 | | | _ib //存儲二義性 1> 8 | | | _cb //1 1> | | | <alignment member> (size=3) //記憶體對齊 1> | | +--- 1>12 | | _ib1 1>16 | | _cb1 1> | | <alignment member> (size=3) 1> | +--- 1>20 | +--- (base class B2) 1>20 | | +--- (base class B) 1>20 | | | {vfptr} 1>24 | | | _ib //存儲二義性 1>28 | | | _cb 1> | | | <alignment member> (size=3) 1> | | +--- 1>32 | | _ib2 1>36 | | _cb2 1> | | <alignment member> (size=3) 1> | +--- 1>40 | _id 1>44 | _cd 1> | <alignment member> (size=3) 1> +--- 1>D::$vftable@B1@: 1> | &D_meta 1> | 0 1> 0 | &D::f 1> 1 | &B::Bf 1> 2 | &D::f1 1> 3 | &B1::Bf1 1> 4 | &D::Df 1>D::$vftable@B2@: 1> | -20 1> 0 | &thunk: this-=20; goto D::f 1> 1 | &B::Bf 1> 2 | &D::f2 1> 3 | &B2::Bf2
B的數據成員有兩份,造成了存儲二義性,共占48B
4.2、菱形虛擬繼承 B1、B2虛擬繼承B;D普通繼承B1、B2 52 1>class D size(52): 1> +--- 1> 0 | +--- (base class B1) //基類B1 1> 0 | | {vfptr} 1> 4 | | {vbptr} // +36 找到虛基類 1> 8 | | _ib1 1>12 | | _cb1 1> | | <alignment member> (size=3) 1> | +--- 1>16 | +--- (base class B2) //基類B2 1>16 | | {vfptr} 1>20 | | {vbptr} // +20找到虛基類 1>24 | | _ib2 1>28 | | _cb2 1> | | <alignment member> (size=3) 1> | +--- 1>32 | _id //派生類D 1>36 | _cd 1> | <alignment member> (size=3) 1> +--- 1> +--- (virtual base B) //基類B 1>40 | {vfptr} 1>44 | _ib 1>48 | _cb 1> | <alignment member> (size=3) 1> +--- 1>D::$vftable@B1@: 1> | &D_meta 1> | 0 1> 0 | &D::f1 // D中覆蓋了 1> 1 | &B1::Bf1 //新增 1> 2 | &D::Df //D中新增,放到B1的虛函數表中 1>D::$vftable@B2@: 1> | -16 1> 0 | &D::f2 // D中覆蓋了 1> 1 | &B2::Bf2 //新增 1>D::$vbtable@B1@: 1> 0 | -4 //距離派生類對象B1首地址偏移 -4 1> 1 | 36 (Dd(B1+4)B) 1>D::$vbtable@B2@: 1> 0 | -4 //距離派生類對象B2首地址偏移 -4 1> 1 | 20 (Dd(B2+4)B) 1>D::$vftable@B@: 1> | -40 1> 0 | &D::f 1> 1 | &B::Bf
B1、B2各有虛基指針
存儲順序本來是:派生類B1、基類B、派生類B2、基類B、派生類D
存儲順序:派生類B1、派生類B2、派生類D、基類B(基類放到後面,解決了存儲二義性)