條款13: 以對象管理資源
1.為防止資源泄漏,使用RAII對象,它們在建構函式中獲得資源, 在解構函式中釋放資源
2.兩個常用的RAII classes: tr1::shared_ptr和auto_ptr,後者複製會使被複製物指向null
RAII (Resource Acquisition is Initialization): 資源取得時機便是初始化時機------以對象管理資源。
auto_ptr:不能讓多個auto_ptr指向相同對象,否則對象會刪除多次,未定義。 因此,若通過copy建構函式或copy assignment操作符複製它們,它們會變成Null, 而複製所得的指標將取得資源的唯一所有權。
1 Investment* createInvestment();
2 void f() {
3 std::auto_ptr<Investment> pInv1(createInvestment());
4 std::auto_ptr<Investment> pInv2(pInv1);//pInv2指向對象,pInv1為null
5 pInv1 = pInv2;//pInv1指向對象,pInv2為null
6 }
因為STL容器要求元素有“正常的”的複製行為,所以這些容器容不得auto_ptr.
auto_ptr替代方案: RCSP (reference-counting smart pointer)引用計數型智慧指標。追蹤共有多少對象指向某資源,無人指向時自動刪除該資源。無法打破環狀引用。
tr1::shared_ptr: 是個RCSP,複製行為正常,所以可用於STL容器
1 Investment* createInvestment();
2 void f() {
3 std::tr1::shared_ptr<Investment> pInv1(createInvestment());
4 std::tr1::shared_ptr<Investment> pInv2(pInv1);//pInv1和pInv2都指向同一對象
5 pInv1 = pInv2;//pInv1和pInv2都指向同一對象
6 }
auto_ptr和tr1::shared_ptr在解構函式中做delete而不是delete[]動作。因此在動態分配數組上用auto_ptr和tr1_share_ptr是個錯誤,但編譯仍能通過。
1 //都會用上錯誤的delete形式
2 atd::auto_ptr<std::string> aps(new std::string[10]);
3 std::tr1::shared_ptr<int> sp1(new int[1024]);
vecotr和string幾乎可以取代動態分配的數組,boost::scoped_array和boost::shared_array也提供需要的行為。
條款14: 在資源管理類中小學copying行為
1.禁制複製:條款06:copying操作聲明為private,private繼承Uncopyable類
2.引用計數: r1::shared_ptr允許指定刪除器(函數或函數對象),當引用次數為0時調用,是可有可無的第二參數
01 class Lock {
02 public:
03 explicit Lock(Mutex* pm)
04 :mutexPtr(pm, unlock) //以unlock為刪除器
05 {
06 lock(mutexPtr.get());
07 }//不要聲明解構函式,預設解構函式自動調用其他non-static成員變數的解構函式
08 private:
09 std::tr1::shared_ptr<Mutex> mutexPtr;
10 };
條款15: 在資源管理類中提供對原始資源的訪問
auto_ptr和tr1::shared_ptr都提供一個get成員函數,來執行顯示轉換,返回內部的原始指標。並且都重載了operator->和operator*操作符
也可以提供從智能帶原始指標的隱式轉換, operator orig_ptr_type() const { return orig_ptr; }
條款16: 成對使用new 和 delete時要採取相同形式
如果在new運算式中使用[],必須在相應的delete運算式中也使用[];如果在new運算式中不使用[],一定不要在相應的delete運算式中使用[]。
在class內有多個建構函式,並動態分配記憶體時,必須小心所有建構函式中使用相同形式的new將指標初始化。
盡量不要對數組做typedef操作。 使用vector,string等templates.
因為單一對象的記憶體布局一般而言不同於數組。多數編譯器對數組的記憶體布局通常含有數組大小的記錄,使用[]可讓編譯器知道是否有個數組大小的記錄。
條款17: 以獨立語句將newed對象置入智能指標
1 int priority();
2 void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);
3
4 processWidget(new Widget,priority());//不能通過編譯
5
6 processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());//能通過編譯,但異常不安全,若priority()在new和shared_ptr之間拋出異常
7
8 std::tr1::shared_ptr<Widget> pw(new Widget);
1 processWidget(pw, priority());// OK
條款18: 讓介面容易被正確使用,不易被誤用
1.保持介面與內建類型的行為一致性。
2.tr1::shared_ptr支援定製型刪除器(custom deleter)。這可防範cross-DLL problem,可被用來自動解鎖互斥鎖。
01 class Date {
02 public:
03 Date(int month, int day, int year);
04 };
05 Date d(30,3,1995); //
06
07 //匯入外覆類型(wrapper types)來區別年月日
08 struct Day {
09 explicit Day(int d):val(d) {}
10 int val;
11 };
12 struct Month {
13 explicit Month(int m):val(m) {}
14 int val;
15 };
16 struct Year {
17 explicit Year(int y):val(y) {}
18 int val;
19 };
20 class Date {
21 public:
22 Date(const Month& m, const Day& d, const Year& y);
23 };
24 Date d(30,3,1995); //error
25 Date d(Month(3), Day(30), Year(1995)); //OK,類型正確
26
27 class Month {
28 public:
29 static Month Jan() {return Month(1);}
30 static Month Feb() {return Feb(2);}
31 ...
32 static Month Dec() {return Month(12);}
33 private:
34 explicit Month(int m);
35 int month;
36 };
37 Date d(Month::Mar(), Day(30), Year(1995));
令factory函數返回智能指標
1 std::tr1::shared_ptr<Investment> createInvestment();
2 std::tr1::shared_ptr<Investment> pInv(0, getRidOfInvetment); //編譯失敗
3 std::tr1::shared_ptr<Investment> pInv(static_cats<Investment*>(0), getRidOfInvetment);//OK
cross-DLL problem: 對象在DLL中被new,在另一個DLL中被delete,導致運行期錯誤, tr1::shared_ptr沒有這個問題
條款19: 設計class猶如設計type
1.新type對象如何被建立和銷毀: 建構函式,解構函式,記憶體配置和釋放operator new, operator delete,operator new[], operator delete[]
2.對象初始化和賦值的差別。建構函式(引用和const成員),賦值操作符
3.新type對象被passed by value意味什麼,copy建構函式
4.什麼是新type的合法值
5.新type需要配合某個繼承圖系嗎?約束,virtual析構
6.新type需要什麼轉換。non-explicit建構函式, operator T2
7.什麼樣的操作符和函數對新type合理: member函數
8.什麼樣的標準函數應該駁回:需要聲明為private
9.誰該取用新type成員:public protected? private? friend? 嵌套?
10.什麼是新type的“未聲明介面”?它對效率,異常安全,資源運用提供何種保證
11.新type有多一般化: 還是應該定義class template?
12.真的需要一個新type嗎?如果只是定義新的derived class添加新功能,說不得定義一或多個non-menber函數或template更好? 條款23.
條款20: 寧以pass-by-reference-to-const替換pass-by-value
除了內建類型,STL迭代器和函數對象外,其他任何東西都盡量遵守本條款。
pass-by-value的代價: 形參的copy構造,形參基類(如果有)的copy建構函式,形參基類所有成員的copy建構函式,形參所有成員的copy建構函式, 以及所有建構函式對應的析構行為。
by-reference也可以避免對象切割(slicing)問題: 衍生類別對象by-value傳給基類。
條款21: 必須返回對象時,別妄想返回其reference
不要返回指標或引用指向一個local stack對象,或返回引用指向一個heap-allocated對象,或返回指標或引用指向一個local stack的對象而有可能同時需要多個這樣的對象。
01 class Rational {
02 public:
03 Rational(int numerator = 0;
04 int denominator = 1);
05 private:
06 int n, d;
07 friend const Rational operator* (const Rational& lhs,
08 const Rational& rhs);
09 };
10
11 const Rational& operator* (const Rational& lhs,
12 const Rational& rhs) {
13 Rational* result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
14 return *resual;
15 }
16 Rational w, x, y, z;
17 w = x * y * z; //兩次new,找不到指標delete,記憶體流失
18
19 const Rational& operator* (const Rational& lhs,
20 const Rational& rhs) {
21 static Rational result;
22 result = ...;
23 return resual;
24 }
25 //static造成多執行緒安全性問題
26 if ((w * x) == (y * z)){} //總是為真
27 //兩次operator*的確各自改變了static Rational的值,但由於返回的都是引用,調用端看到的永遠是static Rational的現值
28
29 inline const Rational& operator* (const Rational& lhs,
30 const Rational& rhs) {
31 return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
32 }//承受傳回值的構造和析構成本,而且編譯器可能實現最佳化
條款22: 將成員變數聲明為private
1.聲明為private,可賦予客戶訪問資料的一致性,可細微劃分存取控制,允諾約束條件獲得保證,並提供class作者以充分的實現彈性。
2.protected不比public更具封裝性
某些東西的封裝性與當其內容改變時可能造成的代碼的破壞量成反比。
public成員變數當我們移除時,所有使用它的客戶碼都被破壞,一個不可知的大量。
protected成員變數被移除時,所有使用它的derived class 都被破壞,也是一個不可知的大量。
條款23:寧以non-member、non-friend替換member函數
可增加封裝性、包裹彈性、和機能擴充性。
01 class WebBrowser {
02 public:
03 void clearCache();
04 void clearHistory();
05 void removeCookie();
06
07 void clearEverything();//調用clearCache,clearHistory,removeCookie
08 };
09
10 //或定義non-member non-friend函數哪個好?
11 void clearEverything(WebBrower& wb) {
12 wb.clearCache();
13 wb.clearHistory();
14 wb.removeCookie();
15 }
愈多東西被封裝,愈少人可以看到它。愈少人看到它,我們就有愈大的彈性與改變它。這就是我們首先推崇封裝的原因:使我們改變事物而隻影響有限客戶。
愈少代碼可以看到資料,愈多的資料被封裝,我們就愈能自由地改變對象資料。 因此,愈多函數訪問資料,資料的封裝性就愈低。
如果資料不是private,就毫無封裝性。能夠訪問private的只有member函數或friend函數。
因此non-member non-friend比member函數更有封裝性。
01 //WebBrowser如有大量便利函數,降低各函數的編譯相依的依存性
02 //使用時只需包含需要的標頭檔
03 //WebBrowser.h
04 namespace WebBrowserStuff {
05 class WebBrowser {...};
06 ... //核心機能,例如所有客戶都需要的non-member函數
07 }
08
09 //WebBrowserBookmark.h
10 namespace WebBrowserStuff {
11 ... //與書籤相關的便利函數
12 }
13
14 //WebBrowserBookmark.h
15 namespace WebBrowserStuff {
16 ... //與cookie相關的便利函數
17 }
條款24: 若所有參數需類型轉換, 請為此採用non-member函數
01 class Rational {
02 public:
03 Rational(int numerator = 0, int denominator = 1);
04 int numerator() const;
05 int denominator() const;
06 private:...
07 };
08
09 //operator*應該實現為member還是non-member?不考慮條款23
10 //先看看member實現
11 class Rational {
12 public:
13 const Rational operator*(const Rational& lhs, const Rational& rhs);
14 };
15 Rational oneEight(1, 8);
16 Rational oneHalf(1, 2);
17 Rational result;
18
19 result = oneHalf * 2; //ok, oneHalf.operator*(2); 隱式轉換
20 result = 2 * oneHalf; //error, 2.operator*(oneHalf);
21
22 class Rational {
23 ...
24 };
25 const Rational operator*(const Rational& lhs, const Rational& rhs) {
26 return Rational(lhs.numerator() * rhs.numerator(),
27 lhs.denominator() * rhs.denominator());
28 }
29 //各種OK
條款25: 考慮寫出一個不拋異常的swap函數
01 //pimpl (point to implementation)手法:以指標指向一個對象,內含真正資料
02 namespace std{
03 template<typename T>
04 void swap(T& a, T& b) {
05 T temp(a);
06 a = b;
07 b = temp;
08 }
09 }
10
11 class WidgetImpl {
12 public:
13 ...
14 private:
15 int a, b, c; //可能有許多資料
16 std::vector<double> v; //意味複製時間很長
17 };
18 class Widget {
19 public:
20 Widget(const Widget&);
21 Widget& operator=(const Widget& rhs) {
22 *pImpl = *rhs.pImpl;
23 }
24 private:
25 WidgetImpl* pImpl;
26 };
27
28 //置換兩個Widget對象,只需置換pImpl指標,預設的swap複製3和Widget對象,3個WidgetImpl對象,缺乏效率
29
30 namespace std {
31 template<>
32 void swap<Widget>(Widget& a, Widget& b) {
33 swap(a.pImpl, b.pImpl); //編譯失敗,訪問private
34 }
35 }//聲明friend????
36
37 //一種策略,與STL有一致性
38 class Widget {
39 public:
40 void swap(Widget& other) {
41 using std::swap; //曝光
42 swap(pImpl, other.pImpl); //尋找順序:本命名空間->global範圍->std::swap特化版(如有)->std::swap普通版
43 }
44 };
45 namespace std {
46 template<>
47 void swap<Widget>(Widget &a, Widget& b) {
48 a.swap(b);
49 }
50 }
51 //若兩個class都是template
52 template<typename T> class WidgetImpl{...};
53 template<typename T> class Widget{...};
54 //不能偏特化函數模板,C++只允許偏特化類模板
55 //不能再std命名空間中增加新的template,只能全特化
56 //一種方案
57 class WidgetStuff {
58 ... //模板化的WidgetImpl等等
59 template<typename T>
60 class Widget{...}; //同前,內含swap成員函數
61
62 template<typename T>
63 void swap(Widget<T>& a,Widget<T>& b){
64 a.swap(b);
65 }
66 };
總結:
如果swap的預設版本的效率不高(幾乎總意味你的class貨template 使用了pimpl手法):
1. 提供一個public swap成員函數,高效地置換你的類型的兩個對象值,且不該拋出異常(異常安全,條款29)
2.在你的class或template所在命名空間提供一個non-member swap,並令他調用上述swap成員函數
3.如果你正在編寫一個class(而非template) ,為你的class特化std::swap,並令他調用你的成員函數。
最後用using運算式讓std::swap在你的函數內曝光,然後不加任何namespace 修飾符,赤裸裸調用swap
【轉載】http://www.cnblogs.com/Atela/archive/2011/04/08/2008859.html