天天看點

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

文章目錄

    • 1 何為多重繼承
    • 2 多重繼承問題一:對象擁有不同位址
    • 3 多重繼承問題二:成員備援
      • 3.1 虛繼承解決資料備援
    • 4 小結

1 何為多重繼承

C++允許一個類擁有多個父類,這就是多重繼承

  • 子類擁有所有父類的成員變量
  • 子類繼承所有父類的成員函數
  • 子類對象可以當作任意父類對象使用

多重繼承的文法規則:

【C++深度解析】38、被遺棄的多重繼承(上)
// 38-1.cpp
#include<iostream>
using namespace std;
class BaseA
{
    int ma;
public:
    BaseA(int a) { ma = a; }
    int getA() { return ma; }
};

class BaseB
{
    int mb;
public:
    BaseB(int b) { mb = b; }
    int getB() { return mb; }
};

class Derived : public BaseA, public BaseB
{
    int mc;
public:
    Derived(int a, int b, int c) : BaseA(a), BaseB(b)
    {
        mc = c;
    }
    int getC() { return mc; }
    void print()
    {
        cout << "ma = " << getA() << ", "
            <<"mb = " << getB() << ", "
            << "mc = " << mc << endl;
    }
};
int main()
{
    cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
    Derived d(1, 2, 3);
    d.print();
    cout << "d.getA() = " << d.getA() << endl;
    cout << "d.getB() = " << d.getB() << endl;
    cout << "d.getC() = " << d.getC() << endl;
    cout << endl;

    BaseA* pa = &d;
    BaseB* pb = &d;

    cout << "pa->getA() = " << pa->getA() << endl;
    cout << "pb->getB() = " << pb->getB() << endl;
    cout << endl;

    cout << "pa = " << pa << endl;
    cout << "pb = " << pb << endl;
    return 0;
}
           
  • 類 Derived 繼承了類 BaseA 和類 BaseB,子類是由父類成員疊加子類得到的,是以類 Derived 擁有成員變量 ma,mb,mc。計算類對象大小時,計算的是成員變量的大小,成員函數儲存在代碼段,不計入對象大小。是以類 Derived 大小為 12。
  • Derived 對象可以調用繼承來的成員函數 getA() ,getB()
  • 子類對象可以轉換為任意父類對象使用,是以程式第 46-50 行可以轉換為兩個父類對象,調用父類函數
  • 程式第 53-54行列印指向對象 d 的兩個指針來說明多重繼承的第一個問題

編譯運作:

$ g++ 38-1.cpp -o 38-1
$ ./38-1
sizeof(Derived) = 12
ma = 1, mb = 2, mc = 3
d.getA() = 1
d.getB() = 2
d.getC() = 3

pa->getA() = 1
pb->getB() = 2

pa = 0x7ffee3d7e03c
pb = 0x7ffee3d7e040
           

運作結果和我們分析的一緻,但是 pa,pb 都是指向對象 d 的指針,他們的值卻不同。這就是第一個問題。

2 多重繼承問題一:對象擁有不同位址

  • 通過多重繼承得到的對象可能擁有“不同的位址”

如下圖所示,兩個指針指向一個對象,可能指向的位置不同,且目前沒有解決方案。

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

3 多重繼承問題二:成員備援

  • 多重繼承可能産生備援的成員

如下所示,Teacher 和 Student 都是 People 的子類,Doctor 是 Teacher 和 Student 的子類。

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

我們用代碼描述上述關系

// 38-2.cpp
#include<iostream>
using namespace std;
class People
{
    string m_name;
    int m_age;
public:
    People(string name, int age)
    {
        m_name = name;
        m_age = age;
    }
    void print()
    {
        cout << "Name = " << m_name << ", "
            << "Age = " << m_age << endl;
    }
};
class Teacher : public People
{
public:
    Teacher(string name, int age) : People(name, age)
    {
    }
};
class Student : public People
{
public:
    Student(string name, int age) : People(name, age)
    {
    }
};
class Doctor : public Teacher, public Student
{
    public:
    Doctor(string name, int age) : Teacher(name, age), Student(name, age)
    {
    }
};
int main()
{
    Doctor d("Tom", 29);
    //d.print();
    d.Teacher::print();
    d.Student::print();
    return 0;
}
           

由于 Teacher 和 Student 都是 People 的子類,是以 Teacher 和 Student 都有成員變量 m_name,int m_age,都有成員函數 print()。

Doctor 是 Teacher 和 Student 的子類,是以 Doctor 中有兩個 m_name 變量,兩個 m_age 變量,兩個 print() 函數,我們使用 d.print() 調用時,編譯器并不知道我們調用的是哪一個函數,需要用作用于限定符指明哪一個函數才行。

3.1 虛繼承解決資料備援

當多重繼承關系出現閉合時産生資料備援的問題

解決方案:虛繼承

  • 虛繼承能夠解決資料冗問題
  • 中間層父類不再關心頂層父類的初始化
  • 最終子類必須直接調用頂層父類的構造函數
#include <iostream>
#include <string>
using namespace std;
class People
{
    string m_name;
    int m_age;
public:
    People(string name, int age)
    {
        m_name = name;
        m_age = age;
    }
    void print()
    {
        cout << "Name = " << m_name << ", "
             << "Age = " << m_age << endl;
    }
};

class Teacher : virtual public People				// 虛繼承
{
public:
    Teacher(string name, int age) : People(name, age)
    {
    }
};

class Student : virtual public People				// 虛繼承
{
public:
    Student(string name, int age) : People(name, age)
    {
    }
};

class Doctor : public Teacher, public Student
{
public:
	// 最終子類必須直接調用頂層父類的構造函數
    Doctor(string name, int age) : Teacher(name, age), Student(name, age), People(name, age)
    {
    }
};
int main()
{
    Doctor d("Tom", 33);
    d.print();
    return 0;
}
           

使用虛繼承,最終子類直接調用頂層父類的構造函數,解決了資料備援的問題。

$ g++ 38-2.cpp -o 38-2
$ ./38-2
Name = Tom, Age = 29
           

在架構設計時我們并不知道後面的代碼會不會有多繼承,是以我們并不知道是否應該使用虛繼承,而且,需要直接調用頂層父類的構造函數,查找頂層父類效率低下。現在軟體很少使用多重繼承了。

4 小結

1、C++支援多重繼承的程式設計方式

2、多重繼承的問題

  • 可能出現同一個對象位址不同的情況
  • 虛繼承可以解決資料備援的問題
  • 虛繼承使得架構設計可能出現問題

繼續閱讀