天天看點

C++:53---菱形繼承、虛繼承

一、菱形繼承

  • 在介紹虛繼承之前介紹一下菱形繼承
  • 概念: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
  1. class ZooAnimal {}; //虛基類
  2. class Bear :public virtual ZooAnimal {}; //虛繼承
  3. class Raccoon :public virtual ZooAnimal {}; //虛繼承
  4. //Panda隻儲存一份ZooAnimal的定義
  5. 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 {};      

析構函數

  • 析構函數的執行順序與構造函數執行順序相反