深入理解C++面向對象機制(二)虛繼承
零.聲明
1.《深入理解C++面向對象機制》系列的博文是博主閱讀《深度探索C++對象模型》之後的自我總結性質的文章。當然也希望這些文章能夠幫助那些想深入瞭解C++的網友。
2.文章中會有一些被稱爲“編譯器生成的代碼”,這些代碼並不是編譯器真正的生成代碼,只是爲了方便討論而寫的模擬代碼。
3.如果覺得文章對你有幫助而需要轉載,也請閣下能夠註明出處。
4.如果覺得博文對問題的討論有誤,也可以給博主留言。
一.引入
我們先來看一個例子。
class CX ; class CBase1 ; class CBase2 ; class CDerived;
其中CBase1和CBase2都是CX的派生類,CBase1和CBase2都是CDerived的基類。當我們有一個CDerived的對象的時候,我們只想要一個CX的subobject。在上一篇文章討論的多繼承下,CDerived對象會存在CBase1的subobject和CBase2的subobject。而這兩個subobject都會包含CX的subobject。
這樣一個CDerived對象就會有兩個CX的subobject,這樣和我們的要求不一樣(我們只想要一個呀!!!)
所以就有了虛繼承。
二.虛繼承的實現
1. 例子
按照討論的慣例,我們先提供一個虛繼承的例子。
class CX
{
public:
CX();
virtual ~CX();
private:
int m_x;
public:
virtual void Fun();
};
class CBase1 : public virtual CX
{
public:
CBase1();
virtual ~CBase1();
private:
int m_y;
public:
virtual void Fun();
virtual void Fun1();
};
class CBase2 : public virtual CX
{
public:
CBase2();
virtual ~CBase2();
private:
int m_z;
public:
virtual void Fun();
virtual void Fun2();
};
class CDerived : public CBase1,CBase2
{
public:
CDerived();
~CDerived();
private:
int m_f;
public:
void Fun();
void Fun1();
void Fun2();
virtual void Fun3();
};
接下來便是virtual table圖。
圖1.0
這張virtual table圖相對於之前,變得更加複雜。對於這張圖有一些要說明的(以免引起誤會)。
首先這次我將vptr放在類的底部,之前這個指針都是放在類的頂部的。放在底部僅爲了討論的方便。並沒有規定編譯器一定要將vptr放在類的頂部或者底部。
其次虛繼承下的,virtual table又多了一個“offset to CX subobject”,這個槽的序號往往是一個負數,比如這裏便是-1;type_info的槽序號便是0;虛函數槽都是正數。
而offset這是爲了將this指針移向virtual base subobject的一個偏移量。
根據上一篇文章多繼承的討論,我們將CDerived第一個基類CBase1的virtual table作爲主表,第二個基類CBase2的virtual table作爲次表。
這裏的變化就是又多了一個CX的virtualtable。
最後我們在CDerived的class圖中,就會發現只有一個CX的subobject,而我們只在CBase1和CBase2的virtual table中存放CX subobject的位置(通常就是一個offset)。
2.虛繼承下的構造函數
上一小節,我們展現了虛繼承下的virtual table圖和它是如何實現虛函數的。那麼我們怎麼樣使CX只構造一次呢?
我的意思是如何抑制CX調用兩次構造函數。
最常用的做法,在構造函數中再安插一個bool的參數,_b_most_derived。
現在我們展現一下CDerived的構造函數在編譯器下展開成什麼樣。
CDerived::CDerived(CDerived * this,bool _b_most_derived)
{
//
if (!_b_most_derived)
this->CX::CX();
_b_most_derived = false;
this->CBase1::CBase1(_b_most_derived);
this->CBase2::CBase2(_b_most_derived);
//省略
}
上面的代碼就是對於這個問題的解決方法。編譯器使用_b_most_derived去抑制上層再去調用CX的構造函數。
這裏需要說明的是,構造函數的展開遠沒有上面代碼這麼簡單。這裏只爲討論本文的主題,故而省去了許多細節。對於構造函數的詳細討論將在下一篇博文中討論。
三.結尾
這裏我再次聲明一次,上面的virtual table不是編譯器真的會這麼生成。博主只是爲方便討論才做的,當然也參考了《深度探索C++對象模型》。這裏只是說明博主所介紹的只是一種編譯器實現虛繼承的一種方法。
各種繼承方式的多態特性也討論完畢。下面的文章將介紹構造函數和析構函數。這兩個函數看似簡單,卻暗藏了太多東西。只有深入瞭解了C++對象模型之後,才能真正去了解構造函數和析構函數的意義。