<深度探索C++對象模型>>(簡體版)中的蛇足
(沒有此書的人請勿看)
上次見到這本書是一年前(是候先生的繁體版),花了一個星期的時間讀完,囫圇吞棗,不求甚解,饒是如此,也解決了我在C++方面的諸多疑惑,這次終於看到了簡體版,同樣花了一個星期,或許真的是一回生,兩回熟吧(也可能是對簡體文字的親切感^_^),思考問題的同時也發現了一些問題,一愚之見,不吐不快。
蛇足之一,P84,
class X {};
class Y : public virtual X {};
class Z : public virtual Z {};
class A : public X, pubic Z {};
書中寫到
事實上這個大小受到3個因素的影響,
1) 語言本身所造成的額外負擔,。
2) 編譯器對於特殊情況所提拱的優化處理。。
3) Alignment的限制。。
候先生自己添加的示意圖如下:
當時看這個圖真是“百思不得其解”,無論對於Y或Z來說,既然已經從X virtual繼承,也就有了的額外的4byte開銷,也就是說已經不是一個empty class,那個1 char byte for Y(Z) is empty也就說不通了,而且從lippman的本意來看,
“2)編譯器對於特殊情況所提供的優化處理,virtual base class X的1byte subobject也出現在class Y和Z身上,傳統上它被放在derived class的固定(不變動)部分的尾端”,P88中說明,“它把數據直接存放於每一個class object之中,對於繼承而來的nonstatic data member(不管是virtual或nonvirtual base class)均是如此”,
因此,我理解的圖應該是這樣的:
也就是說,父類中成員是直接繼承到子類中的,只不過對於虛擬繼承來說,需要通過一個間接層(表現爲某種形式的指針)來存取,這一想法在3.5節(虛擬繼承)中再次得到驗證,(見P121),書中還提到,empty base class的優化通常把empty base class置於derived class object開頭一部分,從而不花費任何額外空間。由此也可以分析出,A的結構如下:(本書中,候先生沒有給出這張圖)
sizeof(A)爲12個byte,在empty base class優化的情況下,爲8個Byte。
蛇足之二,P234,
候先生改動了lippman的順序(指析構時發生的事情),他的根據是“這樣才符合ctor的相反順序”,這也未免太主觀了一點,過於咬文嚼字了吧,君不見lippman說“2,dtor的函數本身現在被執行,也就是說vptr會在程序員的碼執行之前被重設”,事實上。
struct b {};
struct d1 : virtual public b {};
struct d2 : virtual public b {};
struct d : public d1, d2 {};
Ctor如下: (可參考Vertex3d的ctor)
d()
{
if( __most_derived )
this->b();
this->d1(false), this->d2(false);
vptr = d::vptr;
// usercode
}
file://編譯器擴充dtor如下,按相反順序。
~d()
{
vptr = d::vptr; //**//
//user code...
this->~d2(false), this~d1(false);
if( __most_derived )
this->~b();
}
這不就是lippman的意思嗎(見書上從1到5的順序)?候先生所謂的相反自定義的順序反而難以理解。
當d被析構的時候,先設vptr = d::vptr,再執行析構函數體,然後依次設d2::vptr, d1::vptr, 最後b::vptr,這些都很容易明白。
而候先生第3點表明“如果object內帶一個vptr,則現在被重新設定,指向適當之base class的virtual table”,即認爲vptr的設定爲跳躍式的,是在~d()中user code 執行完之後(//**//標記的那行不執行),設定vptr 爲d2::vptr,然後執行~d2()函數體,最後在~d2()的末尾設定vptr爲d1::vptr,再執行~d1(),奇哉!
這可是一個類設定vptr爲另一個不相關的類的vptr啊,它們之間可是沒有母子關係的噢。個人覺得候先生所制定的順序過於理論化了,而lippman的方法是從實際出發的。
蛇足之三:P263
class Point
{
public:
virtual ~Point() {}
};
class Point3d : public Point
{
};
Point *ptr = new Point3d[10];
for( int ix = 0 ; ix < elem_count ; ++ix )
{
Point *p = &((Point3d*)ptr)[ix];
delete p;
}
候先生將之改爲:
for( int ix = 0 ; ix < elem_count ; ++ix )
{
Point3d *p = &((Point3d*)ptr)[ix];
delete p;
}
這一處很明顯,虛擬析構函數用不着指定明顯類型,就算指定了類型,都是被resolve成(p->vptr[1])(p),不會帶來性能上的改善。
最後指出一點,lippman認爲
Point *ptr = new Point3d[10];
delete[] ptr;
“這樣做完全不是個好主意(具體解釋見P263)”,或許在他那個時候不是吧^_^,但就我在VC6上的測試結果,是完全正確的,所以大家還是可以放心的這樣用,(我沒有測試其它編譯器,有興趣的可以自己試試)。
佳節之日,倉促成文,恐詞不答意,請見諒。
19時16分2001/10/1