在C++中,我們會遇到virtual這個關鍵字,但是它有兩種含義:虛函數和虛繼承,它們兩個是完全無相關的兩個概念。
什麼是虛繼承
虛繼承是解決C++多重繼承問題的一種手段,從不同途徑繼承來的同一基類,會在子類中存在多份拷貝。這將存在兩個問題:其一,浪費存儲空間;第二,存在二義性問題,通常可以將派生類對象的地址賦值給基類對象,實現的具體方式是,將基類指針指向繼承類(繼承類有基類的拷貝)中的基類對象的地址,但是多重繼承可能存在一個基類的多份拷貝,這就出現了二義性。
當一個基類被聲明爲虛基類後,即使它成爲了多繼承鏈路上的公共基類,最後的派生類中也只有它的一個備份。例如:
class CBase { };
class CDerive1:virtual public CBase{ };
class CDerive2:virtual public CBase{ };
class CDerive12:public CDerive1,CDerive2{ };
則在類CDerive12的對象中,僅有類CBase的一個對象數據
虛繼承實現原理
虛繼承底層實現原理與編譯器相關,一般通過虛基類指針和虛基類表實現,每個虛繼承的子類都有一個虛基類指針(任何類型的指針變量都是佔用4個字節)和虛基類表(不佔用類對象的存儲空間)(需要強調的是,虛基類依舊會在子類裏面存在拷貝,只是僅僅最多存在一份而已,並不是不在子類裏面了);當虛繼承的子類被當做父類繼承時,虛基類指針也會被繼承。
實際上,vbptr指的是虛基類表指針,該指針指向了一個虛基類表,虛表中記錄了虛基類與本類的偏移地址;通過偏移地址,這樣就找到了虛基類成員,而虛繼承也不用像普通多繼承那樣維持着公共基類(虛基類)的兩份同樣的拷貝,節省了存儲空間。
虛繼承與虛函數的異同
相似之處:都利用了虛指針(均佔用類的存儲空間)和虛表(均不佔用類的存儲空間)。
不同之處:虛基類依舊存在繼承類中,只佔用存儲空間;虛函數不佔用存儲空間。虛基類表存儲的是虛基類相對直接繼承類的偏移;而虛函數表存儲的是虛函數地址。
#include<iostream>
using namespace std;
class A //大小爲4
{
public:
int a;
};
class B :virtual public A //大小爲12,變量a,b共8字節,虛基類表指針4
{
public:
int b;
};
class C :virtual public A //與B一樣12
{
public:
int c;
};
class D :public B, public C //24,變量a,b,c,d共16,B的虛基類指針4,C的虛基類指針
{
public:
int d;
};
int main()
{
A a;
B b;
C c;
D d;
cout << sizeof(a) << endl;
cout << sizeof(b) << endl;
cout << sizeof(c) << endl;
cout << sizeof(d) << endl;
system("pause");
return 0;
}
//
//1 > class A size(4) :
//1 > +-- -
//1 > 0 | a
//1 > +-- -
//1 >
//1 > class B size(12) :
//1 > +-- -
//1 > 0 | {vbptr}
//1 > 4 | b
//1 > +-- -
//1 > +-- - (virtual base A)
//1 > 8 | a
//1 > +-- -
//1 >
//1 > B::$vbtable@:
//1 > 0 | 0
//1 > 1 | 8 (Bd(B + 0)A)
//1 > vbi:class offset o.vbptr o.vbte fVtorDisp
//1 > A 8 0 4 0
//1 >
//1 > class C size(12) :
//1 > +-- -
//1 > 0 | {vbptr}
//1 > 4 | c
//1 > +-- -
//1 > +-- - (virtual base A)
//1 > 8 | a
//1 > +-- -
//1 >
//1 > C::$vbtable@:
//1 > 0 | 0
//1 > 1 | 8 (Cd(C + 0)A)
//1 > vbi:class offset o.vbptr o.vbte fVtorDisp
//1 > A 8 0 4 0
//1 >
//1 > class D size(24) :
//1 > +-- -
//1 > 0 | +-- - (base class B)
//1 > 0 | | {vbptr}
//1 > 4 | | b
//1 > | +-- -
//1 > 8 | +-- - (base class C)
//1 > 8 | | {vbptr}
//1 > 12 | | c
//1 > | +-- -
//1 > 16 | d
//1 > +-- -
//1 > +-- - (virtual base A)
//1 > 20 | a
//1 > +-- -
//1 >
//1 > D::$vbtable@B@:
//1 > 0 | 0
//1 > 1 | 20 (Dd(B + 0)A)
//1 >
//1 > D::$vbtable@C@:
//1 > 0 | 0
//1 > 1 | 12 (Dd(C + 0)A)
//1 > vbi:class offset o.vbptr o.vbte fVtorDisp
//1 > A 20 0 4 0
//1 >