天天看點

虛基類相關總結

嘗試性熟悉熟悉寫法,總結了虛基類的知識,内容有不足和錯誤還望大神指正

虛基類:

虛基類相關總結

虛基類繼承

1.虛基類的作用:

(1):當在多條繼承路徑上有一個公共的基類,在這些路徑的某幾條彙合處,這個公共的基類就會産生多個執行個體(或多個副本),若隻想儲存這個基類的一個執行個體,可以将這個公共基類說明為虛基類,示例如下:

class CBase { };
  class ChildA1:virtual public CBase{ };
  class ChildA2:virtual public CBase{ };
  class ChildB:public ChildA1,ChildA2{ };
           

則在類ChildB的對象中,僅有類CBase的一份對象資料

(2):虛基類的初始化是由派生類調用虛基類構造函數完成。例如

class A//定義基類A
{
   A(int i){ } //基類構造函數,有一個參數
};
class B :virtual public A   //A作為B的虛基類
{
   B(int n):A(n){ } //B類構造函數,在初始化表中對虛基類初始化
};
class C :virtual public A //A作為C的虛基類
{
   C(int n):A(n){ } 
//C類構造函數,在初始化表中對虛基類初始化
};
class D :public B,public C 
//類D的構造函數,在初始化表中對直接基類和虛基類初始化
{
   D(int n):A(n),B(n),C(n){ }
};
           

注意:

在定義類D的構造函數時,與以往使用的方法有所不同。規定:

在最後的派生類中不僅要負責對其直接基類進行初始化,還要負責對虛基類初始化。C++編譯系統隻執行最末端的派生類對虛基類的構造函數的調用,而忽略虛基類的其他派生類(如類B和類C)

對虛基類的構造函數的調用僅由最末端派生類完成,這就保證了虛基類的資料成員不會被多次初始化。很多時候,對于繼承鍊上的中間類,我們也會在其構造函數中顯式調用虛基類的構造函數,因為一旦需要建立這些中間類的對象,也要保證它們得到正确的初始化。

虛基類的特點:

(1)虛基類構造函數必須由最新派生出來的類負責調用(即使存在中間基類).

(2)虛基類的構造函數先于非虛基類的構造函數執行。

派生類構造函數的執行順序

派生類構造函數的執行順序:

1.調用基類的構造函數,如有多個基類,則按照它們被繼承的順序依次調用。

2.調用内嵌對象的構造函數,如果有多個,則按照它們在類的資料成員聲明中的先後順序依次調用。

3.執行派生類的構造函數體中的内容。

如果派生類的構造函數沒有顯示聲明其基類和其内嵌對象的構造方式,那麼系統按照“預設”方式對它們進行初始化,也就是調用它們的預設構造函數。析構函數的執行順序與構造函數的順序正好相反。

引入虛基類和直接繼承會有什麼差別

由于有了間接性和共享性兩個特征,是以決定了虛繼承體系下的對象在通路時必然會在時間和空間上與一般情況有較大不同。

1.時間:在通過繼承類對象通路虛基類對象中的成員(包括資料成員和函數成員)時,都必須通過某種間接引用來完成,這樣會增加引用尋址時間(就和虛函數一樣),其實就是調整this指針以指向虛基類對象,隻不過這個調整是運作時間接完成的。

2.空間:由于共享是以不必要在對象記憶體中儲存多份虛基類子對象的拷貝,這樣較之多繼承節省空間。虛拟繼承與普通繼承不同的是,虛拟繼承可以防止出現diamond繼承時,一個派生類中同時出現了兩個基類的子對象。也就是說,為了保證這一點,在虛拟繼承情況下,基類子對象的布局是不同于普通繼承的。是以,它需要多出一個指向基類子對象的指針。

多級virtual問題

請仔細閱讀下面代碼,将以此展開對多級virtual問題的讨論:

class A {
protected:
	int a;
public:
	A(int a) :a(a) {}
	A() :a(0) {}
};
class B:virtual public A {
protected:
	int b;
public:
	B(int a,int b):A(a),b(b){}
	B() :A(), b(0) {}
};
class C :virtual public B {
protected:
	int c;
public:
	C(int a,int b,int c):A(a),B(a,b),c(c){}
	C() :A(), B(), c(0) {}
};
class D :virtual public C {
protected:
	int d;
public:
	D(int a, int b, int c, int d):A(a), B(a, b), C(a, b, c), d(d) {}
	D():A(),B(),C(),d(0){}
};
           

若将class D中構造函數初始化清單中A,B,C任意一個構造函數删去,均不能完成對其相應屬性的初始化。

例如将:

D(int a, int b, int c, int d):A(a), B(a, b), C(a, b, c), d(d) {}

改為:

D(int a, int b, int c, int d):A(a), C(a, b, c), d(d) {}

便不能完成對class B中屬性b的初始化,但是如果把class C:virtual public B

中的“virtual”關鍵字去掉,便能完成對class B中屬性b的初始化。

原因便在于如果class C把class B聲明為虛基類時,class D初始化時,如果不調用class B的構造函數,則系統會調用class B的預設構造函數,因為C++編譯系統隻執行最後的派生類對虛基類的構造函數的調用,而忽略虛基類的其他派生類(如類B和類C) 。而将“virtual”關鍵字去掉後,class C把class B聲明為基類,class D初始化時,可以通過調用class C的構造函數再調用class B的構造函數,對class B完成初始化。

如果多重繼承中類的繼承均采用虛基類,那麼第二級以及之後的派生類,如果不顯式調用虛基類的構造函數,則系統調用預設構造函數,可能導緻虛基類無法按預期方式初始化。是以,可将多重繼承中第二級以及之後的部分派生類的繼承中采用直接繼承,既能簡化程式,又能保證類的初始化按預期進行。

使用注意

(1) 一個類可以在一個類族中既被用作虛基類,也被用作非虛基類。

(2) 在派生類的對象中,同名的虛基類隻産生一個虛基類子對象,而某個非虛基類産生各自的子對象。

(3) 虛基類子對象是由最遠派生類的構造函數通過調用虛基類的構造函數進行初始化的。

(4) 最遠派生類是指在繼承結構中建立對象時所指定的類。

(5) 派生類的構造函數的成員初始化清單中必須列出對虛基類構造函數的調用;如果未列出,則表示使用該虛基類的預設構造函數。

(6) 從虛基類直接或間接派生的派生類中的構造函數的成員初始化清單中都要列出對虛基類構造函數的調用。但僅僅用建立對象的最遠派生類的構造函數調用虛基類的構造函數,而該派生類的所有基類中列出的對虛基類的構造函數的調用在執行中被忽略,進而保證對虛基類子對象隻初始化一次。

(7) 在一個成員初始化清單中同時出現對虛基類和非虛基類構造函數的調用時,虛基類的構造函數先于非虛基類的構造函數執行。

繼續閱讀