程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++多繼承的細節

C++多繼承的細節

編輯:C++入門知識

這幾天寫的程序應用到多繼承。

以前對多繼承的概念非常清晰,可是很久沒用就有點模糊了。重新研究一下,“刷新”下記憶。

假設我們有下面的代碼:

#include <stdio.h>

class A
{
private:
   char data;
public:
   A(){data = A;}
   virtual void Show(){printf("A ");};
   virtual void DispA(){printf("a ");};
};

class B
{
private:
   int data;
public:
   B(){data = B;}
   virtual void Show(){printf("B ");};
   virtual void DispB(){printf("b ");};
};

class C
{
private:
   char data;
public:
   C(){data = C;}
   virtual void Show(){printf("C ");};
   virtual void DispC(){printf("c ");};
};

class D : public A, public B, public C
{
public:
   char data;
public:
   D(){data = D;}
   virtual void Show(){printf("D ");};
   virtual void DispD(){printf("d ");};
};

class E : public D
{
private:
   char data;
public:
   E(){data = E;}
   virtual void Show(){printf("E ");};
   virtual void DispE(){printf("e ");};
};

int main()
{
   D *d = new D;
   A *a = (A*)d;
   B *b = (B*)d;
   C *c = (C*)d;;

   d->Show();
   a->Show();
   b->Show();

   a->DispA();
   b->DispB();
   d->DispD();

   D *d1 = (D*)a;
   d1->Show();
   d1->DispD();
   D *d2 = (D*)b;
   d2->Show();
   d2->DispD();

   char x = d->data;
   return 0;
}

每個類都有兩個虛擬函數Show()和DispX()。類A,B,C是基本類,而D是多繼承,最後E又繼承了D。那麼對於類E,它的內存映像是怎樣的呢?為了解答這個問題,我們回顧一下基本類的內存映像:

+ --------------+ <- this
+    VTAB       +
+ --------------+
+               +
+    Data       +
+               +
+ --------------+

如果一個類有虛擬函數,那麼它就有虛函數表(VTAB)。類的第一個單元是一個指針,指向這個虛函數表。如果類沒有虛函數,並且它的祖先(所有父類)均沒有虛函數,那麼它的內存映像和C的結構一樣。所謂虛函數表就是一個數組,每個單元指向一個虛函數地址。
如果類Y是類X的一個繼承,那麼類Y的內存映像如下:
+ --------------+ <- this
+   Y 的 VTAB   +
+ --------------+
+               +
+   X 的 Data   +
+               +
+ --------------+
+               +
+   Y 的 Data   +
+               +
+ --------------+
Y的虛函數表基本和X的相似。如果Y有新的虛函數,那麼就在VTAB的末尾加上一個。如果Y重新定義了原有的虛函數,那麼原的指針指向新的函數入口。這樣無論是內存印象和虛函數表,Y都和X兼容。這樣當執行 X* x = (Y*)y;之後,x可以很好的被運用,並且可以享受新的虛擬函數。

現在看多重繼承:
class D : public A, public B, public C
{
   ....
}
它的內存映像如下:  
+ --+ -----------------+ 00H <- this
+   +    D 的 VTAB     +
+ A + -----------------+ 04H
+   +    A 的 數據     +
+ --+ -----------------+ 08H
+   +    B 的 VTAB    +
+ B + -----------------+ 0CH
+   +    B 的 數據     +
+ --+ -----------------+ 10H
+   +    C 的 VTAB    +
+ C + -----------------+ 14H
+   +    C 的 數據     +
+ --+ -----------------+ 18H
+ D +    D 的 數據     +
+ --+ -----------------+
(因為對齊於雙字,A~D的數據雖然只是一個char,但需要對齊到DWORD,所以占4字節)

對於A,它和單繼承沒有什麼兩樣。B和C被簡單地放在A的後面。如果它們虛函數在D中被重新定義過(比如Show函數),那麼它們需要使用新的VTAB,使被重定義的虛函數指到正確的位置上(這對於COM或類似的技術是至關重要的)。最後,D的數據被放置到最後面。
對於E的內存映像問題就可以不說自明了。

下面我們看一下C++是如何處理
   D *d;
   ......
   B *b = (B*)d;
這樣的要求的。設置斷點,進入反匯編,你可以看到如下的匯編代碼:(因為UBB關系,將方括號替換成了大括號。看上去有點別扭)
B *b = (B*)d;
00401062  cmp         dword ptr {d},0
00401066  je          main+73h (401073h)
00401068  mov         eax,dword ptr {d}
0040106B  add         eax,8
0040106E  mov         dword ptr {ebp-38h},eax
00401071  jmp         main+7Ah (40107Ah)
00401073  mov         dword ptr {ebp-38h},0
0040107A  mov         ecx,dword ptr {ebp-38h}
0040107D  mov         dword ptr {b},ecx
從上述匯編代碼可以看出:如果源(這裡是d)是NULL,那麼目標(這裡是b)也將被置為NULL,否則目標將指向源的地址並向下偏移8個字節,正好就是上圖所示B的VTAB位置。至於為什麼要用ebp-38h作緩存,這是編譯器的算法問題了。等以後有時間再研究。

接下來看一個比較古怪的問題,這個也是我寫這篇文章的初衷:
根據上面的多繼承定義,如果給出一個類B的實例b,我們是否可以求出D的實例?

為什麼要問這個問題。因為存在下面的可能性:
class B
{
   ...
   virtual int GetTypeID()=0;
   ...
};

class D : public A, public B, public C
{
   ...
   virtual int GetTypeID(){return 0;};
   ...
};

class Z : public X, public Y, public B
{
   ...
   virtual int GetTypeID(){return 1;};
   ...
};

void MyFunc(B* b)
{
   int t = b->GetTypeID();
   switch(t)
   {
   case 0:
       DoSomething((D*)b); //可能嗎?
       break;
   case 1:
       DoSomething((Z*)b); //可能嗎?
       break;
   default:
       break;
   }
}

猛一看很值得懷疑。但仔細想想,這是可能的,事實也證明了這一點。因為編譯器了解這D和B這兩個類相互之間的關系(也就是偏移量),因此它會做相應的轉換。同樣,設置斷點,查看匯編:
D *d2 = (D*)b;
00419992  cmp         dword ptr {b},0
00419996  je          main+196h (4199A6h)
00419998  mov         eax,dword ptr {b}
0041999B  sub         eax,8
0041999E  mov    &nb

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved