虛擬基底類別
由於類別可以多次做為衍生類別的間接基底類別,因此 C++ 針對此類的基底類別工作提供一種最佳化的方式。 虛擬基底類別可節省空間,並在使用多重繼承的類別階層架構時避免出現模稜兩可的問題。
每個非虛擬物件都包含基底類別中定義的資料成員。 重複項目不僅佔用空間,您還必須在存取時指定您需要的基底類別成員複本。
將基底類別指定為虛擬基底類別時,可多次將其當做間接基底,而不需要使用其資料成員的複本。 其資料成員的單一複本會由所有基底類別共用 (這些類別會將其當做虛擬基底使用)。
宣告虛擬基底類別時,會在衍生類別的基底清單中顯示 virtual 關鍵字。
考慮下圖中的類別階層架構,其中示範模擬的午餐供應線。
模擬的午餐線圖表
在圖中,Queue 為 CashierQueue 和 LunchQueue 的基底類別。 不過,在將這兩個類別合併為 LunchCashierQueue 時會發生下列問題:新的類別會內含兩個來自 Queue 類型的子物件,其中一個來自 CashierQueue,另一個則是來自 LunchQueue。 下圖顯示概念性的記憶體配置 (實際的記憶體配置可能會進行最佳化)。
模擬的午餐線物件
請注意,LunchCashierQueue 物件中有兩個 Queue 子物件。 下列程式碼會將 Queue 宣告為虛擬基底類別:
// deriv_VirtualBaseClasses.cpp
// compile with: /LD
class Queue {};
class CashierQueue : virtual public Queue {};
class LunchQueue : virtual public Queue {};
class LunchCashierQueue : public LunchQueue, public CashierQueue {};
使用 virtual 關鍵字可確保只會包含一個 Queue 子物件的複本 (請參閱下圖)。
含有虛擬基底類別的模擬 Lunch-Line 物件
一個類別可以擁有一個虛擬元件和一個特定類型的非虛擬元件。 此情況會發生在如下圖中示範的情況下。
相同類別的虛擬與非虛擬元件
在圖中,CashierQueue 和 LunchQueue 使用 Queue 做為虛擬基底類別。 不過,TakeoutQueue 指定 Queue 做為基底類別,而不是虛擬基底類別。 因此,LunchTakeoutCashierQueue 內含兩個類型為 Queue 的子物件:一個是來自包含 LunchCashierQueue 的繼承路徑,另一個是來自包含 TakeoutQueue 的路徑。 下圖中說明此情形。
具有虛擬和非虛擬繼承的物件配置
注意事項 |
---|
與使用非虛擬繼承相比較,使用虛擬繼承在大小方面提供相當大的優勢。不過,它可能會增加額外的處理負擔。 |
如果衍生類別會覆寫從虛擬基底類別繼承的虛擬函式,且衍生基底類別的建構函式或解構函式使用虛擬基底類別的指標呼叫函式,編譯器可能會在內含虛擬基底的類別中採用額外的隱藏 [vtordisp] 欄位。 /vd0 編譯器選項會抑制隱藏 vtordisp 建構函式/解構函式替代成員的加入。 /vd1 編譯器選項 (預設值) 會在必要時啟用它們。 請只有在您確定所有類別建構函式和解構函式都會實際呼叫虛擬函式時,才關閉 vtordisps。
/vd 編譯器選項會影響整個編譯模組。 請對各個類別逐一使用 vtordisp pragma 加以抑制,然後再重新啟用 vtordisp 欄位:
#pragma vtordisp( off )
class GetReal : virtual public { ... };
#pragma vtordisp( on )