C++ 多重繼承與虛基類

單一繼承是指:一個派生類只繼承一個基類。

多重繼承指的是一個類可以同時繼承多個不同基類的行爲和特徵功能

class Base1
{
public:
	Base1() { cout << "Base1()" << endl; }
	~Base1() { cout << "~Base1()" << endl; }
};
class Base2
{
public:
	Base2() { cout << "Base2()" << endl; }
	~Base2() { cout << "~Base2()" << endl; }
};

/*
**  : 之後稱爲類派生表,表的順序決定基類構造函數
** 調用的順序,析構函數的調用順序正好相反
*/
class Derive : public Base2, public Base1
{
};
int main()
{
	Derive derive;
	return 0;
}

多重繼承有什麼好處?壞處?怎麼解決?

 

優點:多重繼承與單一繼承一樣,都可以是的代碼得到更好的複用,思路清晰,代碼簡單。

缺點:1.二義性問題

           2.菱形繼承導致派生類持有間接基類的多份拷貝

菱形繼承導致派生類持有間接基類的多份拷貝

class A
{
public:
	A(int data = 0) :ma(data) { cout << "A" << endl; }
	~A() { cout <<" ~A()" << endl; }

private:
	int ma;
};
class B : public A
{
public:
	B(int data = 0) :A(data),mb(data) { cout << "B" << endl; }
	~B() { cout << " ~B()" << endl; }
private:
	int mb;
};
class C: public A
{
public:
	C(int data = 0) :A(data),mc(data) { cout << "C" << endl; }
	~C() { cout << " ~C()" << endl; }

private:
	int mc;

};
class D :public B, public C
{
public:
	D(int data = 0) :B(data),C(data), md(data) { cout << "D" << endl; }
	~D() { cout << " ~D()" << endl; }

private:
	int md;
};
int main()
{
	D d;
	return 0;
}

這樣會使的D含有兩份相同的A的成員變量 。 

二義性問題 

此時我們不知道調用B與C中的那個show()函數。

如何解決這樣的問題? 需要用到我們的虛基類

虛基類

首先需要先分清楚虛繼承與虛函數

虛繼承是解決C++多重繼承問題的一種手段,從不同途徑繼承來的同一基類,會在派生類中存在多份拷貝。

這將存在兩個問題:1.浪費存儲空間
                                2.存在二義性問題。

通常可以將派生類對象的地址賦值給基類對象,實現的具體方式是,將基類指針指向繼承類(繼承類有基類的拷貝)中的基類對象的地址,但是多重繼承可能存在一個基類的多份拷貝,這就出現了二義性。

虛基類使得從多個類(它們的基類相同)派生出的對象只繼承一個基類對象。例如,通過在類聲明中使用關鍵字 virtual ,可以使這些派生類只保留虛基類的一個副本。
 

class A//由於B與C虛繼承A,此時A就是一個虛基類
{
public:
	A(int data = 0) :ma(data) { cout << "A" << endl; }
	~A() { cout <<" ~A()" << endl; }

private:
	int ma;
};
class B :virtual public A
{
public:
	B(int data = 0) :A(data),mb(data) { cout << "B" << endl; }
	~B() { cout << " ~B()" << endl; }
private:
	int mb;
};
class C:virtual public A
{
public:
	C(int data = 0) :A(data),mc(data) { cout << "C" << endl; }
	~C() { cout << " ~C()" << endl; }

private:
	int mc;

};
class D :public B, public C
{
public:
	D(int data = 0) :B(data),C(data), md(data) { cout << "D" << endl; }
	~D() { cout << " ~D()" << endl; }

private:
	int md;
};
int main()
{
	D d;
	return 0;
}

D中此時只有一份A的成員變量 

 

Derive1 和 Derive2 虛繼承了 Base後, Base 成爲了Derive1 和 Derive2 的虛基類,那 Derive3 就可以安全的多繼承 Derive1 和 Derive2了。如下圖:

虛繼承底層的內存佈局


虛基類的實現是產生虛基類表指針 vbptr 與虛基類表 vbtable。

虛繼承底層實現原理與編譯器相關,一般通過虛基類指針和虛基類表實現,每個虛繼承的子類都有一個虛基類指針(佔用一個指針的存儲空間,4字節)和虛基類表(不佔用類對象的存儲空間)。

需要強調的是,虛基類依舊會在派生類裏面存在拷貝,只是僅僅只存在一份而已,並不是不在派生類裏面了;當虛繼承的派生類被當做基類繼承時,虛基類指針也會被繼承。

實際上,vbptr指的是虛基類表指針(virtual base table pointer),該指針指向了一個虛基類表(virtual table),虛基類表中記錄了虛基類與本類的偏移地址;

通過偏移地址,這樣就找到了虛基類成員,而虛繼承也不用像普通多繼承那樣維持着公共基類(虛基類)的兩份同樣的拷貝,節省了存儲空間。

在這裏我們可以對比虛函數的實現原理:他們有相似之處,都利用了虛指針(均佔用對象的存儲空間)和虛表(均不佔用對象的存儲空間)。

虛基類依舊存在繼承類中,只佔用存儲空間;虛函數不佔用存儲空間。
虛基類表存儲的是虛基類相對直接繼承類的偏移;而虛函數表存儲的是虛函數地址。
接下來我們從內存佈局看一下具體過程:

 

class A
{
public:
	A(int data = 0) :ma(data) { cout << "A()" << endl; }
	~A() { cout << " ~A()" << endl; }

private:
	int ma;
};
class B :virtual public A
{
public:
	B(int data = 0) :A(data), mb(data) { cout << "B()" << endl; }
	~B() { cout << " ~B()" << endl; }
private:
	int mb;
};
class C :virtual public A
{
public:
	C(int data = 0) :A(data), mc(data) { cout << "C()" << endl; }
	~C() { cout << " ~C()" << endl; }

private:
	int mc;

};
class D :public B, public C
{
public:
	D(int data = 0) :B(data), C(data), md(data) { cout << "D()" << endl; }
	~D() { cout << " ~D()" << endl; }

private:
	int md;
};

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章