2010年9月6日 星期一

has-a 的 private 繼承觀念

以下這段程式使用了 public 繼承表示一種 is-a 的關係,吉娃娃繼承了狗,所以吉娃娃是狗的一種。


class CDog
{
};

class CChihuahua : public CDog
{
};


這種 is-a 的關係代表吉娃娃繼承了狗的各種介面,吉娃娃包含了狗的各種 Method 或 Property,如吉娃娃若什麼事都不做,牠貴為一條狗,就具備了吠的能力,擁有 4 條腿,對於 CDog 的純虛擬函數,吉娃娃除了繼承外還必須提供實作。

這種關係不適合用來表達 "組合" 的概念,例如我們不會寫一個 CHouse 的類別又使用 public 繼承 CToliet,因為房子不是一種廁所,房子應該擁有廁所,由廁所、臥房、廚房等所組成,在不考慮很多間廁所、廚房的情況下我們可以這樣表示


class CHouse
{
protected:
  CKitchen m_kitchen;
  CToliet m_toliet;
  CBedroom m_bedroom;
};


這是一種常用的 has-a 概念,因為房子取得廚房的所有介面是無意義的,在另一篇中有提到,使用 private 繼承時,不論基底類別的成員屬性為何,卻會成為衍生類別的 private 成員或更受限制必須使用基底介面操作的成員,private 成員在類別被宣告為一個實體時,外界無從存取這個介面,所以可以說使用 private 方式衍生類別不會得到與基底類別同樣的介面,除此之外,private 繼承方式也不許可向上的隱轉型,這種繼承就屬於 has-a 的關係,同樣的例子修改如下


class CHouse : private CKitchen, private CToliet, private CBedroom
{
public:
  CHouse() : CKitchen(), CToliet(), CBedroom() {};
};

這裡還順便記下了多重繼承的建構方式,在多重繼承的類別建構中,一樣是使用 ":" 來串接並使用 "," 來分開各類別,需注意的是此建構式的執行順序是由右而左,在右邊的 CBedroom 會在 CToliet 之前被建構,所以若多重繼承中的基底類別成員是有相依性的,就要注意一下建構的順序。

私有繼承使用基底成員的方式與上面直接將類別宣告在類別中的方式也不相同,必須使用 "::" 運算子來存取基底類別的成員,例如


public:
double CHouse::GetSize() const
{
  return CKitchen()::GetSize + CToliet()::GetSize() + CBedroom()::GetSize();
}


這時還有另一個問題,CHouse 中的 CBedroom 並沒有實體的名稱,若外界必須取得 CHouse 中的 CBedroom 該怎麼做呢?這時可以利用類別繼承向上轉型的觀念,


public:
CBedroom CHouse::GetBedroom() const
{
  return *((CBedroom*)this);
}


函式中的 this 代表著 CHouse 自己的指標,而把自己的指標轉型成 CBedroom 就可以得到自己所繼承的那份臥房。

類別中包含類別與私有繼承這兩種方式都可以建構出 has-a 的關係,但私有繼承似乎麻煩且必須額外考慮許多細節,例如多重繼承 (multiple inheritance),另外 private 繼承也限制了單一物件,若上述情況考慮到兩間臥房情況即不適用,到底什麼情況會非用私有繼承不可呢?舉例兩項如下

1. 必須使用成員的 protected 成員時
2. 在類別中必須重新定義虛擬函式時

沒有留言:

張貼留言