1,好的接口很容易被正確使用,不容易被誤用。你應該在你的所有接口中努力達成這些性質。
2,“促進正使用”的辦法包括接口的一致性,以及與內置類型的行為兼容。
3,“阻止誤用”的辦法包括建立新類型,限制類型上的操作,束縛對象值,以及消除客戶的資源管理責任。
4,shared_ptr支持定制型刪除器。這可以防范DLL問題,可以用來自動解除互斥鎖。
博客地址: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以便為既有的類添加機能,那麼說不定單純定義一個或多個非成員函數或模板,更能夠達到目標。
pass-by-value會造成較多的構造函數與析構函數的開銷,並且在將派生類傳遞給基類接口的時候會發生類的切割問題。
上面的規則並不適用於內置類型,以及STL的迭代器和函數對象。對它們而言,pass-by-vaule往往比較適當。
絕對不要返回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); }
首先是代碼的一致性(調用public成員時不用考慮是成員還是函數)。
其次封裝性,都寫成函數進行訪問可以提供以後修改訪問方法的可能性,而不影響使用方法。另外,public影響的是所有使用者,而protected影響的是所有繼承者,都影響巨大,所以都不建議聲明成員變量。
切記將成員變量聲明為private。這可賦予客戶訪問數據的一致性、可細微劃分訪問控制、允諾條件獲得保證,並提供class作者以充分的實現彈性。
protected並不比public更具封裝性。
想像有個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函數。這樣做可以增加封裝性、包裹彈性和機能擴充性。
當我們為一個有理數類的設計一個乘法操作符的重載函數,如果我們把它作為類的成員:
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。
博客地址: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而言全新的東西。