首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ 繼承深度解析:單繼承、多重繼承、菱形繼承與虛繼承的記憶體佈局

     編輯:關於C++

前言

繼承是 C++ 物件導向的核心機制,但其背後的記憶體佈局、建構/解構順序、虛函數表初始化等細節往往被忽視。本文通過反組合代碼與記憶體圖示,系統剖析單繼承、多重繼承、抽象類別、菱形繼承及虛繼承的底層實現。

一、單繼承:線性佈局與建構鏈

記憶體佈局

衍生類物件記憶體從低址到高址依序排列:基類成員 → 衍生類成員。基類子物件位於衍生類物件起始位置,故基類指標可直接指向衍生類物件無需偏移。

class Base { int b; };
class Derived : public Base { int d; };
// 記憶體:[Base::b][Derived::d]

建構與解構順序

  1. 建構:基類建構函式 → 成員物件建構 → 衍生類建構函式體。初始化列表順序不影響實際初始化順序,編譯器按記憶體佈局順序初始化。
  2. 解構:衍生類解構函式體 → 成員物件解構 → 基類解構函式,順序完全相反。

虛函數表初始化

若基類有虛函數,衍生類覆寫時會發生兩次 vptr 初始化:

  1. 基類建構時:vptr 指向基類 vtable。
  2. 衍生類建構時:vptr 覆寫指向衍生類 vtable。

故在基類建構函式中調用虛函數會解析到基類版本(靜態綁定),而在衍生類建構函式中則為動態綁定。

二、多重繼承:多基類與多 vptr

記憶體佈局

按繼承宣告順序線性排列各基類子物件,最後是衍生類自身成員。每個含虛函數的基類都有自己的 vptr,故衍生類物件包含多個 vptr。

class Base1 { virtual void f1(); int b1; };
class Base2 { virtual void f2(); int b2; };
class Derived : public Base1, public Base2 { int d; };
// 佈局:[Base1::vptr][Base1::b1][Base2::vptr][Base2::b2][Derived::d]

指標轉型與偏移調整

將 `Derived*` 轉為 `Base2*` 時,編譯器自動加上偏移量(`sizeof(Base1)`),使指標指向 `Base2` 子物件起始處。此調整在編譯期完成,零成本。

建構順序

依繼承宣告順序:`Base1()` → `Base2()` → `Derived()`。各基類建構時初始化各自 vptr,最後衍生類建構覆寫所有 vptr 指向自己的 vtable。

三、抽象類別與純虛函數

含純虛函數(`=0`)的類別為抽象類別,不可實例化。其 vtable 中對應槽位填入 `__cxa_pure_virtual`(或 MSVC 的 `_purecall`),防止誤調用純虛函數導致未定義行為。衍生類實作後,vtable 槽位替換為實際函數地址。

四、菱形繼承與虛繼承

問題:重複基類子物件與二義性

class Grand { int g; };
class Parent1 : public Grand { int p1; };
class Parent2 : public Grand { int p2; };
class Child : public Parent1, public Parent2 { int c; };
// Child 含兩份 Grand::g → 存取 g 產生二義性,轉型 Grand* 也模稜兩可

虛繼承解決方案

在繼承基類時加 `virtual`:

class Parent1 : virtual public Grand { ... };
class Parent2 : virtual public Grand { ... };

虛繼承記憶體佈局(關鍵差異)

  • 共享單一基類子物件:`Grand` 只有一份,通常放在衍生類物件末尾。
  • 引入虛基類表指標 (vbptr):每個虛繼承的中間類別(`Parent1`、`Parent2`)增加一個 `vbptr`,指向虛基類表(vbtable),表中記錄從該子物件到虛基類的偏移量。
  • 指標轉型需查表:`Child* → Grand*` 需透過 `vbptr` 查偏移量動態計算,不再是固定編譯期偏移。

代價:每條虛繼承路徑增加 4/8 字節 `vbptr`,且轉型有間接查表開銷。但消除了資料重複與二義性。

建構函式特殊規則

最衍生類直接負責建構虛基類。中間類別建構函式收到「是否建構虛基類」的旗標參數(通常為 0/1),僅最衍生類首次建構時傳 1,後續傳 0 跳過。這確保虛基類只被建構一次。

總結對照表

特性單繼承多重繼承虛繼承(菱形)
基類子物件數1多個虛基類僅 1 份
vptr 數量0 或 1每個含虛函數基類 1 個同多重繼承 + vbptr
指標轉型無偏移或固定偏移固定偏移需透過 vbtable 動態查偏移
建構順序基類 → 衍生類宣告順序依次虛基類最先(由最衍生類建構) → 非虛基類 → 衍生類
適用場景單一介面擴充混入多個介面/實作共享公共基類狀態

工程建議

  1. 單繼承優先,滿足「是一種」關係。
  2. 多重繼承僅用於「介面類(純虛)」混入,避免多個含資料成員的基類。
  3. 菱形繼承必須用虛繼承,否則資料重複與二義性極難維護。
  4. 虛繼承增加複雜度與空間開銷,權衡後再用;優先考慮組合替代繼承。

深入理解繼承的記憶體模型,是寫出高效、正確、可維護 C++ 代碼的關鍵基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved