1. 程式人生 > >effective C++筆記——設計與宣告

effective C++筆記——設計與宣告

文章目錄

讓介面容易被正確使用,不易被誤用

. 建立一個容易被正確使用的介面,首先必須考慮客戶可能犯什麼樣的錯誤,比如對函式的引數傳遞順序有誤,或者引數是無效的,以及返回值並不安全等,書中舉了一個關於日期的函式,客戶可能將月份和天數的順序輸入弄錯,也可能輸入無效日期如2月30日。
  對介面保持一致性

的原則往往對正確使用介面有幫助,比如STL容器的介面大多都是一致的,每個STL容器都有一個名為size的成員函式,用來表明容器中當前有多少個物件,這樣比一會兒使用size一會使用length的方式要好很多。

設計class猶如設計type

. c++如同其他面向物件程式語言一樣,定義了一個新的class就定義了一個新的type,如何設計高效的class通常需要面對以下的問題:

  • 新type的物件應該如何被建立和刪除: 這將影響class的建構函式和解構函式以及記憶體的分配和釋放問題。
  • 物件的初始化和物件的賦值有什麼區別: 這將影響到建構函式和賦值操作符的行為,以及兩者的差異。
  • 新type的物件如果被pass-by-value,意味著什麼?
  • 什麼是新type的合法值: 這影響對資料的錯誤檢查,函式的異常丟擲等。
  • 新type是否需要配合某個繼承體系: 值得注意的是解構函式是否需要宣告為virtual。
  • 新type需要什麼樣的轉換?
  • 什麼樣的操作符和函式對此type而言是合理的?
  • 什麼樣的標準函式應該被駁回: 那些應當被宣告為private
  • 誰該取用新type的成員: 對許可權(private、public、protected)和友元的設定是否合理
  • 什麼是type的未宣告介面?
  • 新的type有多一般化: 有沒有必要直接做泛型處理。
  • 真的需要一個新的type嗎: 如果只是為既有的class新增功能,說不定單純的定義一個或多個non-member函式或template,更能達到目標。

寧以pass-by-reference-to-const替換pass-by-value

. 值傳遞對是建立引數的一個副本,這雖然不會改變傳入的引數本身,但是這將帶來更多更費時的操作,使用常引用(指標)的方式傳值將回避這種情況。

必須返回物件是,別妄想返回其reference

. 知道了值傳遞帶來的種種不便後,可能會堅持使用引用傳遞的方式,這時往往會犯一個致命錯誤:開始傳遞一些references指向其實並不存在的東西。比如一個函式的返回值是一個引用,在函式中建立了本地物件,在函式結束的時候將它返回,這個本地物件實際上是被銷燬了,外部的變數取得的值實際上是不知道指向是什麼地方的。
  還有就是不要讓這個引用或者指標指向堆上動態分配的記憶體,因為這些記憶體是需要手動去釋放的,在多次呼叫這個函式時,將帶來很多記憶體管理的麻煩。

將成員變數宣告為private

. 眾所周知類的三大特性之一是封裝,將成員變數宣告為private,通過一系列函式來進行訪問能很好的實現封裝的效果。

寧以non-member、non-friend替換member函式

. 假設有一個類用來表示網頁瀏覽器,其中有三個函式分別用來刪除快取、刪除歷史記錄以及刪除所有cookies:

class WebBrowser{
public:
	...
	void clearCache();
	void clearHistory();
	void removeCookies();
	...
};

假設想一整個執行所有的清除操作,很容易想到兩種方法:
1.為類再新增一個方法,在這個方法中呼叫以上三個步驟:

void clearAll();				//呼叫clearCache、clearHistory和removeCookies

2.寫一個non-member函式來呼叫以上三個步驟:

void clearAll(WebBrowser& b){
	b.clearCache();
	b.clearHistory();
	b.removeCookies();
}

. 以上兩種方式哪種更好呢?根據面向物件守則要求資料應該儘可能被封裝,然而member函式版本比non-member函式版本的封裝性要低。因為增加一個成員函式就增加了訪問類的成員變數的機會。

若所有的引數皆需要型別轉換,請為此採用non-member函式

. 令classes支援隱式型別轉換通常是個糟糕的主意,但是這個規則也有例外,比如自己建立一個有理數的類,應該支援從整型數轉換到有理數的轉換:

class Rational{
public:
	Rational(int numerator = 0,int denominator = 1); //不將建構函式設為explicit的,
																				//允許隱式轉換
	int numerator() const;
	int denominator() const;
private:
	...
};

這個類應該要支援算術運算,例如加法、乘法等,但是應該將運算的函式寫成成員函式還是非成員函式呢?先假設寫成成員函式的版本:

class Rational{
public:
	...
	const Rational operator*(const Rational& rhs) const;
};

這種方式可以使用運算了,但是有一種情況卻會導致錯誤:

Rational r1(1,8);
Rational r2(1,2);
Rational result = r1 * r2;						//正確
result = result * r1;							//正確
result = r1 * 2;							//正確
result = 2 * r1 ;							//錯誤

可以看出怎麼連乘法連交換律都不能實現了,因為錯誤的那個語句和下面的形式一樣:

result = 2.operator(r1 );

r1是一個內含operator成員函式的物件,所以編譯器呼叫該函式,整數2並沒有相應的class,也就沒有operator成員函式。編譯器也會嘗試尋找非成員函式的接受兩個引數(一個int,一個Rational物件)的operator*函式,但是並不存在,所以查詢也會失敗。
  因此讓這個函式成為一個非成員函式是一個可行的方法,編譯器會允許在每一個實參身上執行隱式轉換,以上錯誤的形式就能通過編譯了:

const Rational operator*(const Rational& lhs,const Rational& rhs){
	...
}
result = 2 * r1;				//正確