一、菱形繼承
- 在介紹虛繼承之前介紹一下菱形繼承
- 概念:A作為基類,B和C都繼承與A。最後一個類D又繼承于B和C,這樣形式的繼承稱為菱形繼承
- 菱形繼承的缺點:
- 資料備援:在D中會儲存兩份A的内容
- 通路不明确(二義性):因為D不知道是以B為中介去通路A還是以C為中介去通路A,是以在通路某些成員的時候會發生二義性
- 缺點的解決:
- 資料備援:通過下面“虛繼承”技術來解決(見下)
- 通路不明确(二義性):通過作用域通路符::來明确調用。虛繼承也可以解決這個問題
示範案例
class A
{
public:
A(int a) :m_a(a) {}
int getMa() { return m_a; }
private:
int m_a;
};
class B :public A
{
public:
B(int a, int b) :A(a), m_b(b) {}
private:
int m_b;
};
class C :public A
{
public:
C(int a, int c) :A(a), m_c(c) {}
private:
int m_c;
};
class D :public B, public C
{
public:
D(int a, int b, int c, int d) :B(a, b), C(a, c), m_d(d) {}
void func()
{
/*錯誤,通路不明确
std::cout << getMa();*/
//正确,通過B通路getMa()
std::cout << B::getMa();
}
private:
int m_d;
};
二、虛繼承
- 虛繼承的作用:為了保證公共繼承對象在建立時隻儲存一分執行個體
- 虛繼承解決了菱形繼承的兩個問題:
- 資料備援:頂級基類在整個體系中隻儲存了一份執行個體
- 通路不明确(二義性):可以不通過作用域通路符::來調用(原理就是因為頂級基類在整個體系中隻儲存了一份執行個體)
- 共享的基類對象成為“虛基類”
- 說明:虛繼承不會影響派生類本身,隻是對虛基類進行的說明
- 通過在繼承清單中使用virtual關鍵字來說明,virtual與繼承說明符(public、protected、private)的位置可以互換
示範案例
- 下面的ZooAnimal是一個虛基類,Bear和Raccoon分别虛繼承于ZooAnimal
- class ZooAnimal {}; //虛基類
- class Bear :public virtual ZooAnimal {}; //虛繼承
- class Raccoon :public virtual ZooAnimal {}; //虛繼承
- //Panda隻儲存一份ZooAnimal的定義
- class Panda :public Bear, public Raccoon, public Endangered {};
三、虛繼承中的類型轉換
- 虛繼承中也可以将派生類抓換為基類,用基類的指針/引用指向于派生類
菱形繼承中的類型轉換
- 菱形繼承中會發生錯誤,不能将派生類轉換為基類
- 原理是差不多的,就是因為派生類中擁有多份基類的實體,是以不能轉換,會産生二義性
class A {};
class B: publi A {};
class C: publi A {};
class D: public B, public C {};
int main()
{
D d;
A* pa = &d; //錯誤
return 0;
}
虛繼承中的類型轉換
class ZooAnimal {};
class Bear :public virtual ZooAnimal {};
class Raccoon :public virtual ZooAnimal {};
class Panda :public Bear, public Raccoon, public Endangered {};
void dance(const Bear&);
void rummage(const Raccoon&);
ostream& operator<<(ostream&, const ZooAnimal&);
int main()
{
Panda ying_yang;
dance(ying_yang); //正确,把一個Panda對象當成Bear傳遞
rummage(ying_yang);//正确,把一個Panda對象當成Raccoon 傳遞
cout << ying_yang; //正确,把一個Panda對象當成ZooAnimal傳遞
return 0;
}
四、虛基類成員的可見性與隐藏
- 虛基類的成員沒有被任何派生類隐藏,那麼該成員可以直接通路,并且不會産生二義性
- 如果虛基類的成員隻被一條派生路徑隐藏,則我們仍然可以直接通路這個被隐藏的版本
- 如果虛基類的成員多多個派生路徑隐藏,則會産生二義性
- 例如,D1和D2虛繼承與B,D繼承于D1和D2,并且B有一個x成員:
- 如果D1和D2都沒有x的定義:此時對x的通路不會産生二義性,因為隻含有x的一個執行個體
- 如果D1中有x的定義而D2沒有:同樣沒有二義性,派生類的x比虛基類B的x優先級更高(或者D1中沒有x的定義而D2有x的定義)
- 如果D1和D2都有x的定義:對x的通路會産生二義性
- 解決二義性最好的辦法就是在派生類為成員自定義新的執行個體
五、虛繼承的構造函數
- 普通繼承:派生類可以不為間接基類(基類的基類)進行構造函數的調用
- 虛繼承:不論派生類屬于哪一層,派生類都需要對虛基類進行構造
- 原因:假設以下間接派生類沒有為虛基類進行構造,那麼當間接派生類進行構造時,會對虛基類進行重複的構造函數的調用(例如下面的示範案例D如果不顯式構造A,那麼當構造B和C的時候,B和C都會構造一次A,進而造成錯誤)。是以我們需要在間接派生類中為虛基類進行構造,進而避免了重複構造的二義性
示範案例
//普通繼承
class A {
public:
A(int a);
};
class B :public A {
public:
B(int a):A(10) {}
};
class C :public B {
public:
C() :B(10) {} //可以不為A進行構造,因為A的構造已經交給B了
};
//虛繼承
class A {
public:
A(int a);
};
class B :virtual public A {
public:
B(int a):A(10) {}
};
class C :virtual public A {
public:
C(int a) :A(10) {}
};
class D :public B,public C {
public:
//D() :B(10), C(20) {} 錯誤的,必須顯式為A進行構造
D() :A(5), B(10), C(20) {} //正确
};
構造函數的執行順序
- 規則:虛基類總是先于非虛基類構造,與它們在繼承體系中的次序和位置無關
- 例如,在上面的示範案例中,構造順序為:A-->B-->C-->D
- 下面再示範一個有多個虛基類的例子,其構造函數執行熟悉怒為:
- ZooAnimal
- ToyAnimal
- Character
- BookCharacter
- Bear
- TeddyBear
class Character {};
class BookCharacter :public Character {};
class ZooAnimal {};
class Bear :public virtual ZooAnimal {};
class ToyAnimal {};
class ReddyBear :public BookCharacter, public Bear,
public virtual ToyAnimal {};
析構函數