如〈繼承共同行為〉中看過的,在單一繼承時,情況比較單純,建構子類實例時,會先執行父類建構式,接著是子類建構式,而解構的時候相反,會先執行子類解構式,接著才是父類解構式。
多重繼承時,若繼承來源之一有狀態定義,另一個沒有狀態定義,就如〈純虛擬函式(二)〉、〈模版與繼承〉中的範例,因為另一來源沒有狀態定義,也就不用考慮該來源的初始或銷毀問題,這時只要考量有狀態定義的繼承來源的建構與解構,如同單一繼承,問題就可以單純化。
在進一步看到多重繼承的建構與解構之前,先來看個單一繼承時 this
實際位址在哪的示範:
#include <iostream>
using namespace std;
class P {
int x;
public:
P(int x) : x(x) {
cout << "P:" << this << endl;
}
};
class C : public P {
public:
C(int x) : P(x) {
cout << "C:" << this << endl;
}
};
int main() {
C c(10);
cout << &c << endl;
return 0;
}
顯示的結果會是同一位址:
P:0x61febc
C:0x61febc
0x61febc
如果是多重繼承的話呢?
#include <iostream>
using namespace std;
class P1 {
int x;
public:
P1(int x) : x(x) {
cout << "P1:" << this << endl;
}
};
class P2 {
int x;
public:
P2(int x) : x(x) {
cout << "P2:" << this << endl;
}
};
class C : public P1, public P2 {
public:
C(int x) : P1(x), P2(x) {
cout << "C:" << this << endl;
}
};
int main() {
C c(10);
cout << &c << endl;
return 0;
}
多重繼承時,建構式的執行順序會與繼承的順序有關(而不是呼叫父類建構式的順序),C
因為繼承時的順序是 P1
、P2
,建構式執行順序會是 P1
、P2
、C
,至於解構式的執行順序,會是與建構式執行相反的順序,從執行結果中,可以發現 this
的位址會是不同:
P1:0x61feb8
P2:0x61febc
C:0x61feb8
0x61feb8
多重繼承時,C
實例的起始位址是 0x61feb8,而 P1
位址的偏移量是 0,P2
位址的偏移量是 4,因此 P1
、P2
中雖然都定義了 x
成員,若在個別的類別中寫 this->x
,因為 this
位址不同,取得就會是各自不同的 x
,因為是各自不同的位址,建構時也是個別地初始化在不同的位址,從執行結果中也可以看到,衍生類別實例的位址會用來初始第一個繼承的父類。
多重繼承時,個別類別中的 this
位址不同的事實,也會反應在以父類型態參考子類實例之時:
...略
int main() {
C c(10);
cout << &c << endl;
P1 &p1 = c;
P2 &p2 = c;
cout << "p1:" << &p1 << endl;
cout << "p2:" << &p2 << endl;
return 0;
}
執行時有關 p1
、p2
位址的顯示結果會是不同的:
...
p1:0x61feb0
p2:0x61feb4
取址的時候也是,在以下的範例中,都是 &c
,然而 p1
與 p2
儲存的位址並不同,知道這個事實後,就會知道將 p1
指標的位址指定給 p2
是不可行的,會造成編譯錯誤:
... 略
int main() {
C c(10);
P1 *p1 = &c;
P2 *p2 = &c;
p2 = static_cast<P2*>(p1); // error: invalid static_cast from type 'P1*' to type 'P2*'
return 0;
}
如果強硬地使用 C 風格轉型,也就是 p2 = (P2*) p1
的話,雖然可以通過編譯,結果是造成 p2
儲存了 p1
的位址,雖然 p2
的型態是 P2*
,若透過 p2
操作 P2
定義的方法,會造成方法中的 this
指向的是 p1
的位址,結果會是不可預期的,當然,P1
與 P2
本來就是不同繼承體系、不同型態,試圖在兩者之間轉換,本來就是錯誤的。