《Effective C++》第4章 設計與聲明(2)-讀書筆記

章節回顧: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++》第5章 實現-讀書筆記指針

《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效率引起的,下次回顧時再補充吧。

相關文章
相關標籤/搜索