struct C041 { C041(): c_(0x01) {} virtual void foo() { c_ = 0x02; } char c_; };
struct C100 : public virtual C041 { C100(): c_(0x02) {} char c_; };
struct C101 : public virtual C041 { C101(): c_(0x03) {} char c_; };
struct C110 : public C100, public C101 { C110(): c_(0x04) {} char c_; };
C110 經由 C100、C101 兩條路徑繼承 C041,虛擬繼承確保只有一份 C041 實體 (解決菱形問題)。
| C100 部分 (5B) | C101 部分 (5B) | C110 部分 (1B) | C041 共享部分 (5B) | | osptr(4),11 | m(1) | osptr(4),6 | m(1) | m(1) | vptr(4) | m(1) |
C100、C101、C110)。C041 在最後),即使未真正共享 (單一虛繼承亦同)。C100、C101 各含一個指向 offset (偏移量) 的指標,用於在執行期定位共享虛基類位置。C100 的 offset=11 (到 C041 起始),C101 的 offset=6。C041 含虛函數表指標 (vptr) 與資料成員 c_。C041 + C100 + C041 + C101 + C110 — 兩份 C041 實體。
C110 c110;
c110.c_ = 0x51; // C110 自身成員 → 直接寫入
c110.C100::c_ = 0x52; // C100 成員 → 直接寫入 (普通繼承)
c110.C101::c_ = 0x52; // C101 成員 → 直接寫入
c110.C041::c_ = 0x53; // 虛基類成員 → 透過 vbptr 間接定位
c110.foo(); // 呼叫虛函數
虛基類成員存取 需兩步:讀取 vbptr → 取得 offset → 基址 + offset 定位成員。
虛函數呼叫 同理:調整 this 指標至虛基類起始 (透過 vbptr),再依 vptr 查虛表呼叫。
struct C041 { virtual void foo() { c_ = 0x02; } char c_; };
struct C140 : virtual C041 { virtual void foo() { c_ = 0x11; } char c_; };
struct C141 : virtual C041 { virtual void foo() { c_ = 0x12; } char c_; };
struct C150 : C140, C141 { virtual void foo() { c_ = 0x21; } char c_; };
| C140 (5B) | C141 (5B) | C150 (1B) | zero padding (4B) | C041 (5B) | | osptr,15 | m | osptr,10 | m | m | 0000 | vptr | m |
C041 仍在最後,含唯一 vptr。obj.foo() 之 this 指標調整C150::foo 位址。this 指標指向虛基類 C041 起始 (位移 15),而非 C150 起始!C150::foo 內部透過 this - 5 (跳過 padding 4B + C041::c_ 1B) 定位到 C150::c_。this 總指向虛基類子物件,衍生類成員以負偏移存取。這是實作細節,非語言規則。pt->foo() 之多態分派
C150* pt = &obj;
pt->foo(); // 多態調用
pt → 取 vptr → 查虛表 → 取 foo 位址。this 傳遞調整後指標 (指向 C041),行為與物件呼叫一致。dynamic_cast<C141*>(pt)pt 非空 → 基址 + 5 (C140 大小) → 指向 C141 子物件起始。foo(),多態分派正確至 C150::foo (最終覆寫者)。this 指向虛基類子物件,衍生類成員以負偏移存取 (編譯器實作策略)。dynamic_cast 在虛繼承下透過靜態已知佈局偏移直接計算目標子物件位址,效率高。