effective C++筆記--繼承與面向對象設計(二)

絕不重新定義繼承而來的非虛函數

. 假設基類中有一個非虛的成員函數,那麼派生類在公有繼承基類的時候,會繼承這個函數接口和它的實現,但是如果派生類中重新定義了這個函數接口,則會遮掩住基類的同名函數,這就不符合“public繼承是一種is-a關係”了。所以絕對不要重新定義繼承而來的non-virtual函數。

絕不重新定義繼承而來的缺省參數值

. 假設基類中有一個虛函數並對參數有一個缺省的值,而派生類對這個虛函數又給出了不一樣的缺省值,這樣做可能讓你達不到想要的結果:

class B{
public:
	virtual void f(int x = 1){
		cout<<x<<endl;
	}
};

class D : public B{
public:
	void f(int x = 2){
		cout<<x<<endl;
	}
};

int main(){
	
	B* b = new D;
	b->f();
	
	return 0;
}

. 以上代碼輸出的結果是1.但是指針指向的是D的對象,那使用這個虛函數應該得到2纔對,這是因爲缺省的參數值是靜態綁定(聲明時確定的類型)。你也可以在派生類中提供相同的缺省值,但是這對減少代碼重複就沒有幫助了。

通過複合塑模出has-a或“根據某物實現出”

. 複合是類型之間的一種關係。當某種類型的對象內含其它的類型的時候,便是這種關係。比如:

class Address{...};
class PhoneNumber{...};
class Person{
public:
	...
private:
	Address addr;
	PhoneNumber pn;
};

. 可以說人有一個住址或有一個電話號碼,但是不能說人是一個住址或人是一個電話號碼,這就是has-a和is-a的區別。
  但是比較麻煩的是區分is-a和is-implemented-in-terms-of(根據某物實現出)這兩種關係。這裏假設來自己實現一個set,底層通過linked lists,但是如果直接讓set繼承自list的話,會出現一些問題,比如list應該允許內含有重複的元素,但set不允許含有重複的元素,由此可知,二者並不是is-a的關係,正確的做法是根據list對象來實現一個set對象:

template<class T>
class set{
public:
	bool member(const T& item) const;
	void insert(const T& item);
	void remove(const T& item);
	std::size_t size() const;
private:
	std::list<T> rep;				//用來表示set的數據
};

//實現
template <typename T>
bool Set<T>::member(const T& item) const{
	return std::find(rep.begin(),rep.end(),item) != rep.end();
}
template <typename T>
void Set<T>::insert(const T& item){
	if(!member(item)){
		rep.push_back(item);
	}
}
template <typename T>
void Set<T>::remove(const T& item){
	typename std::list<T>::iterator it = 
		std::find(rep.begin(),rep.end(),item) ;
	if (it != rep.end()){
		rep.erase(it);
	}
}
template <typename T>
std::size_t Set<T>::size() const{
	return rep.size();
}

. 在應用域,複合意味着has-a;在實現域,複合意味着is-implemented-in-terms-of。

明智而審慎地使用private繼承

. private繼承不意味着is-a關係,先看看private繼承的行爲:如果class之間是private繼承,編譯器不會自動將一個派生類對象轉換成基類對象,這將造成派生類對象調用基類的成員函數時失敗;另外通過private繼承而來的成員屬性都會變成private屬性,縱使在基類中原來是public或是protected。
  private繼承意味着is-implemented-in-terms-of(根據某物實現出)。這好像和複合的概念有重合,如何取捨呢?答案很簡單:儘可能使用複合,必要時才使用private繼承。何時纔是必要?主要是當protected成員或virtual函數牽扯進來的時候。
  還有一種激進的情況也可以使用private繼承來處理:當處理的class不帶任何數據時。這樣的class沒有no-static成員變量,沒有虛函數,也沒有虛基類。這種所謂的empty class對象不使用任何的內存空間,因爲沒有數據需要存儲,但是由於技術上的理由,C++決定凡是獨立的對象都必須有非零大小,因此:

class Empty{};
class HasInt{
private:
	int x;
	Empty e;
};

. 這樣做之後會發現sizeof(HasInt) > sizeof(int)。如果是通過繼承的方式:

class HasInt : private Empty{
private:
	int x;
};

. 這樣做之後會發現sizeof(HasInt) == sizeof(int)。這就是所謂的EBO(空白基類最優化),值得注意的是EBO一般只在單一繼承中有效。

明智而審慎地使用多重繼承

. 多重繼承在C++中還是有不小的爭議的。最先需要認清的事情是當使用多重繼承的時候,程序可能從一個以上的基類中繼承相同的名稱(函數、typedef等),那可能導致更多的歧義機會,如菱形繼承。
  一種針對菱形繼承的可行的方式是:將最頂上的基類作爲虛基類,並且令所有繼承自它的class都採用virtual繼承。但是採用virtual繼承帶來的代價是:使用virtual繼承的那些class所產生的對象往往比不使用virtual繼承的class產生的對象體積要大,訪問虛基類成員變量時也會更耗時。對此有兩條忠告:1.非必要時不要使用虛繼承;2.必須使用虛基類時,儘量不在裏面放數據(就像java中的接口)。
  和單一繼承比較,多重繼承更加複雜,使用上也更加難以理解,所以如果有一個單一繼承方案和多重繼承方案大約等價,那麼最好使用單一繼承方案。(不過沒有呢?那不還是得用多重繼承)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章