繼承本身就會令事情複雜化,多重繼承更是會令複雜度加劇,〈虛擬繼承〉中看到的不過是部份情況。
同名的方法或值域若在子類中可見,就必須處理名稱重疊時的相關議題(在子類中不可見的值域或方法,程式碼撰寫上本來就不能存取,也就不會有名稱重疊的判斷問題)。例如,如果繼承的父類有實作方法,而另一父類有同名的純 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
,那就必須明確指定給 P1
或 P2
型態:
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
位址的不同來存取,上例若要能存取,必須明確指定給 P1
或 P2
型態:
C c;
P1 &p1 = c;
cout << p1.x << endl; // 10
P2 &p2 = c;
cout << p2.x << endl; // 20