虛擬繼承是C++語言中一個非常重要但是又比較生僻的存在,它的定義非常簡單,但是對於理解C++的繼承機制卻是非常有用的。筆者最近學習過程中發現對C++的虛擬繼承不是很明朗,故在這裡對虛繼承做個小結。
首先說下遇到的問題吧。代碼如下(代碼來自於何海濤《程序員面試精選100題第32題)。意圖是要設計一個不能被繼承的類,類似java中的final。但是又可以生成棧對象,可以像一般的C++類一樣使用。
#include<iostream>
using std::endl;
using std::cout;
template <class T> class MakeFinal
{
friend T;
private:
MakeFinal()
{
cout<<"in MakeFinal"<<endl;
}
~MakeFinal(){}
};
class FinalClass: virtual public MakeFinal<FinalClass>
{
public:
FinalClass()
{
cout<<"in FinalClass"<<endl;
}
~FinalClass(){}
};
class Try: public FinalClass
{
public:
Try()
{
cout<<"in Try"<<endl;
}
};
這樣的確使得FinalClass不能被繼承了,原因在於類FinalClass是從類MakeFinal<Final>虛繼承過來的,在調用Try的構造函數的時候,會直接跳過FinalClass而直接調用MakeFinal<FinalClass>的構造函數。而Try不是MakeFinal<Final>的友元,所以這裡就會出現編譯錯誤。但是如果把虛繼承改成一般的繼承,這裡就沒什麼問題了。筆者對這裡的調用順序不是很明朗,為了對虛繼承有徹底的了解,故做個小結。將從下面幾個方向進行總結:1、為何要有虛繼承;2、虛繼承對於類的對象布局的影響;3、虛基類對構造函數的影響;
1、為什麼需要虛繼承
由於C++支持多重繼承,那麼在這種情況下會出現重復的基類這種情況,也就是說可能出現將一個類兩次作為基類的可能性。比如像下面的情況
#include<iostream>
using std::cout;
using std::endl;
class Base
{
protected:
int value;
public:
Base()
{
cout<<"in Base"<<endl;
}
};
class DerivedA:protected Base
{
public:
DerivedA()
{
cout<<"in DerivedA"<<endl;
}
};
class DerivedB: protected Base
{
public:
DerivedB()
{
cout<<"in DerivedB"<<endl;
}
};
class MyClass:DerivedA,DerivedB
{
public:
MyClass()
{
cout<<"in MyClass"<<value<<endl;
}
};
編譯時的錯誤如下
這中情況下會造成在MyClass中訪問value時出現路徑不明確的編譯錯誤,要訪問數據,就需要顯示地加以限定。變成DerivedA::value或者DerivedB::value,以消除歧義性。並且,通常情況下,像Base這樣的公共基類不應該表示為兩個分離的對象,而要解決這種問題就可以用虛基類加以處理。如果使用虛繼承,編譯便正常了,類的結構示意圖便如下。
虛繼承的特點是,在任何派生類中的virtual基類總用同一個(共享)對象表示,正是如上圖所示。