1. 程式人生 > >effective C++筆記--繼承與面向物件設計(二)

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中的介面)。
  和單一繼承比較,多重繼承更加複雜,使用上也更加難以理解,所以如果有一個單一繼承方案和多重繼承方案大約等價,那麼最好使用單一繼承方案。(不過沒有呢?那不還是得用多重繼承)