在《多繼承》中講過的例子中,由類A,類B1和類B2以及類C組成了類繼承的階層。在該結構中,類C的對象將包含兩個類A的子物件。由於類A是衍生類別C兩條繼承路徑上的一個公用基類,那麼這個公用基類將在衍生類別的對象中產生多個基類子物件。如果要想使這個公用基類在衍生類別中只產生一個基類子物件,則必須將這個基類設定為虛基類。
虛基類的引入和說明
前面簡單地介紹了要引進虛基類的原因。實際上,引進虛基類的真正目的是為瞭解決二義性問題。
虛基類說明格式如下:
virtual <繼承方式><基類名>
其中,virtual是虛類的關鍵字。虛基類的說明是用在定義衍生類別時,寫在衍生類別名的後面。例如:
class A { public: void f(); protected: int a; }; class B : virtual public A { protected: int b; }; class C : virtual public A { protected: int c: }; class D : public B, public C { public: int g(); private: int d; }; |
由於使用了虛基類,使得類A,類B,類C和類D之間關係用DAG圖示法表示如下:
A{ f(), a }
/ /
B{b} C{c}
/ /
D{g(),d}
從該圖中可見不同繼承路徑的虛基類子物件被合并成為一個對象。這便是虛基類的作用,這樣將消除了合并之前可能出現的二義性。這時,在類D的對象中只存在一個類A的對象。因此,下面的引用都是正確的:
D n;
n.f(); file://對f()引用是正確的。
void D::g()
{
f(); file://對f()引用是正確的。
}
下面程式段是正確的。
D n;
A *pa;
pa = &n;
其中,pa是指向類A對象的指標,n是類D的一個對象,&n是n對象的地址。pa=&n是讓pa指標指向類D的對象,這是正確的,並且也無二義性。
虛基類的建構函式
前面講過,為了初始化基類的子物件,衍生類別的建構函式要調用基類的建構函式。對於虛基類來講,由於衍生類別的對象中只有一個虛基類子物件。為保證虛基類子物件只被初始化一次,這個虛基類建構函式必須只被調用一次。由於繼承結構的層次可能很深,規定將在建立對象時所指定的類稱為最衍生類別。C++規定,虛基類子物件是由最衍生類別的建構函式通過調用虛基類的建構函式進行初始化的。如果一個衍生類別有一個直接或間接的虛基類,那麼衍生類別的建構函式的成員初始列表中必須列出對虛基類建構函式的調用。如果未被列出,則表示使用該虛基類的預設建構函式來初始化衍生類別對象中的虛基類子物件。
從虛基類直接或間接繼承的衍生類別中的建構函式的成員初始化列表中都要列出這個虛基類建構函式 的調用。但是,只有用於建立對象的那個最衍生類別的建構函式調用虛基類的建構函式,而該衍生類別的基類中所列出的對這個虛基類的建構函式調用在執行中被忽略,這樣便保證了對虛基類的對象只初始化一次。
C++又規定,在一個成員初始化列表中出現對虛基類和非虛基類建構函式的調用,則虛基類的建構函式先於非虛基類的建構函式的執行。
下面舉一例子說明具有虛基類的衍生類別的建構函式的用法。
#include class A { public: A(const char *s) { cout< ~A() {} };class B : virtual public A { public: B(const char *s1, const char *s2):A(s1) { cout< } }; class C : virtual public A { public: C(const char *s1, const char *s2):A(s1) { cout< } }; class D : public B, public C { public: D(const char *s1, const char *s2, const char *s3, const char *s4) :B(s1, s2), C(s1, s3), A(s1) { cout< } }; void main() { D *ptr = new D("class A", "class B", "class C", "class D"); delete ptr; } |
該程式的輸出結果為:
class A
class B
class C
class D
在衍生類別B和C中使用了虛基類,使得建立的D類對象只有一個虛基類子物件。
在衍生類別B,C,D的建構函式的成員初始化列表中都包含了對虛基類A的建構函式。
在建立類D對象時,只有類D的建構函式的成員初始化列表中列出的虛基類建構函式被調用,並且僅調用一次,而類D基類的建構函式的成員初始化列表中列出的虛基類建構函式不被執行。這一點將從該程式的輸出結果可以看出。