程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> Effective C++筆記:設計與聲明

Effective C++筆記:設計與聲明

編輯:C++入門知識

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

1,好的接口很容易被正確使用,不容易被誤用。你應該在你的所有接口中努力達成這些性質。

2,“促進正使用”的辦法包括接口的一致性,以及與內置類型的行為兼容。

3,“阻止誤用”的辦法包括建立新類型,限制類型上的操作,束縛對象值,以及消除客戶的資源管理責任。

4,shared_ptr支持定制型刪除器。這可以防范DLL問題,可以用來自動解除互斥鎖。

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

博客地址:http://www.cnblogs.com/ronny/ 轉載請注明出處!

如何設計你的類:

1,新type的對象應該如何創建和銷毀?

影響到的設計函數:構造函數、析構函數以有內存分配函數和釋放函數(operator new,operator new[],operator delete,operator delete [])。

2,對象初始化和對象的賦值有什麼樣的差別?

取決於構造函數和賦值操作符的行為。

3,新type對象如果被passed-by-value,意味著什麼?

拷貝構造函數用來定義一個type的pass-by-value該如果實現。

4,什麼是新type的“合法值”?

構造函數必須進行有效的檢測。

5,你的新type需要配合某個繼承圖系嗎?

如果繼承自其他的類,要受到其他類的束縛,注意virtual和no-virtual的影響。如果有其他類繼承這個類,那麼需要考慮是否要把析構函數設計為virtual函數。

6,你的新type需要什麼樣的轉換?

如要希望其他類型能轉換為你所設計的type類型,則需要對應的non-explicit構造函數存在。如果需要你把設計的type可以轉換為其他類型,則需要定義類型轉換函數operator T。

7,什麼樣的操作和函數對此新type而言是合理的。

取決於你為你的class聲明哪些函數。其中有些可能是成員函數,有些則不是。

8,什麼樣的標准使函數應該駁回?

那些正是你必須聲明為private者。

9,誰該取用新type的成員?

這個提問幫組你決定哪個成員為public,哪個為protected,哪個為private。它也幫助你決定哪一個class或function應該是友元,以及將它們嵌套於另一個之內是否合理。

10,你的新type有多麼一般化?

如果你定義的是一整個types,是否應該定義一個新的class template 。

11,什麼是新type的“未聲明接口”?

12,你真的需要一個新type嗎

如果只是定義新的derived class以便為既有的類添加機能,那麼說不定單純定義一個或多個非成員函數或模板,更能夠達到目標。

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

pass-by-value會造成較多的構造函數與析構函數的開銷,並且在將派生類傳遞給基類接口的時候會發生類的切割問題。

上面的規則並不適用於內置類型,以及STL的迭代器和函數對象。對它們而言,pass-by-vaule往往比較適當。

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

絕對不要返回point或reference指向一個local stack對象,或返回reference指向一個heap-allocated對象,或返回point或reference指向一個local static對象而有可能同時需要多個這樣的對象。

我們來考慮一個有理數的類以及為它定義一個有理數想乘的友元:

// 博客地址:http://www.cnblogs.com/ronny/
class Rational{
public:
    Rational(int numerator = 0, int denominator = 1);
private:
    int n, d;
    friend const Rational operator*(const Rational& lhs, const Rational& rhs);
};

現在我們來設計這個友元函數,它的功能是返回兩個Rational對象的乘積,我們有3種方案:

// 方案一
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
    Rational result(lhs.n*rhs.n, lhs.d*rhs.d);
    return result;
}

這個函數返回了result的引用,但是result是一個local對象,調用函數結束時,該對象會被銷毀,而它的引用也就毫無意義指向了未定義的對象。

// 方案二
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
    Rational* result=new Rational(lhs.n*rhs.n, lhs.d*rhs.d);
    return *result;
}

方案中result並不是一個local對象,而是一個從heap中動態分配得到的對象的指針,那麼這樣的問題是誰來負責delete這個指針呢,假如有這樣的表達式:

Rational w, x, y, z;
w = x*y*z;

這面的表達式其實調用了兩次operator*操作,也就是創建了兩次動態內存區域,但是沒有辦法取得它們的指針。

// 方案三
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
    static Rational result;
    result = Rational(lhs.n*rhs.n, lhs.d*rhs.d);
    return result;
}

這次是通過static對象使得result脫離函數後依然存在,但是就像所有的static對象設計一樣,這一個也立刻造成我們對多線程安全性的疑慮。而且如果有下面的代碼:

