C對象布局及多態(tài)實(shí)現(xiàn)探索之虛繼承

字號:

下面我們來看虛繼承。首先看看這C020類,它從C010虛繼承:}
    struct C010
    {
    C010() : c_(0x01) {}
    void foo() { c_ = 0x02; }
    char c_;
    };
    struct C020 : public virtual C010
    {
    C020() : c_(0x02) {}
    char c_;
    };
    運(yùn)行如下代碼,查看對象的內(nèi)存布局:
    PRINT_SIZE_DETAIL(C020)
    結(jié)果為:
    The size of C020 is 6
    The detail of C020 is c0 c2 45 00 02 01
    很明顯對象的起始處是一個指針,然后是子類的成員變量,接下來是父類的成員變量。和以前的討論不同的是由于使用了虛繼承,父類的成員變量被放到了最后面。
    運(yùn)行如下的代碼:
    C020 c020;
    c020.C010::c_ = 0x04;
    由于子類中的變量和父類中的變量重名,所以我們必須用這種方式來訪問屬于父類的成員變量,普通情況下不需要這種寫法。我們看看后面這行代碼對應(yīng)的匯編代碼:
    0042387E mov eax,dword ptr [ebp FFFFF82Ch]
    00423884 mov ecx,dword ptr [eax 4]
    00423887 mov byte ptr [ebp ecx FFFFF82Ch],4
    前面說過對象的起始是一個指針,第1行指令取到這個指針的值,第2行把這個指針指向的地址后移4字節(jié)后的值(做為一個4字節(jié)的值)取出來。執(zhí)行完這句我們看看ecx寄存器,可知取出來的值為5。最后一行是真正的賦值指令,它通過在對象的起始處(即[ebp FFFFF32Ch])加上ecx中的值做偏移值(即5)來得到賦值的目的地址。接合前面的對象布局輸出,我們可以發(fā)現(xiàn)從對象起始地址開始加5字節(jié)的偏移值,剛好得到父類的成員變量的地址。這樣我們可以大致分析出直接虛繼承的子類的對象布局。
    |子類5            |父類1   ?。?BR>    |偏移值指針4,5|子類成員變量1|父類成員變量1|
    (注:第一個數(shù)字為所在區(qū)域的長度(字節(jié)數(shù)),偏移值指針后的第二個數(shù)字為該指針指向的偏移值。后同。)
    通過查看內(nèi)存可以發(fā)現(xiàn)偏移值指針指向的內(nèi)存前4字節(jié)為0,我不知道它的具體的用途是什么。接下來的4字節(jié)是一個32位的整數(shù),也就是真正的偏移值。即從子類的起始位置到被虛繼承的父類的起始位置的偏移值,在我們前面的例子中這個值為5(一個指針加一個char成員變量)。
    通過這個分析我們可以看到在虛承繼的情況下,通過子類的對象訪問父類的普通成員變量的效率是相當(dāng)?shù)偷?。如果必須用到虛繼承,也應(yīng)該盡量不要在父類中放置普通成員變量(靜態(tài)成員變量不受影響)。