虚继承

問題的產生

考慮下面的類的層次和關係。
class Animal {
 public:
  virtual void eat();};class Mammal : public Animal {
 public:
  virtual void breathe();};class WingedAnimal : public Animal {
 public:
  virtual void flap();};// A bat is a winged mammalclass Bat : public Mammal, public WingedAnimal {};Bat bat;
按照上面的定義，呼叫bat.eat()是有歧義的，因為在Bat中有兩個Animal基礎類別（間接的），所以所有的Bat物件都有兩個不同的Animal基礎類別的子物件。因此，嘗試直接參考Bat物件的Animal子物件會導致錯誤，因為該繼承是有歧義的：
Bat b;Animal &a = b; // error: which Animal subobject should a Bat cast into, 
               // a Mammal::Animal or a WingedAnimal::Animal?
要消除歧義，需要顯式的將bat轉換為每一個基礎類別子物件：
Bat b;Animal &mammal = static_cast<Mammal&> (b); 
Animal &winged = static_cast<WingedAnimal&> (b);

為了正確的呼叫eat()，還需要相同的可以消歧義的語句：static_cast<Mammal&>(bat).eat()或static_cast<WingedAnimal&>(bat).eat().
在這個例子中，我們可能並不需要Animal被繼承兩次，我們只想建立一個模型來說明這層關係（Bat 屬於 Animal）；Bat是Mammal也是WingedAnimal並不意味著它是兩個Animal：Animal定義的功能由Bat來實現（上面「是」的屬性實際上是「實現需求」的含義），且一個Bat只實現一次。「只是一個」的真正含義是Bat只有一種實現eat()的方法，無論是從Mammal的角度還是從WingedAnimal的角度來看。（在上面的第一段代碼範例中我們看到eat()並沒有在Mammal或WingedAnimal中被過載，所以這兩個Animal子物件實際上是以相同的方式運作，但這只是一個不完善的例子，從C++的角度來看二者之間正好沒有實際的區別。）
若將上面的關係以圖形方式表示看起來類似菱形，所以這一情況也被稱為菱形繼承。虛繼承可以解決這一問題。

解決方法

我們可以按如下方式重新聲明上面的類：
class Animal {
 public:
  virtual void eat();};// Two classes virtually inheriting Animal:class Mammal : public virtual Animal {
 public:
  virtual void breathe();};class WingedAnimal : public virtual Animal {
 public:
  virtual void flap();};// A bat is still a winged mammalclass Bat : public Mammal, public WingedAnimal {};
Bat::WingedAnimal中的Animal部分現在和Bat::Mammal中的Animal部分是相同的了，這也就是說Bat現在有且只有一個共享的Animal部分，所以對於Bat::eat()的呼叫就不再有歧義了。另外，直接將Bat例項分派給Animal例項的過程也不會產生歧義了，因為現在只存在一種可以轉換為Animal的Bat實體了。
因為Mammal例項的起始位址和其Animal部分的記憶體偏移量直到程式執行分配記憶體時才會明確，所以虛繼承應用給Mammal和WingedAnimal建立了虛表（vtable）指標（「vpointer」）。因此「Bat」包含vpointer, Mammal, vpointer, WingedAnimal, Bat, Animal。這裡共有兩個虛表指標，其中最衍生類別的物件位址所指向的虛表指標，指向了最衍生類別的虛表；另一個虛表指標指向了WingedAnimal的類的虛表。Animal虛繼承而來。在上面的例子裡，一個分配給Mammal，另一個分配給WingedAnimal。因此每個物件占用的記憶體增加了兩個指標的大小，但卻解決了Animal的歧義問題。所有Bat類的物件都包含這兩個虛指標，但是每一個物件都包含唯一的Animal物件。假設一個類Squirrel聲明繼承了Mammal，那麼Squirrel中的Mammal物件的虛指標和Bat中的Mammal物件的虛指標是不同的，儘管他們占用的記憶體空間大小是相同的。這是因為在記憶體中Mammal到Animal的距離是相同的。虛表不同而實際上占用的空間相同。

虛基礎類別的初始化

由於虛基礎類別是多個衍生類別共享的基礎類別，因此由誰來初始化虛基礎類別必須明確。C++標準規定，由最衍生類別直接初始化虛基礎類別。因此，對間接繼承了虛基礎類別的類，也必須能直接存取其虛繼承來的祖先類，也即應知道其虛繼承來的祖先類的位址偏移值。
例如，常見的「菱形」虛繼承例子中，兩個衍生類別、一個最衍生類別的建構函式的初始化列表中都可以給出虛基礎類別的初始化；但只由最衍生類別的建構函式實際執行虛基礎類別的初始化。

