文章目錄
條款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
- 新type的對象應該如何被創建和銷燬?
- 對象的初始化和對象的賦值該有什麼樣的差別?
- 新type的對象如果被passed by value,意味着什麼?
- 什麼是新type的“合法值”?
- 你的新type需要配合某個繼承圖系嗎?
- 你的新type需要什麼樣的轉換?
- 什麼樣的操作符和函數對此新type而言是合理的?
- 什麼樣的標準函數應該駁回?
- 誰該取用新type的成員?
- 什麼是新type的“未聲明接口”?
- 你的新type有多麼一般化?
- 你真的需要一個新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
- 語法一致性。所有成員變量都通過成員函數訪問。
- 使用函數訪問可以對成員變量的處理有更精確的控制。(只讀、只寫等)
- 封裝。爲”所有可能的實現“提供彈性,public意味着不封裝,即不可改變,取消一個public成員變量影響極大。
- 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成員函數。