bool operator==(const Rational& lhs, const Rational& rhs);
Rational a, b, c, d;
if ((a*b) == (c*d)){}
else{}

上面代碼裡的if後的條件總是成立的,因為a*b返回的一個static對象的引用,而c*d返回的是同一個static對象的引用,所以永遠相等。

所以,我們最終的選擇是通過pass-by-value來返回新的對象。

const Rational operator*(const Rational& lhs, const Rational& rhs)
{
    return Rational(lhs.n*rhs.n, lhs.d*rhs.d);
}

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

首先是代碼的一致性(調用public成員時不用考慮是成員還是函數)。

其次封裝性,都寫成函數進行訪問可以提供以後修改訪問方法的可能性,而不影響使用方法。另外,public影響的是所有使用者,而protected影響的是所有繼承者,都影響巨大,所以都不建議聲明成員變量。

切記將成員變量聲明為private。這可賦予客戶訪問數據的一致性、可細微劃分訪問控制、允諾條件獲得保證,並提供class作者以充分的實現彈性。

protected並不比public更具封裝性。

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

想像有個class用來表示網頁浏覽器。這樣的class可能提供的眾多函數中,有一個用來清除下載元素的高速緩沖區(cache of downloaded elements)、清除訪問過的URLs的歷史記錄、以及移除系統中所有的cookies。

class WebBrowse
{
public:
    void clearCache();
    void clearHistroy();
    void removeCookies();
};

許多用戶想一個函數來執行整個動作,因些WebBrowse也提供這樣一個函數:

class WebBrowse
{
public:
    //...
    void clearEverything();//依次調用clearCache(),clearHistory(),removeCookies()
};

當然這個功能也可以由一個non-member函數來完成:

void clearEverything(WebBrowse& wb)
{
    wb.clearCache();
    wb.clearHistory();
    wb.removeCookies();
}

那麼哪一個比較好呢?

根據面向對象守則要求,數據以及操作的那些函數應該捆綁在一塊,這意味著member函數是較好的選擇。不幸的是這個建議不正確。面向對象要求數據應該盡可能被封裝。

member函數帶來的封閉性比non-member函數低,因為它並不增加“能夠訪問class內之private成份”的函數數量。

此外,提供non-member函數可以允許對WebBrowse相關機能能有較大的包裹彈性,而那最終導致較低的編譯相依度,增加WebBrowse的可延伸性。

如果我們把WebBrowse相關的功能設計為non-member函數,那麼可以將其功能相關的函數的聲明單獨放在一個頭文件中,然後在一個命名空間下。這時候如果我們相擴展相關的這些功能,只需要像其他功能函數一樣把聲明入在頭文件裡即可。

而這種切割方式並不適用於class成員函數,因為一個class必須整體定義,不能被切割為片斷。

請記住

寧可拿non-member non-friend函數替換member函數。這樣做可以增加封裝性、包裹彈性和機能擴充性。

條款24:若所有參數皆需要類型轉換,請為些采用non-member函數

當我們為一個有理數類的設計一個乘法操作符的重載函數,如果我們把它作為類的成員:

class Rational
{
public:
    //...
    const Rational operator*(const Rational &lhs);
};

當我們嘗試混合算術的時候,你會發現只有一半行得通:

Rational result, oneHalf;
result = oneHalf * 2;
result = 2 * oneHalf;

上面第二個賦值語句是錯誤的,因為它等價於result=2.operator*(oneHalf),這顯然是錯誤的。

而第2條語句等價於result=oneHalf.operator(2),它可以執行成功是因為2發生了隱式類型轉換,因為Rational有一個non-explicit的接受int形參的構造函數。

所以,如果我們想執行上面的操作,我們需要將這個重載函數設計為non-member函數。

const Rational operator*(const Rational& lhs, const Rational& rhs);

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

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

博客地址:http://www.cnblogs.com/ronny/ 轉載請注明出處!

1,當std::swap對你的類型效率不高時,提供一個swap成員函數,並確定這個函數不拋出異常。

2,如果你提供一個member swap,也該提供一個non-member swap來調用前者,對於classes(而非templates),也請特化std::swap。

3,調用swap時應針對std::swap使用using聲明式,然後調用swap並且不帶任何“命名空間資格修飾”。

4,為“用戶定義類型”進行std template全特化是好的,但千萬不要嘗試在std內加入某些對std而言全新的東西。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved