繼續整理第三章的內容(以下部分圖片來自於原書):
1. 在只有繼承沒有多態的情況下,子類是的內容就是父類加上子類特有的資料成員,例如,對於如下兩個類,Point2d和Point3d,後者公有繼承自前者,此時的資料布局如下所示:
在某些情況下,把一個類分解成多層,可能會導致類所佔用空間的膨脹,例如,如下類,算上對齊操作大小為8 (4 + 1 + 1+1 +(1對齊)):
而如果相同的資料(val,c1,c2,c3),被分散的放入繼承體系之中,現在一個包含所有這些資料的類Concrete3的大小就膨脹到了16,這是因為上面的無繼承的類設計中,只有一個位元組被用來對齊,而此種繼承體系之下,有9個位元組被用來對齊:
這時的類的布局如下:
為什麼不把Concrete2和Concrete3的資料填補到Concrete1用於對齊的空間中呢。原因時,在此種情況下,當發生Concrete1的複製操作時,會破壞Concrete2的內容:
2. 加入多態時的情況,例如,街上上述的例子,對於Point2d,現在加入幾個虛函數,用以支援多態特性:
此時會帶來額外的空間以及存取時間上的額外負擔:
a. 虛函數表會被產生出來(virtual table)。
b. 每一個類對象中會加入一個指向上述虛表的指標(vptr)。
c. 加強建構函式,使之可以為vptr設定初值。
d. 加強解構函式,使之可以清除指向虛函數表的vptr。
此時的類的記憶體布局會增加一個指向虛表的指標(該指標可能放在頭部也可能放在尾部):
3. 多種繼承的情況,在單繼承中,可以看到,基類和子類的對象都是從相同的地址開始的,差異只是子類比較大,例如以下操作,
P3d p3d; P2d* p = &p3d;
以基類指標指向子類指標並不需要修改地址,多重繼承則不一樣,因為第二個乃至後面的基類起始地址與對應基類的地址並不一樣,例如對於如下繼承體系以及對應的記憶體布局:
從上圖看以看出,最左端基類(P2d和P3d)的起始地址和子類V3d是一樣的,而之後的基類Vertex則和子類不一致,因此,對於如下對象和指標:
Vertex3d v3d;Vertex* pv;Point2d* p2d;Point3d* p3d;
如下的賦值操作:pv = v3d; 需要內部轉化為:pv = (Vertex*)(((char*)&v3d) + sizeof(Point3d));即需要位移才可以指向子類中對應的該基類的部分,而對於如下賦值:p2d = &v3d;p3d = &v3d; 則不需要任何調整。
如果要存取第二個或者後繼基類中的一個資料成員,並不需要額外負擔,因為資料成員的位置在編譯時間期就固定了,因此存取只是一個簡答的位移(offset)運算,並不需要額外成本。
4. 虛擬繼承的情況,考慮如下繼承體系,Vertex和Point3d虛擬繼承自Point2d,Vertex3d共有繼承(非虛繼承)自Vertex和Point3d
a. 此時必須有在子類對象中安插指標指向虛基類,一種可能的布局如下,子類需要維護指向虛基類地址的指標,此種策略下,對於Point3d運算子:
void Point3d::operator+=(const Point3d& rhs){ _x += rhs._x; _y += rhs._y; _z += rhs._z;}; 會內部轉化為:
pPoint2d->_x += rhs.pPoint2d->_x;pPoint2d->_y += rhs.pPoint2d->_y;_z += rhs._z;
而對於一個子類和基類的轉換:
Point2d* p2d = pv3d; //Vertex3d* pv3d
會被轉化為(必須判斷pv3d是否為空白,否則可能導致不正確的賦值):
Point2d* p2d = pv3d ? pv3d->pPoint2d : 0;
b. 另一種策略是將虛基類的位移量(而不是地址)存入虛表中(以下例子中,虛表中的正值索引會索引到虛函數地址,負值索引會索引到虛基類位移量),也就是與虛函數放到一個表中,針對上例,此種策略下可能的布局如下,此種策略下,上述+=運算子會被轉化為:
(this + __vptr__Point3d[-1])->_x += (rhs + rhs.__vptr__Point3d[-1])->_x;(this + __vptr__Point3d[-1])->_y += (rhs + rhs.__vptr__Point3d[-1])->_y;_z += rhs._z;
對於
Point2d* p2d = pv3d;
會轉化為:
Point2d* p2d = pv3d 。 pv3d + pv3d->__vptr__Point3d[-1] : 0;
由於虛擬繼承的存在帶來了額外的負擔以及高度的複雜性,所以,一般而言,“虛基類的最有效運用形式就是:一個抽象的虛基類,沒有任何資料成員。”