#include<iostream> class x { public: int _x; }; class y : public virtual x { public: int _y; }; class z : public virtual x { public: int _z; }; class A:public z,public y { public: int _a; }; int main() { std::cout <<"sizeof(x): "<< sizeof(x) << std::endl; std::cout <<"sizeof(y): "<< sizeof(y) << std::endl; std::cout <<"sizeof(A): "<< sizeof(A) << std::endl; std::cout <<"&A::_x :"<< &A::_x << std::endl;; std::cout << "&A::_y :" << &A::_y << " " << std::endl; std::cout << "&A::_z :" << &A::_z << " " << std::endl; std::cout <<"&A::_a :"<< &A::_a << " " << std::endl; getchar(); return 0; }
輸出: sizeof(x): 4 sizeof(y): 12 sizeof(A): 24 sizeof(x)和sizeof(y)的結果在我們的意料中:x中沒有虛函數,所以sizeof(x) = sizeof(int)。class y虛繼承自class x,它需要一個指針指向類似於virtual base table的指針(下面簡稱vbptr),指向自己實際的x對象地址,所以sizeof(y)=2*sizeof(int) + sizeof(vbptr),我使用32位編譯器生成的代碼,所以sizeof(y)=12. 按照我開始的料想,sizeof(A)應該等於sizeof(int)*4(分別是:_x,_y,_z,_a) + sizeof(vbptr)=20。這裡的vbptr指向了唯一的x實例。 (注意啦,我要開始了) C++標准中有規定,子類的對象模型中,應該保持父類對象的布局。在上面,我們說了sizeof(y)=2*sizeof(int) + sizeof(vbptr),bingo~,這裡的sizeof(A)就是sizeof(y) + sizeof(z)=24 而vbptr因為出在了兩個父類中,已經不需要額外的一個vbptr指向了。 你是不是要問這個vbptr(虛基類表指針)到底是干什麼的? 我們知道虛繼承的父類x,無論它被派生多次,它在最後多重繼承的子類中,只存在一份實例。這樣可能出現這樣的情況: y object_y; A object_A; object_A._x =1; object_y = object_A;//這裡發生類對象的剪切,將子類中的非父類成員剪去,剩下的賦給父類。 可能你還沒有明白,沒關系,我再啰嗦一句~ 抱歉沒有很好的繪圖工具,我們使用“|“來隔開內存中的成員。 object_y 的內存布局應該是這樣的:vbptr | _x | _y,注意先實例化父類,再實例化子類。 而,object_A的內存布局應該是這樣的:vbptr_z | _z | vbptr_y | _y | _a | _x,注意我們多重繼承的先後順序:class A:public z,public y,這個順序決定了z的對象布局在y對象前面。因為虛繼承的原因,我們對象布局發生了改變:先實例化不共享部分(也就是class y和class z中除了class x的成員),這個部分依然是先父類再子類。最後才是虛繼承的公共部分:class x的成員。 我們發現,直接進行內存布局的剪切行不通(紅色部分),這需要編譯器介入,但編譯器也不是神仙啊,它需要信息去找_x在哪裡,這就是vbptr中存的信息——它告訴編譯器去哪裡找_x,這裡的信息可能是偏移,也可能是指針。為什麼是表呢?而不是直接指向成員的指針呢?因為,如果你再虛繼承幾個父類,你的這個指針會越來越多,對象的規模也就越來越大,這不能容忍。使用表可以始終只占一個指針大小,只是需要間址查詢,但是這完全可以由編譯器優化之。