多重繼承的複雜


繼承本身就會令事情複雜化,多重繼承更是會令複雜度加劇,〈虛擬繼承〉中看到的不過是部份情況。

同名的方法或值域若在子類中可見,就必須處理名稱重疊時的相關議題(在子類中不可見的值域或方法,程式碼撰寫上本來就不能存取,也就不會有名稱重疊的判斷問題)。例如,如果繼承的父類有實作方法,而另一父類有同名的純 virtual 函式,從父類繼承的實作方法並不被視為實作了純 virtual 函式。例如:

#include <iostream>
using namespace std;

class P1 {
public:
    void foo() {
        cout << "foo" << endl;
    }
};

class P2 {
public:
    virtual void foo() = 0;
};

class C : public P1, public P2 {

};

int main() { 
    C c; // error: cannot declare variable 'c' to be of abstract type 'C'

    return 0;
}

C 仍被視為抽象類別,C 必須重新定義 foo,才可用來建立實例。

如果繼承的父類具有同名的實作方法,會造成實例呼叫的方法版本模棱兩可:

#include <iostream>
using namespace std;

class P1 {
public:
    virtual void foo() {
        cout << "P1:foo" << endl;
    }
};

class P2 {
public:
    virtual void foo() {
        cout << "P2:foo" << endl;
    }
};

class C : public P1, public P2 {

};

int main() { 
    C c;

    c.foo();   // error: request for member 'foo' is ambiguous

    return 0;
}

如果 C 只想保留其中一個版本,那就在 C 中重新定義 foo,並以 :: 指明會呼叫哪個父類的版本,如果不想重新定義 foo,那就必須明確指定給 P1P2 型態:

C c;

P1 &p1 = c;
p1.foo();    // 顯示 P1:foo

P2 &p2 = c;
p2.foo();    // 顯示 P2:foo

若從兩個父類繼承了同名且可見的值域,也會有類似問題:

#include <iostream>
using namespace std;

class P1 {
public:
    int x = 10;
};

class P2 {
public:
    int x = 20;
};

class C : public P1, public P2 {

};

int main() { 
    C c;

    int x = c.x; // error: request for member 'x' is ambiguous

    return 0;
}

C 實例其實會有兩份 x,藉由 this 位址的不同來存取,上例若要能存取,必須明確指定給 P1P2 型態:

C c;

P1 &p1 = c;
cout << p1.x << endl;  // 10

P2 &p2 = c;
cout << p2.x << endl;  // 20