在虛繼承中,虛基類是由最終的衍生類別初始化的,換句話說,最終衍生類別的建構函式必須要調用虛基類的建構函式。對最終的衍生類別來說,虛基類是間接基類,而不是直接基類。這跟普通繼承不同,在普通繼承中,衍生類別建構函式中只能調用直接基類的建構函式,不能調用間接基類的。
下面我們以菱形繼承為例來示範建構函式的調用:
#include <iostream> using namespace std; //虛基類A class A{ public: A(int a); protected: int m_a; }; A::A(int a): m_a(a){ } //直接衍生類別B class B: virtual public A{ public: B(int a, int b); public: void display(); protected: int m_b; }; B::B(int a, int b): A(a), m_b(b){ } void B::display(){ cout<<"m_a="<<m_a<<", m_b="<<m_b<<endl; } //直接衍生類別C class C: virtual public A{ public: C(int a, int c); public: void display(); protected: int m_c; }; C::C(int a, int c): A(a), m_c(c){ } void C::display(){ cout<<"m_a="<<m_a<<", m_c="<<m_c<<endl; } //間接衍生類別D class D: public B, public C{ public: D(int a, int b, int c, int d); public: void display(); private: int m_d; }; D::D(int a, int b, int c, int d): A(a), B(90, b), C(100, c), m_d(d){ } void D::display(){ cout<<"m_a="<<m_a<<", m_b="<<m_b<<", m_c="<<m_c<<", m_d="<<m_d<<endl; } int main(){ B b(10, 20); b.display(); C c(30, 40); c.display(); D d(50, 60, 70, 80); d.display(); return 0; } 運行結果:
m_a=10, m_b=20
m_a=30, m_c=40
m_a=50, m_b=60, m_c=70, m_d=80
請注意第 50 行代碼,在最終衍生類別 D 的建構函式中,除了調用 B 和 C 的建構函式,還調用了 A 的建構函式,這說明 D 不但要負責初始化直接基類 B 和 C,還要負責初始化間接基類 A。而在以往的普通繼承中,衍生類別的建構函式只負責初始化它的直接基類,再由直接基類的建構函式初始化間接基類,使用者嘗試調用間接基類的建構函式將導致錯誤。
現在採用了虛繼承,虛基類 A 在最終衍生類別 D 中只保留了一份成員變數 m_a,如果由 B 和 C 初始化 m_a,那麼 B 和 C 在調用 A 的建構函式時很有可能給出不同的實參,這個時候編譯器就會犯迷糊,不知道使用哪個實參初始化 m_a。
為了避免出現這種矛盾的情況,C++ 乾脆規定必須由最終的衍生類別 D 來初始化虛基類 A,直接衍生類別 B 和 C 對 A 的建構函式的調用是無效的。在第 50 行代碼中,調用 B 的建構函式時試圖將 m_a 初始化為 90,調用 C 的建構函式時試圖將 m_a 初始化為 100,但是輸出結果有力地證明了這些都是無效的,m_a 最終被初始化為 50,這正是在 D 中直接調用 A 的建構函式的結果。
另外需要關注的是建構函式的執行順序。虛繼承時建構函式的執行順序與普通繼承時不同:在最終衍生類別的建構函式調用列表中,不管各個建構函式出現的順序如何,編譯器總是先調用虛基類的建構函式,再按照出現的順序調用其他的建構函式;而對於普通繼承,就是按照建構函式出現的順序依次調用的。
修改本例中第 50 行代碼,改變建構函式出現的順序:
D::D(int a, int b, int c, int d): B(90, b), C(100, c), A(a), m_d(d){ }
雖然我們將 A() 放在了最後,但是編譯器仍然會先調用 A(),然後再調用 B()、C(),因為 A() 是虛基類的建構函式,比其他建構函式優先順序高。
一般情況下是按照繼承的順序來調用建構函式。(與初始化建構函式時參數列表無關)