1. 程式人生 > >C++基礎教程面向物件(學習筆記(51))

C++基礎教程面向物件(學習筆記(51))

多重繼承

到目前為止,我們提出的所有繼承示例都是單繼承 - 也就是說,每個繼承的類都有一個且只有一個父類。但是,C ++提供了執行多重繼承的功能。 多重繼承使派生類可以從多個父級繼承成員。

假設我們想編寫一個程式來跟蹤一群老師。老師是一個人。但是,教師也是一名僱員(如果為自己工作,他們就是自己的僱主)。可以使用多重繼承來建立從Person和Employee繼承屬性的Teacher類。要使用多重繼承,只需指定每個基類(就像在單繼承中一樣),用逗號分隔。

#include <string>
 
class Person
{
private:
    std::string m_name;
    int m_age;
 
public:
    Person(std::string name, int age)
        : m_name(name), m_age(age)
    {
    }
 
    std::string getName() { return m_name; }
    int getAge() { return m_age; }
};
 
class Employee
{
private:
    std::string m_employer;
    double m_wage;
 
public:
    Employee(std::string employer, double wage)
        : m_employer(employer), m_wage(wage)
    {
    }
 
    std::string getEmployer() { return m_employer; }
    double getWage() { return m_wage; }
};
 
// Teacher publicly inherits Person and Employee
class Teacher: public Person, public Employee
{
private:
     int m_teachesGrade;
 
public:
    Teacher(std::string name, int age, std::string employer, double wage, int teachesGrade)
        : Person(name, age), Employee(employer, wage), m_teachesGrade(teachesGrade)
    {
    }
};

多重繼承的問題

雖然多繼承似乎是單繼承的簡單擴充套件,但多繼承引入了許多問題,這些問題可能顯著增加程式的複雜性,並使它們成為維護的噩夢。我們來看看其中的一些情況。 在這裡插入圖片描述 首先,當多個基類包含具有相同名稱的函式時,可能會導致歧義。例如:

#include <iostream>
 
class USBDevice
{
private:
    long m_id;
 
public:
    USBDevice(long id)
        : m_id(id)
    {
    }
 
    long getID() { return m_id; }
};
 
class NetworkDevice
{
private:
    long m_id;
 
public:
    NetworkDevice(long id)
        : m_id(id)
    {
    }
 
    long getID() { return m_id; }
};
 
class WirelessAdapter: public USBDevice, public NetworkDevice
{
public:
    WirelessAdapter(long usbId, long networkId)
        : USBDevice(usbId), NetworkDevice(networkId)
    {
    }
};
 
int main()
{
    WirelessAdapter c54G(5442, 181742);
    std::cout << c54G.getID(); // Which getID() do we call?
 
    return 0;
}

當c54G.getID() 被編譯,編譯器會檢查是否WirelessAdapter包含了一個名為的getID()函式。它沒有。然後編譯器檢視是否有任何父類具有名為getID()的函式。看到這裡的問題?問題是c54G實際上包含兩個getID()函式:一個繼承自USBDevice,一個繼承自NetworkDevice。因此,此函式呼叫是不明確的,如果您嘗試編譯它,將收到編譯器報錯。

但是,有一種方法可以解決此問題:您可以明確指定要呼叫的版本:

int main()
{
    WirelessAdapter c54G(5442, 181742);
    std::cout << c54G.USBDevice::getID();
 
    return 0;
}

雖然這種解決方法非常簡單,但是當您的類繼承自其他類本身的四個或六個基類時,您可以看到事情會變得複雜。當您繼承更多類時,命名衝突的可能性會呈指數級增長,並且每個命名衝突都需要明確解決。

其次,更嚴重的是鑽石問題,作者喜歡稱之為“厄運之鑽”。當類乘法繼承自兩個類時,會發生這種情況,每個類都從一個基類繼承。這導致菱形的遺傳模式。

例如,考慮以下一組類:

class PoweredDevice
{
};
 
class Scanner: public PoweredDevice
{
};
 
class Printer: public PoweredDevice
{
};
 
class Copier: public Scanner, public Printer
{
};

在這裡插入圖片描述 Scanner和Printer都是PoweredDevice,因此它們來自PoweredDevice。但是,Copier具有Scanner和Printer的功能。 在此上下文中出現了許多問題,包括Copier是否應該有一個或兩個PoweredDevice副本,以及如何解決某些型別的模糊引用。雖然大多數這些問題都可以通過顯式範圍來解決,但是為了處理增加的複雜性而新增到類中的維護開銷可能會導致開發時間急劇增加。我們將在下一課中詳細討論解決鑽石問題的方法。

多重繼承比它帶來的價值更麻煩嗎?

事實證明,使用多重繼承可以解決的大多數問題也可以使用單繼承來解決。許多面向物件的語言(例如Smalltalk,PHP)甚至不支援多重繼承。許多相對現代的語言(如Java和C#)將類限制為普通類的單繼承,但允許介面類的多重繼承(我們將在後面討論)。在這些語言中不允許多重繼承的驅動思想是,它只會使語言過於複雜,並最終導致比修復更多的問題。

許多作者和有經驗的程式設計師認為,由於它帶來的許多潛在問題,應該不惜一切代價避免C ++中的多重繼承。作者不同意這種方法,因為有時候和多種情況下,多重繼承是最好的方法。但是,應該非常明智地使用多重繼承。

有趣的是,你已經使用了多重繼承編寫的類而不知道它:iostream庫物件std :: cin和std :: cout都是使用多重繼承實現的!

規則:避免多重繼承,除非替代方案導致更復雜。