多重繼承的問題,首先想到多繼承構造函數如何處理?
Symbian下是可以一個C類多個M類多重繼承的
C++中的多繼承,構造函數處理並沒有問題,對象構造的時候按照繼承中聲明的順序調用多個父類的構造函數,析構函數同樣遵守單繼承中的原則。
二意性問題
如果多基類中存在同名成員,會產生二意性的問題
比如,Root1類中聲明DoAny()接口,Root2類中也聲明瞭DoAny()接口,Child多承繼Root1和Root2,那麼如果Child對象直接調用DoAny接口就會產生編譯錯誤。
深入原因??
對象值給父類指針、引用產生的二義性問題
繼續上面的例子,Child對象的引用可以賦給Root1和Root2類引用。
如果存在void display(const Root1&),void display(cont Root2&)兩個接口,那麼Child child; display(child);這個調用也會產生二義性錯誤,編譯器不知道應該調用哪個接口。
名字查找過程
查找過程是一個域一個域的查找,直到全局域。每個類都有一個類域。看如下示例代碼:
class ZooAnimal {
public:
void print() const;
// 爲了向外域暴露設爲 public
string is_a;
int ival;
private::
double dval;
};
class Bear : public ZooAnimal {
public:
void print() const;
int mumble( int );
// 爲了向外域暴露設爲 public
string name;
int ival;
};
Bear bear; bear.is_a;
如上調用,is_a的名字解析過程爲:
1. 首先查找Bear類域,沒有找到
2. 查找Bear父類ZooAnimal類域,找到其聲明,名字解析成功。
bear.ival;
這個調用解析過程如何呢?因爲Bear和其父類ZooAnimal裏都有該成員定義。答案是按照上的順序查找,查找到馬上返回。
1. Bear類域中查找,找到ival聲明,解析成功,不再向下查找。那麼父類中名字就被子類中定義屏蔽了。
需要用bear.ZooAnimal::ival的形式來域限定符訪問ZooAnimal類中成員ival,這樣編譯器直接在ZooAnimal類域中開始查找名字。
看如下調用各名字的位置:
int ival;
int Bear::mumble(int ival)
{
return ival + //形參
::ival + //全局成員
ZooAnimal::ival +
Bear::ival;
}
而如下名字解析會出錯:
int dval;
int Bear::mumble(int ival)
{
return ival + dval;//----dval爲ZooAnimal私有成員,錯誤
}
解析過程如下:1. 在Bear類域中查找,沒有找到
2. 在ZooAnimal類域中查找,找到,但是因爲私有成員不能訪問,編譯錯誤,不再向下查找。
3. 因爲上一步,全局域中的名字就被屏蔽了。
多繼承中的情況
多繼承中,名字的解析的時候,對父類域的查找是同時進行的。同時查找Root1類域和Root2類域,如果發現找到兩個同樣的名字,那麼由於二義性名字解析出錯,產生編譯錯誤。
========================更新========================
才現上次的研究還沒有抓住多繼承問題的重點,下面來一個新的總結:
1. 普通多繼承
類同時繼承自多個基類就是多繼承,C++支持多繼承。
但是需要注意多繼承產生的二意性問題,如果兩個基類都存在同名的成員,那麼在子類中對它的訪問就是有二義性錯誤的。名字解析過程中,同時找到兩個滿足條件的成員,這讓編譯器無法確定應該使用哪個。
其中比較典型的問題就是下面要講到的:菱形繼承問題。
2. Private、public、protected繼承
2.1 private繼承
與public繼承的不同:
² 不是父類的子類型,也就是說不能賦值給父類指針或引用。
² 接口私有化。
通常多繼承時會使用public繼承a,private繼承b的方式。
可以通過如下方式還原private基類中的成員訪問級別:
public:
//using private father func
//You can level down the access level, butcan't level up it.
using ZooAnimal::GetLegs;//correct
但是,經過我的測試,你可以還原或降低原有的訪問級別,不能提高訪問級別。比如基類中本身是private方法,在public塊中using這個方法是編譯錯誤的。
2.2 public繼承
普通的繼承方式
2.3 protected繼承
基類所有public成員成爲派生類的protected成員。
3. 菱形繼承
3.1 基本概念
菱形繼承是指:有基類A,B、C繼承自A,D多繼承自B、C,於是最終派生類D由兩條不同的繼承線繼承基類A。
這是多繼承中典型需要考慮的問題,這種情況下D的實例對基類A成員的訪問都是二義性錯誤的。
3.2 普通繼承方式中存在的問題
D對象中存在A類的2份不同的對象。
存儲2份A對象浪費空間。
A的構造函數會被調用2次。
引起二義性問題。
因爲是2份不同的對象,2個對象中成員的值不一致。
爲解決如上的種種問題,C++引用虛繼承。
4. 虛繼承
4.1 基本概念
虛繼承的基類叫做虛擬基類,無論它在派生層次中出現多少次,只有一個共享的基類子對象被繼承。
C++ Primmer書裏不建議大量使用虛繼承,會有效率問題。
4.2 構造、析構
虛繼承時,需要由最終派生類主動調用虛基類的構造函數,否則編譯錯誤。
因爲,對於虛繼承的情況編譯器會阻止中間派生類調用虛基類的構造函數。
比如:
在類D的實例初始過程中,D爲最終派生類,B、C爲中間派生類,A爲虛基類。
調用D的構造函數前,先找到它的基類B,調用B的構造函數,調用B構造函數前找到B的基類A,發現爲虛繼承關係,阻止B對A構造函數的調用。編譯器會確定D有沒有對A構造函數的調用,如沒有編譯報錯。
虛基類的構造函數最先被調用,然後再按聲明順序構造非虛基類。於是構造函數是:A、B、C、D調用順序。
析構函數是和構造相反的順序進行。
4.3 成員可視性
有如下幾種情況:
情況 |
普通繼承 |
虛繼承 |
A中的接口,未被B、C重載 |
二義性 |
OK,調用A的接口 |
B重載了A的接口,C未重載 |
二義性 |
OK,調用B的接口 |
B、C都重載的接口 |
二義性 |
二義性 |