章節回顧:html
《Effective C++》第1章 讓本身習慣C++-讀書筆記算法
《Effective C++》第2章 構造/析構/賦值運算(1)-讀書筆記安全
《Effective C++》第2章 構造/析構/賦值運算(2)-讀書筆記函數
《Effective C++》第3章 資源管理(1)-讀書筆記測試
《Effective C++》第3章 資源管理(2)-讀書筆記this
《Effective C++》第4章 設計與聲明(1)-讀書筆記spa
《Effective C++》第4章 設計與聲明(2)-讀書筆記設計
《Effective C++》第8章 定製new和delete-讀書筆記code
條款22:將成員變量聲明爲private
首先,有兩個小點理由支持你將成員變量聲明爲private。
(1)接口的一致性。
若是public接口的都是函數,那麼客戶在調用時就不用考慮是否須要加小括號,由於每一個調用的都是函數,必須加小括號。
(2)精細的訪問控制。
使用函數可讓你對成員變量的處理有更精確的控制,若是你令成員變量爲public,每一個人均可以讀寫它。
最重要的是private提供封裝性:
若是你經過函數訪問成員變量,後面能夠更改某個計算替換這個成員,而class客戶一點也不會知道class的內部已經變化了,只需從新編譯便可。
假設你將一個成員變量聲明爲public或protected而客戶開始使用它,就很難改變那個成員變量所涉及的一切。太多代碼須要重寫、測試、重寫文檔、編譯。從封裝的角度,其實只有兩種訪問權限:private和其餘(不提供封裝)。
請記住:
(1)切記將成員變量聲明爲private。這可賦予客戶訪問數據的一致性、可細微劃分訪問控制、允諾約束條件得到保證,並提供class做者以充分的彈性。
(2)protected並不比public更具封裝性。
條款23:寧以non-member、non-friend函數替換member函數
下面有一個class:
class WebBrowser { public: void clearCache(); void clearHistory(); void removeCookies(); };
用戶但願把這三個接口經過提供一個函數去作,能夠定義一個成員函數調用這三個函數。
void WebBrowser::clearEverything() //成員函數,調用clearCache、clearHistory和removeCookies { clearCache(); clearHistory(); removeCookies(); }
固然,這個功能也能夠經過一個非成員函數提供:
void clearBrowser(WebBrowser& wb) //非成員函數 { wb.clearCache(); wb.clearHistory(); wb.removeCookies(); }
如今要考量的是哪種作法比較好?
面向對象守則要求,數據以及操做數據的函數應該被捆綁在一塊兒,這意味着它建議member是較好的選擇。這個建議是錯誤的,是對面向對象真實意義的一個誤解。
面向對象守則要求數據應該儘量被封裝。與直觀相反,clearEverything帶來的封裝性比clearBrowser要低。由於non-member non-friend函數不能訪問class內的private。
注意:這裏考量的是member和non-member non-friend兩者提供相同的機能時的一個抉擇。friends對private的訪問權利與member函數相同。
條款24:若全部參數皆需類型轉換,請爲此採用non-member函數
一般令class支持隱式類型轉換是很差的,但也有例外。假設一個class表示有理數,那麼容許整數「隱式轉換」爲有理數是比較合理的。
class Rational { public: Rational(int numerator = 0, int denominator = 1); int numerator() const; //分子訪問函數 int denominator() const; //分母訪問函數 };
你想讓這個class支持乘法運算。可能聲明operator*爲成員函數。
class Rational { public: ... const Rational operator*(const Rational& rhs) const; };
這個設計按照下面這種方式使用是沒問題的:
Rational oneEighth(1, 8); Rational oneHalf(1, 2); Rational result = oneHalf * oneEighth; //好的,沒問題 result = result * oneEighth; //好的,沒問題
當你想使用混合運算時:
result = oneHalf * 2; //好的,沒有問題 result = 2 * oneHalf; //錯誤,不知足交換律
本質上上面的用法與下面的等價:
result = oneHalf.operator*(2); //result = oneHalf * 2; result = 2.operator*(oneHalf); //result = 2 * oneHalf;
因此你就明白爲何不知足交換律了。爲了支持混合運算,須要將operator*設置爲non-member函數。
const Rational operator*(const Rational& lhs, const Rational& rhs) { return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator()); }
下面的調用的OK的:
Rational oneFourth(1, 4); Rational result; result = oneFourth * 2; //好的,沒問題 result = 2 * oneFourth; //好的,沒問題
這裏還要說明的一點是構造函數必定不能聲明爲explicit的,不然整型的數值2就不能隱式轉換爲Rational對象了。
請記住:若是你須要爲某個函數的全部參數(包括被this指針所指的那個隱喻參數)進行類型轉換,那麼這個函數必須是non-member的。
條款25:考慮寫出一個不拋出異常的swap函數
swap函數就是將兩對象的值彼此賦予對方。缺省狀況下,swap可由STL提供的swap算法完成。典型實現以下:
namespace std { template<typename T> void swap(T& a, T& b) { T temp(a); a = b; b = temp; } }
只要T支持拷貝(經過拷貝構造函數和copy assignment操做符完成),就能夠利用該算法。
但存在某些狀況,缺省的swap行爲每每效率較低。例如,以指針指向一個對象。考慮下面的class:
class WidgetImpl { public: ... private: int a, b, c; vector<double> v; }; class Widget { public: Widget(const Widget& rhs); Widget& operator=(const Widget& rhs) { ... *pImpl = *(rhs.pImpl); ... } private: WidgetImpl *pImpl; };
若是咱們要交換Widget對象,缺省的算法會拷貝3個Widget對象和3個WidgetImpl對象,效率很低。實際上,咱們只須要置換pImpl指針的指向便可。
爲了解決效率問題,咱們須要告訴std::swap,當交換Widget對象時,只須要交換指針就行了。能夠將std::swap針對Widget特化。
namespace std { template<> void swap<Widget>(Widget& a, Widget& b) { swap(a.pImpl, b.pImpl); //交換指針值 } }
這只是個思路,通常咱們不能改變std內的任何東西,咱們能夠令Widget聲明一個swap的public成員函數作置換工做,而後將std::swap特化。
class Widget { public: void swap(Widget& other) { using std::swap; // swap(pImpl, other.pImpl); } };
注意:成員swap函數毫不可拋出異常。由於swap的一個最好應用是幫助class提供強烈的異常安全性保障。
請記住:當std::swap對你的類型效率不高時,提供一個swap成員函數,並肯定這個函數不拋出異常。
補充說明:條款25還有一些其餘知識,我沒有理解的特別好,就沒有說明,但整個問題是由swap效率引起的,下次回顧時再補充吧。