C++多重繼承下的指針類型轉換


在C++中,指針的類型轉換是經常發生的事情,比如將派生類指針轉換為基類指針,將基類指針轉換為派生類指針。指針的本質其實就是一個整數,用以記錄進程虛擬內存空間中的地址編號,而指針的類型決定了編譯器對其指向的內存空間的解釋方式。

基於上面的理解,我們似乎可以得出一個結論,C++中對指針進行類型轉換,不會改變指針的值,只會改變指針的類型(即改變編譯器對該指針指向內存的解釋方式),但是這個結論在C++多重繼承下是 不成立的。

看下面一段代碼:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class CBaseA
 5 {
 6 public:
 7     char m_A[32];
 8 };
 9 
10 class CBaseB
11 {
12 public:
13     char m_B[64];
14 };
15 
16 class CDerive : public CBaseA, public CBaseB
17 {
18 public:
19     char m_D[128];
20 };
21 
22 
23 int main()
24 {
25     auto pD = new CDerive;
26     auto pA = (CBaseA *)pD;
27     auto pB = (CBaseB *)pD;
28 
29     cout << pA << '\n' << pB << '\n' << pD << endl;
30     cout << (pD == pB) << endl;
31 }

這段代碼的輸出是:

0x9f1080
0x9f10a0
0x9f1080
1

可以看出,指向同一個堆上new出來的內存指針,在經過類型轉換之后,其值會發生改變。究其原因,要從C++中多重繼承的內存布局說起。

new CDerive;執行之后,生成的內存布局如下:

 

同時我們注意到,pB與pD的指針差值正好是CBaseA占用的內存大小32字節,而pA與pD都指向了同一段地址。這是因為,將一個派生類的指針轉換成某一個基類指針,編譯器會將指針的值偏移到該基類在對象內存中的起始位置。

可是為什么C++要這樣設計呢?

試想,沿用上面的場景,如果pB和pA都指向對象的首地址,那么使用pB指針來定位CBaseB的成員變量m_B時,編譯器應該將pB指針偏移32個字節,從而跳過CBaseA的內存部分。而pB指針如果是這樣產生的auto pB = new CBaseB;,那么使用pB指針來定位CBaseB的成員變量m_B時,偏移量應該為0。

關鍵在於對於一個指針而言,編譯器不關心也無法知道該指針的來源(一種極端情況,指針是從其他模塊傳遞過來的),而只是把它視為一個有指針類型的整數。所以對於CBaseB類型的指針,取CBaseB的成員變量m_B時,偏移量應該通通為0,這是通過CBaseB的類聲明就可以統一決策的事情。

說到這里,pD和pB的指針地址為什么不一樣大家應該清楚了,可是為什么下面的代碼輸出是1呢?

cout << (pD == pB) << endl;

輸出1表示pD和pB是相等的,而剛剛我們才說明了,pD和pB的地址是相差了32個字節的。

其實這也是編譯器為大家屏蔽了這種指針的差異,當編譯器發現一個指向派生類的指針和指向其某個基類的指針進行==運算時,會自動將指針做隱式類型提升已屏蔽多重繼承帶來的指針差異。因為兩個指針做比較,目的通常是判斷兩個指針是否指向了同一個內存對象實例,在上面的場景中,pD和pB雖然指針值不等,但是他們確確實實都指向了同一個內存對象(即new CDerive;產生的內存對象 ),所以編譯器又在此處插了一腳,讓我們可以安享==運算的上層語義。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM