4、設計與聲明

條款18:讓接口容易被正確使用,不易被誤用

理想上,如果客戶企圖使用某個接口卻沒有獲得預期行爲,代碼就不應該通過編譯。

class Date {
public:
	Date(int month, int day, int year);  // 參數的次序不明確,並且沒有限定範圍
	...
}

// 第一步改動:指定參數次序
struct Day {
explicit Day(int d): val(d) {}
int val;
};
struct Month{};
struct Year{};
Class Date {
public:
	Date(const Month& m, const Year& y, const Day d);
	...
};

// 第二步改動:限定參數範圍(使用enum或者以函數替換對象)
class Month { // 以函數替換對象
public:
    static Month Jan() { return Month(1); }
    ...
    static Month Dec() { return Month(12); }
private:
    explicit Month(int m);
    ...
};
Date d(Month::Mar(), Day(30), Year(1995));

條款19:設計class猶如設計type

  1. 新type的對象應該如何被創建和銷燬?
  2. 對象的初始化和對象的賦值該有什麼樣的差別?
  3. 新type的對象如果被passed by value,意味着什麼?
  4. 什麼是新type的“合法值”?
  5. 你的新type需要配合某個繼承圖系嗎?
  6. 你的新type需要什麼樣的轉換?
  7. 什麼樣的操作符和函數對此新type而言是合理的?
  8. 什麼樣的標準函數應該駁回?
  9. 誰該取用新type的成員?
  10. 什麼是新type的“未聲明接口”?
  11. 你的新type有多麼一般化?
  12. 你真的需要一個新type嗎?

條款20:寧以pass-by-reference-to-const替換pass-by-value

1、pass-by-reference-to-const不會觸發任何構造函數和析構函數,因爲不會創建新對象。
2、避免對象切割行爲,將derived對象賦值到base對象時,僅會複製base對象部分成員。
3、上述規則不適用內置類型、STL迭代器和函數對象,對它們而言,pass-by-value比較適當。

條款21:必須返回對象時,別妄想返回其reference

  • 返回pointer或reference指向一個local stack對象(空指針)
  • 返回reference指向一個heap-allocated對象(內存泄露)
  • 返回pointer或reference指向一個local static對象(線程不安全)

條款22:將成員變量聲明爲private

  1. 語法一致性。所有成員變量都通過成員函數訪問。
  2. 使用函數訪問可以對成員變量的處理有更精確的控制。(只讀、只寫等)
  3. 封裝。爲”所有可能的實現“提供彈性,public意味着不封裝,即不可改變,取消一個public成員變量影響極大。
  4. protected並不比public更具封裝性。

條款23:寧以non-member、non-friend替換member函數

一個同樣的功能,成員函數能做,非成員非友元函數都能做,使用後者更佳。因爲這樣的做法不會增加”能夠訪問class內之private成分”的函數數量。
寧可拿non-member non-friend函數替換member函數,這樣做可以增加封裝性、包裹彈性和機能擴充性。

條款24:若所有參數皆需類型轉換,請爲此採用non-member函數

// 將允許參數進行隱式類型轉換的函數寫成成員函數
class Rational {
public:
	Rational(int numerator = 0, int denominator = 1);      // 構造函數不聲明爲explicit,允許int-to-Rational
	const Rational operator* (const Rational& rhs) const;  // 成員函數
	int numerator() const;
	int denominator() const;
private:
	int numerator;
	int denomiator;
};

Rational x(1, 8);
Rational y(1, 2);
Rational rel = x * y;    // right
rel = x * 2;			 // right, 2就是int-to-Rational
rel = 2 * x;             // error,不支持

只有當參數被列於參數列內,這個參數纔是隱式類型轉換的合格參與者。所以rel = x * 2可以,但是rel = 2 * x不行,原因是前者中2位於x.operator*()的參數列內。可作下述改進:

// 將operator*聲明爲non-member函數,就可以提供這樣的參數列表
const Rational operator* (const Rational &lhs, const Rational rhs) {
	...
}

綜上,如果你需要爲某個函數的所有參數(包括被this指針所指的那個隱喻參數)進行類型轉換,那麼這個函數必須是個non-member。

條款25:考慮寫出一個不拋異常的swap函數

標準庫的swap算法實現:

namespace std {
	template<typename T>
	void swap(T &a, T&B)
	{
		T temp(a);
		a = b;
		b = temp;
	}
}

針對piml(pointer to implementation)手法的對象,swap效率很低:

class WidgetImpl {
public:
	...
private:	// 假設有很多私有成員,意味着複製時間很長
	int a, b, c;
	std::vector<double> v;
	...
};

class Widget {
public:
	Widget(const Widget& rhs);
	Widget& operator=(const Widget &rhs) {
		...
		*pImpl = *(rhs.pImpl);
		...
	}
private:
	WidgetImpl* pImpl;

正常情況下,置換Widget對象的值,只需要置換內部的pImpl指針即可,但是使用缺省的swap算法,會將對象內部的成員複製三次,效率非常低。
處理方式:
1、提供一個public swap成員函數,讓它高效地置換對象值。
2、在class或template的命名空間內提供一個non-member swap,並令它調用上述swap成員函數。
3、如果正在編寫一個class(而非class template),爲它特化std::swap,並令它調用swap成員函數。

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