C++多重繼承的指標問題
下面說說C++多重繼承中關於指標的一些問題。
指標指向問題
先看下面的程式:
class Base1
{
public:
virtual void fun1() {cout << "Base1::fun1" << endl;};
};
class Base2
{
public:
virtual void fun2() {cout << "Base2::fun1" << endl;};
};
class Derive : public Base1, public Base2
{
public:
virtual void fun1 () {cout << "Derive::fun1" << endl;}
virtual void fun2 () {cout << "Derive::fun2" << endl;}
};
int main()
{
Derive oD;
Base1 *pB1 = (Base1*)(&oD);
Base2 *pB2 = (Base2*)(&oD);
cout << "&oD=" << &oD << '\n';
cout << "pB1=" << pB1 << '\n';
cout << "pB2=" << pB2 << '\n';
if (&oD == pB1) cout << "&oD == pB1" << '\n';
if (&oD == pB2) cout << "&oD == pB2" << '\n';
}
我電腦上的執行結果:
首先,可以看到&oD和pB1指標指向相同的儲存地址。為什麼?
這是因為當我們new一個Derive類的時候,計算機給Derive類分配的空間可以分為三部分:首先是類Base1的部分,然後是Base2的部分,然後是Derive中除去Base和Base2剩餘部分,如下圖。
Base1 |
Base2 |
Derive |
所以&oD肯定儲存的是整體的首地址,而pB1指向的是Base1的首地址,恰好也是整體的首地址,所以有&oD和pB1的值剛好相等。pB2則指向的是Base2的首地址。
可是後面為什麼會有&oD == pB2呢?這是因為當編譯器發現一個指向派生類的指標和指向其某個基類的指標進行==運算時,會自動將指標做隱式型別提升已遮蔽多重繼承帶來的指標差異。因為兩個指標做比較,目的通常是判斷兩個指標是否指向了同一個記憶體物件例項,在上面的場景中,&oD和pB2雖然指標值不等,但是他們確確實實都指向了同一個記憶體物件(即new Derive產生的記憶體物件)。
指標型別轉換問題
還是使用上面的類,看主函式:
int main(){
Derive oD;
cout << "&oD=" << &oD << '\n';
Base1 *pB1 = &oD;
cout << "pB1=" << pB1 << '\n';
pB1->fun1();
cout << endl;
Base2 *pB2 = (Base2*)(pB1); // 指標強行轉換,沒有偏移
cout << "pB2=" << pB2 << '\n';
pB2->fun2();
cout << endl;
pB2 = dynamic_cast<Base2*>(pB1); // 指標動態轉換,dynamic_cast幫你偏移
cout << "pB2=" << pB2 << '\n';
pB2->fun2();
return 0;
}
猜猜執行結果:
是不是很意外,為什麼pB2->fun2()的結果是Derive::fun1。這裡我們看到的是使用強制型別轉換是不能把Base1型別的指標轉成Base2型別的指標的,必須使用dynamic_cast的形式進位制轉換才奏效。
下面我們探索下為什麼輸出的是Derive::fun1。
我們修改Base1的定義:
class Base1
{
public:
virtual void fun3() {cout << "Base1::fun3" << endl;};
virtual void fun1() {cout << "Base1::fun1" << endl;};
};
給新增一個函式fun3,然後再次執行上面的main函式,結果如下:
我們可以發現強制轉換不會成功,也不會報錯,你呼叫Base2的fun2函式,因為強制轉換不成功,所以指標仍然指向Base1,而Base1中沒有fun2,所以就會自動呼叫宣告的函式中的第一個函式。(不知道為什麼會這樣設計!)
上面強制將Base1轉為Base2不會報錯,但是不能執行處正確結果。而我們強制將Base2轉為Base1呢?
int main()
{
Derive *pD = new Derive();
Base2 *pB2 = pD;
pB2->fun2();
Base1 *pB1 = (Base1*)(pB2);
pB1->fun1();
return 0;
}
這樣程式執行到第6行的時候會直接奔潰。
所以:
1. C++多重繼承需要慎用
2. 型別轉換儘量採用c++內建的型別轉換函式,而不要強行轉換。