多重繼承下的類型轉換

主要解釋強制類型轉換的影響。由於static_cast會在編譯期間檢測,dynamice_cast會在運行時檢測。
強制類型檢測在編譯器沒有足夠的信息判斷類型是否可以轉換時只能像reinterpret_cast同樣將地址賦值。
#include <iostream>
#include <hash_map>
using namespace std;
class I1
{
public:
	virtual void vf1()
	{
		cout << "I'm I1:vf1()" << endl;
	}
};
class I2
{
public:
	virtual void vf2()
	{
		cout << "I'm I2:vf2()" << endl;
	}
};

class C : public I1, public I2
{
private:
	hash_map<string, string> m_cache;
};

I1* CreateC()
{
	return new C();
}
int main(int argc, char** argv)
{
	I1* pI1 = CreateC();
	pI1->vf1();
	I2* pI2 = (I2*)pI1;
	pI2->vf2();
	delete pI1;
	return 0;
}
上述代碼執行結果以下:

先來看看前面代碼的內存佈局。ios

20140425110212046

之因此會出現pI1和pI2指向了同一個地址,是由於C++編譯器沒有足夠的知識來把IA*類型轉換爲IB*類型,只能按照傳統的C指針強制轉換處理,也就是指針位置不變。爲了驗證上面的結論,簡單的把pIA和pIB打印出來便可。把main()函數修改成以下:算法

int main(int argc, char** argv) 函數

佈局

%2Fa>脣vspa

    I1* pI1 = CreateC(); .net

    pI1->vf1(); 3d

 

    I2* pI2 = (I2*)pI1; 指針

    pI2->vf2(); 對象

 

    cout << "pI1指向的地址爲:"<<std::hex << pI1 << endl; blog

    cout << "pI2指向的地址爲:"<<std::hex << pI2 << endl;   

    delete pI1; 

    return 0; 

執行結果爲:

20140425110720890

可見pI1和pI2確實指向了同一個地址,而這個地址就是I1類的虛表。因爲虛函數是按照順序定位的,編譯器編譯pI2->vf2()的時候,無論實際的pI2指向哪裏,都把它當作指向了I2的虛表,根據I2類定義,推出I2::vf2()這個函數位於其虛表的第0個位置,因此就直接把pI2指向的地址做爲vf2來調用。而實際上,這個位置偏偏是I1虛表的第0個位置,也就是I1::vf1的位置,因此實際執行時調用的是I1::vf1()。其實這種狀況是有些特殊的,也就是這個位置正好也是一個函數地址,並且函數原型也同樣,要是有任何不一樣的地方,就會形成調用失敗,反而更容易及時的提醒開發者。

 

強制類型轉換

 

static_cast:進行編譯期類型轉換,此時若是C++編譯期不能推算出指針調整算法,就會報錯,提醒開發者。

dynamic_cast:使用dynamic_cast進行運行期動態類型轉換,這須要開啓編譯器的RTTI。

reinterpret_cast:地址的轉換,不須要檢測類型。

 

 

 

在C++中,指針的類型轉換是常常發生的事情,好比將派生類指針轉換爲基類指針,將基類指針轉換爲派生類指針。指針的本質其實就是一個整數,用以記錄進程虛擬內存空間中的地址編號,而指針的類型決定了編譯器對其指向的內存空間的解釋方式。C++中對指針進行類型轉換,不會改變指針的值,只會改變指針的類型(即改變編譯器對該指針指向內存的解釋方式),可是這個結論在C++多重繼承下是 不成立的

#include <iostream>
using namespace std;
class CBaseA
{
public:
char m_A[32];
};
class CBaseB
{
public:
char m_B[64];
};
class CDerive : public CBaseA, public CBaseB
{
public:
char m_D[128];
};
int main()
{
auto pD = new CDerive;
auto pA = (CBaseA *)pD;
auto pB = (CBaseB *)pD;
cout << pA << '\n' << pB << '\n' << pD << endl;
cout << (pD == pB) << endl;
}

這段代碼的輸出是:

0x9f1080
0x9f10a0
0x9f1080
1

20140716154534734

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

 

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

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

 

參考地址:http://blog.csdn.net/smstong/article/details/24455371

相關文章
相關標籤/搜索