[深度探索C++對象模型](簡體版)中的蛇足

 

<深度探索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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章