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

QingLiXueShi發表於2015-04-25

章節回顧:

《Effective C++》第1章 讓自己習慣C++-讀書筆記

《Effective C++》第2章 構造/析構/賦值運算(1)-讀書筆記

《Effective C++》第2章 構造/析構/賦值運算(2)-讀書筆記

《Effective C++》第3章 資源管理(1)-讀書筆記

《Effective C++》第3章 資源管理(2)-讀書筆記

《Effective C++》第4章 設計與宣告(1)-讀書筆記

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

《Effective C++》第5章 實現-讀書筆記

《Effective C++》第8章 定製new和delete-讀書筆記


 

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

相關文章