菱形繼承引發的問題和解決方案,以及底層實現的原理.

        定義:兩個子類繼承同一個父類,而又有子類同時繼承這兩個子類。

        如果直接繼承會引發訪問不明確(二義性),以及數據冗餘。如果直接指定訪問對象,可解決二義性(第一段代碼以及解析圖),而要解決數據冗餘,則要引入虛函數(第二段代碼以及解析圖)。

 

代碼一:

#include <iostream>
 
using namespace std;
 
class A
{
public:
       int _a;
};
 
class B :  public A
{
public:
       int _b;
};
 
class C :  public A
{
public:
       int _c;
};
 
class D : public C, public B
{
public:
       int _d;
};
 
int main()
{
       D dd;
       dd.C::_a =2;
       dd._c = 4;
 
       dd.B::_a =1;
       dd._b = 3;
 
       dd._d = 5;
return 0;
}


由上圖可以看出,繼承順序爲C  B , D的繼承方式決定創建空間的先後。指定訪問對象可以解決二義性,卻無法解決數據冗餘。

 

代碼二:


 

#include <iostream>
 
using namespace std;
 
class A
{
public:
       int _a;
};
 
class B :  virtual public A
{
public:
       int _b;
       int _b1;
};
 
class C : virtual public A
{
public:
       int _c;
};
 
class D : public B, public C
{
public:
       int _d;
};
 
int main()
{
       D dd;
 
       dd.B::_a =1;
       dd._b = 3;
       dd._b1 = 6;
 
       dd.C::_a =2;
       dd._c = 4;
 
       dd._d = 5;
       return 0;
}


 對比上圖,由於vptr的存在,空間加大,解決了數據冗餘的問題。下面爲實現過程

現在我們查看dd對象創建的過程。

1.進入反彙編

      D dd;

010B4BC8 push        1 

010B4BCA lea         ecx,[dd] 

010B4BCD call        D::D (010B1442h)      //dd創建

 

      dd.B::_a = 1;

010B4BD2 mov         eax,dword ptr [dd] 

010B4BD5 mov         ecx,dword ptr [eax+4] 

010B4BD8 mov         dword ptr dd[ecx],1        //寫入1

      dd._b = 3;

010B4BE0 mov         dword ptr [ebp-20h],3 

      dd._b1 = 6;

010B4BE7 mov         dword ptr [ebp-1Ch],6 

 

      dd.C::_a = 2;

010B4BEE mov         eax,dword ptr [dd] 

010B4BF1 mov         ecx,dword ptr [eax+4] 

010B4BF4 mov         dword ptr dd[ecx],2 

      dd._c = 4;

010B4BFC mov         dword ptr [ebp-14h],4 

 

      dd._d = 5;

010B4C03 mov         dword ptr [ebp-10h],5 

      return 0;

 

2.進入dd創建語句

D::D:(部分語句)

 

//之前語句對dd進行了初始化。

010B2D47 je          D::D+3Ch (010B2D5Ch) 

010B2D49 mov         eax,dword ptr [this]                //dd首地址送往eax.

010B2D4C mov         dword ptr [eax],10BCCCCh  //B距離A的偏移地址

                                                                                                 //送到eax.

010B2D52 mov         eax,dword ptr [this] 

010B2D55 mov         dword ptr [eax+0Ch],10BCCE8h //C距離A的地址

                                                                                                        //送到eax.

010B2D5C push        0 

010B2D5E mov         ecx,dword ptr [this] 

010B2D61 call        B::B (010B143Dh)                            //B的構造

010B2D66 push        0 

010B2D68 mov         ecx,dword ptr [this] 

010B2D6B add         ecx,0Ch                                          //ecx+B的大小

010B2D6E call        C::C (010B1447h)                             //C的構造

010B2D73 mov         eax,dword ptr [this] 

 

進入B的構造(部分)

010B3049 mov         eax,dword ptr [this] 

010B304C mov         dword ptr [eax],10BCC70h           //存放B的大小

010B3052 mov        eax,dword ptr [this] 

 

進入C的構造部分

010B3119 mov         eax,dword ptr [this] 

010B311C mov         dword ptr [eax],10BCCB8h      //存放C的大小

010B3122 mov        eax,dword ptr [this]

 

      dd對象在創建的過程中,調用D的構造,然後對BC進構造,在B構造時,在其起始位置(dd位置)放置一指向B的父類(A)的指針的偏移地址,偏移地址+4便是距離A的偏移量,同時在創建一偏移地址,保存B的對象的大小。然後dd加上B的對象的空間大小,在之後創建B的對象。BA的對象的首地址指向同一空間(父類A)。

 

ps:對於偏移地址當前內容是爲其他偏移量預留,在菱形繼承和多態進行共同探究時發現那個位置存放的是距離this的偏移量。


發佈了32 篇原創文章 · 獲贊 7 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章