虛基類的影響
1.多繼承
很多時候,一個子類可能有多個父類,比如美人魚既是人也是魚,冬蟲夏草,可以看視訊可以上網的手機,為了增強程式碼複用能力,就有了多繼承,示例程式碼如下:
class Base_A
{
public:
Base_A() :a(0x10), b(0x20)
{ }
int a;
int b;
};
class Base_B
{
public:
Base_B() :c(0x30), d(0x40)
{ }
int c;
int d;
};
class Inherit :public Base_A, public Base_B
{
public:
Inherit() :e(0x50)
{ }
int e;
};
int main()
{
Inherit obj;
return 0;
}
程式碼中,Inherit的物件,就能夠使用從兩個父類繼承下來的所有資料和方法(需要考慮許可權問題)。我們來看一下它的記憶體模型:
可以看到,子類物件包含著父類的全部資料,我們再看另外一種情況:
class Base_A
{
public:
Base_A() :a(0x10), b(0x20)
{ }
int a;
int b;
};
class Base_B
{
public:
Base_B() :c(0x30), d(0x40)
{ }
int c;
int d;
};
class Inherit :public Base_B, public Base_A
{
public:
Inherit() :e(0x50)
{ }
int e;
};
int main()
{
Inherit obj;
return 0;
}
記憶體模型如下:
此時我們可以得出一些簡單的結論:
派生類放在最下面
多個父類的情況下,誰在上,誰在下,由繼承順序決定。
子類總是包含全部的父類
2.多繼承中的二義性問題
暮光之城中有這麼一種物種叫做狼人,
暮色之時是人類,新月到破曉就是狼人了,它有著鋒利的牙齒,恐怖的速度,還能兩個腿奔跑。它可以由狼類和人類共同派生出來。但是有一個問題,就是狼類中可能會有腿的數量,牙齒的數量等等屬性,恰好人類中也有腿的數量,牙齒的數量等等屬性。我們知道子類會具有全部父類的所有成員。那麼此時此刻,狼人物件訪問腿的數量,牙齒的數量的時候,會訪問哪個父類的成員呢?有人已經想出了辦法,就是把狼和人都有的成員抽象出來,形成一個爺爺類,比如叫做動物類,在狼類和人類的上面,形成如下圖所示的情況:
為了解決我們心中的疑惑,我們可以做個試驗,先看下面這段程式碼:
class Animal
{
public:
Animal() :m_nNumberOfLegs(5)//預設5條腿^o^
{ }
public:
int m_nNumberOfLegs;
};
class Wolf :public Animal
{
public:
Wolf() :m_nWolfSomeThing(0x10)
{ }
public:
int m_nWolfSomeThing;
};
class Human :public Animal
{
public:
Human() :m_nHumanSomeThing(0x20)
{ }
public:
int m_nHumanSomeThing;
};
class Werwolf :public Wolf, public Human
{
public:
Werwolf() :m_nWerwolfSomeThing(0x30)
{ }
public:
int m_nWerwolfSomeThing;
};
int main()
{
Werwolf obj;
return 0;
}
檢視狼人類記憶體模型:
我們發現有兩份腿的數量,這是因為子類物件會包含全部的父類成員。對於狼來說,自然會包含動物類中的腿的數量。對於人來說,也是如此。對於狼人來說,會同時包含狼類和人類的所有成員。故而腿的數量這個欄位,在狼人物件中依然是出現兩份,一份在狼中,一份在人中,這是典型的菱形繼承問題。
3.虛繼承
為了解決上面這個問題,產生了一種叫做虛繼承的機制:虛繼承是為了解決二義性的問題而產生的語法。用法是在繼承之前加上一個virtual,我們來看一下最為簡單的情況,下面的例子可以幫助我們理解虛繼承:
class Base
{
public:
Base() :m_B(0x10)
{ }
public:
int m_B;
};
class Inherit :virtual public Base
{
public:
Inherit() :m_I(0x20)
{ }
public:
int m_I;
};
int main()
{
Inherit obj;
printf("虛繼承的物件大小%d", sizeof(obj));
return 0;
}
我們可以看一看輸出結果:(結果可能會讓你大吃一驚哦)
有人可能會問不是應該為8個位元組麼,怎麼會是12呢,那多出來的四個位元組究竟是什麼?
好,下面我們看一看它的記憶體模型:
我們可以看到在整個物件的開頭多了一個奇怪的資料,並且神奇的是子類資料位於基類資料的上面,我們來解釋它在幹什麼:
通過查閱相關文獻,得知頭四個位元組實際上是一個地址,即0x01186b30,
我們可以檢視一下:
剛才的那個地址,我們稱之為虛基類表指標,指向的位置儲存的是一共有兩個元素,分別是兩個差值:
1 本類地址與虛基類表指標地址的差
2 虛基類地址與虛基類表指標地址的差
struct VirtualBase
{
int Offset1;
int Offset2;
}
這裡我們著重關注第二個,它能夠實現這樣的事情:基類與派生類可以不挨在一起,是通過虛基類表中的差值,從派生類就可以找到基類的資料。
我們直接看複雜一些的情況,結合上面的例子更加容易理解一些:
class Base
{
public:
Base() :m_Base(0x10)
{ }
public:
int m_Base;
};
class Inherit_A :virtual public Base
{
public:
Inherit_A() :m_A(0x20)
{ }
public:
int m_A;
};
class Inherit_B :virtual public Base
{
public:
Inherit_B() :m_B(0x30)
{ }
public:
int m_B;
};
class Test :public Inherit_A, public Inherit_B
{
public:
Test() :m_T(0x40)
{ }
public:
int m_T;
};
int main()
{
Test obj;
printf("虛繼承的物件大小%d", sizeof(obj));
return 0;
}
輸出結果:
這個結果估計大多數人都沒有猜到,呵呵我們可以來看一下它的記憶體模型:
可以看出: 從上到下的順序是A,B,派生類,基類Base。Base類被甩到了最後,並且只有一個。
Inherit_A與Inherit_B共用一個虛基類。這個機制,無論是幾個中間內一層的類,都能保證虛基類的資料只有一份,這就是虛繼承解決多繼承中二義性的問題.
小結一下:
進行如圖所示的虛繼承
編譯器會把虛基類單獨置於一處,派生類通過虛基類表指標指向位置儲存的差值能夠找到虛基類,當類似於圖示的情況下的時候,使得孫子類無論從哪一條支路尋找爺爺類(虛基類),找到的都是同一個爺爺。
對於類物件大小,每一個虛繼承的子類由於都會有一個虛基類指標,故而多一個虛繼承,整個物件的大小就會比正常大4個位元組。(這一點與虛擬函式那邊有點類似,呵呵)
虛基類實際上不需要一定放在下面,放在任何位置都可以,因為大家是通過一個差值找到的它。
終於寫完了,呵呵,這是本人在看雪上面發的第一個帖子,因本人水平有限 ,難免會有紕漏,還請各位多提寶貴意見。(本來寫個目錄導航的,結果用toc寫出來之後不知怎麼搞的竟然生成多份目錄,不知什麼原因)