15.3 轉換與繼承
基類類型對象既可以作為獨立對象存在,也可以作為派生類對象的一部分而存在,因此,一個基類對象可能是也可能不是一個派生類對象的部分,結果,沒有從基類引用(或基類指針)到派生類引用(或派生類指針)的(自動)轉換。
相對於引用或指針而言,對象轉換的情況更為復雜。雖然一般可以使用派生類型的對象對基類類型的對象進行初始化或賦值,但,沒有從派生類型對象到基類類型對象的直接轉換。
15.3.1 派生類到基類的轉換
如果有一個派生類型的對象,則可以使用它的地址對基類類型的指針進行賦值或初始化。同樣,可以使用派生類型的引用或對象初始化基類類型的引用。嚴格說來,對對象沒有類似轉換。編譯器不會自動將派生類型對象轉換為基類類型對象。
但是,一般可以使用派生類型對象對基類對象進行賦值或初始化。對對象進行初始化和/或賦值以及可以自動轉換引用或指針。
1. 引用轉換不同於轉換對象
可以將派生類型的對象傳給希望接受基類引用的函數。也許會因此認為對象進行轉換,但是事實並非如此。將對象傳給希望接受引用的函數時,引用直接綁定到該對象,雖然看起來在傳遞對象,實際上是該對象的引用,對象本身未被復制,並且,轉換不會在任何方面改變派生類型對象,該對象仍是派生類型對象。
將派生類對象傳給希望接受基類類型對象(而不是引用)的函數時,情況完全不同。在這種情況下,形參的類型是固定的——在編譯時和運行時形參都是基類類型對象。如果用派生類型對象調用這樣的函數,則該派生類對象的基類部分被復制到形參。
一個是將派生類對象轉換為基類類型引用,一個是用派生類對象對基類對象進行初始化或賦值,理解它們之間的區別很重要。
2. 用派生類對象對基類對象進行初始化或賦值
對基類對象進行初始化或賦值,實際上實在調用函數:初始化時調用構造函數,賦值時調用賦值操作符。
用派生類對象對基類對象進行初始化或賦值時,有兩種可能性。第一種(雖然不大可能的)可能性是,基類可能顯式定義了將派生類型對象復制或賦值給基類對象的含義,這可以通過定義適當的構造函數或賦值操作符實現。
基類一般(顯式或隱式地)定義自己的復制構造函數或賦值操作符,這些成員接受一個形參,該形參是基類類型的(const)引用。因為存在從派生類引用到基類引用的轉換,這些復制控制成員可用於從派生類對象對基類對象進行初始化或賦值。
3. 派生類到基類轉換的可訪問性
像繼承的成員函數一樣,從派生類到基類的轉換可能是也可能不是可訪問的。轉換是否可訪問取決於在派生類的派生列表中指定的訪問標號。
要確定到基類的轉換是否可訪問,可以考慮基類的public成員是否可訪問,如果可以,轉換是可訪問的,否則,轉換是不可訪問的。
如果是public繼承,則用戶代碼和後代類都可以使用派生類到基類的轉換。如果類是使用private或protected繼承派生的,則用戶代碼不能將派生類型對象轉換為基類對象。如果是private繼承,則從private繼承類派生的類不能轉換為基類。如果是protected繼承,則後續派生類的成員可以轉換為基類類型。
無論是什麼派生訪問標號,派生類本省都可以訪問基類的public成員,因此,派生類本身的成員和友元總是可以訪問派生類到基類的轉換。
class Base{
protected:
int i;
public:
int j;
private:
int k;
};
class Child:protected Base{};
class Child1:public Base{};
class Child2:public Child{
void f2(){
i;
Base b(*this);
b.j;
}
};
class Child3:private Base{
void f1(){
i;
}
};
class Child4:public Child3{
void f2(){
//nothing accessiable...
}
};
Child1 c1=Child1();
Base b(c1);
摘自 xufei96的專欄