文章目錄
-
- 1 多重繼承問題三:産生多個虛函數表
-
- 1.1 解決方案:dynamic_cast
- 2 正确使用多重繼承
- 3 小結
1 多重繼承問題三:産生多個虛函數表

繼承是父類與子類的疊加,BaseA 和 BaseB 中都有虛函數,那麼二者都會有虛函數表,Dervied 繼承了這兩個類,類中也就有了兩個指針,分别指向兩個虛函數表。
程式設計實驗:多重繼承問題–産生多個虛函數表
// 38-3.cpp
#include<iostream>
using namespace std;
class BaseA
{
public:
virtual void funcA()
{
cout << "BaseA::funcA()" << endl;
}
};
class BaseB
{
public:
virtual void funcB()
{
cout << "BaseB::funcB()" << endl;
}
};
class Derived : public BaseA, public BaseB
{
};
int main()
{
Derived d;
BaseA* pa = &d;
BaseB* pb = &d;
BaseB* pbb = (BaseB*)pa; // 強制類型轉換 BaseA*-->BaseB*
BaseB* pbc = dynamic_cast<BaseB*>(pa); // dynamic_cast類型轉換
cout << "sizeof(b) = " << sizeof(d) << endl;
pa->funcA();
pb->funcB();
pbb->funcB();
cout << endl;
cout << "pa = " << pa << endl;
cout << "pb = " << pb << endl;
cout << "pbb = " << pbb << endl;
cout << "pbc = " << pbc << endl;
return 0;
}
- BaseA,BaseB 中有虛函數,Derived 繼承了這兩個類,所有就有兩個虛函數表,也就有兩個指針分别指向這兩個虛函數表。是以 Derived 大小為 16。
- 子類的引用可以轉換為父類的指針,根據多态,pa 可以調用類 BaseA 中的函數 funcA(),pb 可以調用類 BaseB 中的函數 funcB()
- 第 28 行,将BaseA* 類型的指針轉換為 BaseB* 類型的指針,會調用什麼函數呢?
- 使用新式類型轉換 dynamic_cast 再嘗試,最後我們列印四個指針的值。
編譯運作:
$ g++ 38-3.cpp -o 38-3
$ ./38-3
sizeof(b) = 16
BaseA::funcA()
BaseB::funcB()
BaseA::funcA()
pa = 0x7ffc0d30ac40
pb = 0x7ffc0d30ac48
pbb = 0x7ffc0d30ac40
pbc = 0x7ffc0d30ac48
從結果可以看出:
- 類大小為 16,說明子類有兩個虛函數表,兩個指針。
- pbb 是 BaseB* 類型的指針,但是調用的确是類 BaseA 中的函數。從最後列印的指針數值上可以看到 pa,pbb 都是指向類 BaseA,pb 和 pbc 指向類 BaseB。
為什麼 BaseB* 類型的指針 pbb 會指向類 BaseA 呢?如下圖所示:
直接強制類型轉換,将 BaseA* 類型的指針轉換為 BaseB* 類型的指針,指向的仍然是 BaseA 的成員函數。并不能調用 BaseB 的成員函數。
1.1 解決方案:dynamic_cast
想要完成指針類型轉換的同時改變指向的函數,需要使用 dynamic_cast,自動幫我們完成轉換
dynamic_cast 類型轉換
- 用于有繼承關系的類指針間的轉換
- 用于有交叉關系的類指針間的轉換
- 具有類型檢查功能
- 需要虛函數的支援
2 正确使用多重繼承
單繼承某個類+實作(多個)接口
如下圖所示:
單繼承某個類+實作(多個)接口避免了多重繼承出現閉合的情亂,也就避免了資料備援的問題。同時使用 dynamic_cast 可以避免指針轉換帶來的問題,保證一個父類指針轉換為另一個父類指針時,調用的函數也相應的改變。
// 38-4.cpp
#include<iostream>
using namespace std;
class Base
{
protected:
int mi;
public:
Base(int i) { mi = i; }
int getI() { return mi; }
bool equal(Base* obj)
{
return this == obj;
}
};
class Interface1
{
public:
virtual void add(int i) = 0;
virtual void minus(int i) = 0;
};
class Interface2
{
public:
virtual void multiply(int i) = 0;
virtual void divide(int i) = 0;
};
class Derived : public Base, public Interface1, public Interface2
{
public:
Derived(int i) : Base(i) {}
void add(int i)
{
mi += i;
}
void minus(int i)
{
mi -= i;
}
void multiply(int i)
{
mi *= i;
}
void divide(int i)
{
if (i != 0)
{
mi /= i;
}
}
};
int main()
{
Derived d(100);
Derived* p = &d;
Interface1* pInt1 = &d;
Interface2* pInt2 = &d;
cout << "p->getI() = " << p->getI() << endl;
pInt1->add(10);
pInt2->divide(11);
pInt1->minus(5);
pInt2->multiply(8);
cout << "p->getI() = " << p->getI() << endl;
cout << endl;
cout << "pInt1 == p : " << p->equal(dynamic_cast<Base*>(pInt1)) << endl;
cout << "pInt1 == p : " << p->equal(dynamic_cast<Base*>(pInt2)) << endl;
return 0;
}
- Derived 有三個父類,其中一個是類Base,另外兩個是接口,避免了資料備援的問題。
- 在子類中要實作 equal() 判斷指針是否指向目前對象。
- 将一個父類指針轉換為另一個父類指針,使用 dynamic_cast
$ g++ 38-4.cpp -o 38-4
$ ./38-4
p->getI() = 100
p->getI() = 40
pInt1 == p : 1
pInt1 == p : 1
工程建議:
- 先繼承一個類,然後實作多個接口
- 父類中提供 equal() 成員函數,判斷指針是否指向目前對象
- 多重繼承相關強制類型轉換用 dynamic_cast 完成
3 小結
1、多繼承可能出現多個虛函數表指針
2、與多重繼承相關強制類型轉換用 dynamic_cast 完成
3、工程中采用“單繼承多接口”的方式使用多繼承
4、父類提供成員函數用于判斷指針是否指向目前對象