天天看點

【C++深度解析】39、被遺棄的多重繼承(下)

文章目錄

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

1 多重繼承問題三:産生多個虛函數表

【C++深度解析】39、被遺棄的多重繼承(下)

繼承是父類與子類的疊加,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 呢?如下圖所示:

【C++深度解析】39、被遺棄的多重繼承(下)

直接強制類型轉換,将 BaseA* 類型的指針轉換為 BaseB* 類型的指針,指向的仍然是 BaseA 的成員函數。并不能調用 BaseB 的成員函數。

1.1 解決方案:dynamic_cast

想要完成指針類型轉換的同時改變指向的函數,需要使用 dynamic_cast,自動幫我們完成轉換

dynamic_cast 類型轉換

  • 用于有繼承關系的類指針間的轉換
  • 用于有交叉關系的類指針間的轉換
  • 具有類型檢查功能
  • 需要虛函數的支援

2 正确使用多重繼承

單繼承某個類+實作(多個)接口

如下圖所示:

【C++深度解析】39、被遺棄的多重繼承(下)

單繼承某個類+實作(多個)接口避免了多重繼承出現閉合的情亂,也就避免了資料備援的問題。同時使用 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、父類提供成員函數用于判斷指針是否指向目前對象

繼續閱讀