g++與虛繼承

g++編譯器生成的C++類別例項，虛擬函式與虛基礎類別位址偏移值共用一個虛表（vtable）。類別例項的開始處即為指向所屬類的虛指標（vptr）。實際上，一個類與它的若干祖先類（父類、祖父類、...）組成部分共用一個虛表，但各自使用的虛表部分依次相接、不相重疊。
g++編譯下，一個類別例項的虛指標指向該類虛表中的第一個虛擬函式的位址。如果該類沒有虛擬函式（或者虛擬函式都寫入了祖先類的虛表，覆蓋了祖先類的對應虛擬函式），因而該類自身虛表中沒有虛擬函式需要填入，但該類有虛繼承的祖先類，則仍然必須要存取虛表中的虛基礎類別位址偏移值。這種情況下，該類仍然需要有虛表，該類別例項的虛指標指向類虛表中一個值為0的條目。
該類其它的虛擬函式的位址依次填在虛表中第一個虛擬函式條目之後（記憶體位址自低向高方向）。虛表中第一個虛擬函式條目之前（記憶體位址自高向低方向），依次填入了typeinfo（用於RTTI）、虛指標到整個物件開始處的偏移值、虛基礎類別位址偏移值。因此，如果一個類虛繼承了兩個類，那麼對於32位元程式，虛繼承的左父類位址偏移值位於vptr-0x0c，虛繼承的右父類位址偏移值位於vptr-0x10.
一個類的祖先類有複雜的虛繼承關係，則該類的各個虛基礎類別偏移值在虛表中的儲存順序尊重自該類到祖先的深度優先遍歷次序。

Microsoft Visual C++與虛繼承

Microsoft Visual C++與g++不同，把類的虛擬函式與虛基礎類別位址偏移值分別放入了兩個虛表中，前者稱為虛擬函式表vftbl，後者稱虛基礎類別表vbtbl。因此一個類別例項可能有兩個虛指標分別指向類的虛擬函式表與虛基礎類別表，這兩個虛指標分別稱為虛擬函式表指標vftbl與虛基礎類別表指標vbtbl。當然，類別例項也可以只有一個虛指標，或者沒有虛指標。虛指標總是放在類別例項的成員變數之前，且虛擬函式表指標總是在虛基礎類別表指標之前。因而，對於某個類別例項來說，如果它有虛基礎類別指標，那麼虛基礎類別指標可能在類別例項的0位元組偏移處，也可能在類別例項的4位元組偏移處（對於32位元程式來說），這給類別成員函式指標的實現帶來了很大麻煩。
一個類的虛基礎類別指標指向的虛基礎類別表的首個條目，該條目的值是虛基礎類別表指標所在的位址到該類別的例項的記憶體首位址的偏移值。即&(obj.vbtbl) - &obj。虛基礎類別第2、第3、... 個條目依次為該類的最左虛繼承父類、次左虛繼承父類、...的記憶體位址相對於虛基礎類別表指標自身位址，即 &(obj.vbtbl)的偏移值。
如果一個類同時有虛繼承的父類與祖父類，則虛祖父類放在虛父類前面。
另外需要注意的是，類的虛擬函式表的第一項之前的項（即*(obj.vftbl-1)）為最衍生類別例項的記憶體首位址到當前虛擬函式表指標的偏移值，即mostDerivedObj-obj.vftbl。衍生類別的虛擬函式覆蓋基礎類別的虛擬函式時，在基礎類別的虛擬函式表的對應條目寫入的是一個「樁」(thunk)函式的入口位址，以調整this指標指向到衍生類別例項的位址，再呼叫衍生類別的對應的虛擬函式。例如：this -= offset; call DerivedClass:virtFunc;

虛繼承的應用：不可衍生的finally類

一個類如果不希望被繼承，類似於Java中的具有finally性質的類，這在C++中可以用虛繼承來實現：
template<typename T> class MakeFinally{
   private:
       MakeFinally(){};//只有MakeFinally的友类才可以构造MakeFinally
       ~MakeFinally(){};
   friend T;};class MyClass:public virtual  MakeFinally<MyClass>{};//MyClass是不可派生类//由于虚继承，所以D要直接负责构造MakeFinally类，从而导致编译报错，所以D作为派生类是不合法的。class D: public MyClass{};//另外，如果D类没有实例化对象，即没有被使用，实际上D类是被编译器忽略掉而不报错int main(){MyClass var1;// D var2;  //这一行编译将导致错误，因为D类的默认构造函数不合法}