條款十四:在資源管理類中小心copying行為
首先來看乙個例子:
1 #include這個是模仿原書中的例子,做的乙個加鎖和解鎖的操作。2using
namespace
std;34
class
lock511
12 ~lock()
1316
17private:18
int *m_p;
19void
lock(int*pm)
2023
24void unlock(int *pm)
2528
};29
3031
intmain()
32
執行結果如下:
這符合預期,當m1獲得資源的時候,將之鎖住,而m1生命週期結束後,也將資源的鎖釋放。
注意到lock類中有乙個指標成員,那麼如果使用預設的析構函式、拷貝建構函式和賦值運算子,很可能會有嚴重的bug。
我們不妨在main函式中新增一句話,變成下面這樣:
1再次執行,可以看到結果:intmain()
2
可見,鎖被釋放了兩次,這就出問題了。原因是析構函式被呼叫了兩次,在main()函式中生成了兩個lock物件,分別是m1和m2,lock m2(m1)這句話使得m2.m_p = m1.m_p,這樣這兩個指標就指向了同一塊資源。根據後生成的物件先析構的原則,所以m2先被析構,呼叫他的析構函式,釋放資源鎖,但釋放的訊息並沒有通知到m1,所以m1在隨後析構函式中,也會釋放資源鎖。
如果這裡的釋放不是簡單的一句輸出,而是真的對記憶體進行操作的話,程式就會崩潰。
歸根到底,是程式使用了預設了拷貝建構函式造成的(當然,如果使用賦值運算的話,也會出現相同的bug),那麼解決方案就是圍繞如何正確擺平這個拷貝建構函式(和賦值運算子)。
第乙個方法,很簡單直觀,就是乾脆不讓程式設計師使用類似於lock m2(m1)這樣的語句,一用就報編譯錯。這可以通過自己寫乙個私有的拷貝建構函式和賦值運算子的宣告來解決。注意這裡只要寫宣告就行了(見條款6)。
1這樣編譯就不會通過了:class
lock28
9 ~lock()
1013
14private:15
int *m_p;
16void
lock(int*pm)
1720
21void unlock(int *pm)
2225
26private
:27 lock(const lock&);
28 lock& operator= (const lock&);
29 };
當然也可以像書上寫的一樣,寫乙個uncopyable的類,把它作為基類。在基類中把它的拷貝建構函式和賦值運算寫成私有的(為了防止生成基類的物件,但又想允許派生類生成物件,可以把建構函式和析構函式的修飾符變成protected)。
然後class lock: public uncopyable
也就ok了
第二個方法,就是使用shared_ptr來進行資源管理(見前乙個條款),但還有乙個問題,我想在生命週期結束後呼叫unlock的方法,其實shared_ptr裡面的刪除器可以幫到我們。
只要這樣子:
1這樣在lock的物件的生命週期結束後,就可以自動呼叫unlock了。class
lock25
private
:6 shared_ptrm_p;
7 }
在條款十三的基礎上,我改了一下自定義的shared_ptr,使之也支援刪除器的操作了,**如下:
1帶刪除器的mysharedptr#ifndef my_shared_ptr_h
2#define my_shared_ptr_h
34 #include 5
using
namespace
std;67
8 typedef void (*fp)();
910 template
11class
mysharedptr
1224
25public
:26 mysharedptr(t* p = null): ptr(p), count(new size_t(1
)),del(null){}
2728
//新增帶刪除器的建構函式
29 mysharedptr(t* p, fp fun): ptr(p), count(new size_t(1
)), del(fun){}
3031
32 mysharedptr(mysharedptr&p): ptr(p.ptr), count(p.count), del(p.del)
3336
3738
39 mysharedptr& operator= (mysharedptr&p)
4046
return *this;47
}4849 ~mysharedptr()
50
55reset();56}
5758 t& operator* () const
5962
63 t* operator-> () const
6467
68 t* get() const
6972
73void
reset()
7484}85
86bool unique() const
8790
91 size_t use_count() const
9295
9697 friend ostream& operator
<< (ostream& out, const mysharedptr&obj)
98102
103};
104105
106107
108109
#endif /* my_shared_ptr_h */
第三個方法是複製底部資源,就是將原來的淺拷貝轉換成深拷貝,需要自己顯示定義拷貝建構函式和賦值運算子。這個也在之前的條款說過了,放到這裡,其實就是在拷貝的時候對鎖的計數次數進行+1,析構函式裡就是對鎖的計數次數進行-1,如果減到0就去unlock(其實思想還是類似於shared_ptr進行資源管理)
第四個方法,是轉移底部資源的控制權,這就是auto_ptr幹的活了,在第二個方法中把shared_ptr換成auto_ptr就行了。
最後總結一下:
複製raii物件必須一併複製它所管理的資源,所以資源copying行為決定raii物件的copying行為
普遍而常見的raii class copying行為是:抑制copying,施行引用計數法(shared_ptr思想),或者是轉移底部資源(auto_ptr思想)
《effective C 》讀書筆記
1,c 關鍵字explicit c 中,乙個引數的 建構函式 或者除了第乙個引數外其餘引數都有預設值的多參建構函式 承擔了兩個角色。1 是個 構造器,2 是個預設且隱含的型別轉換操作符 所以,有時候在我們寫下如 aaa 這樣的 且恰好 的型別正好是aaa單引數構造器的引數型別,這時候 編譯器就自動呼...
Effective C 讀書筆記
一 讓自己習慣c 1 條款01 視c 為聯邦語言 c 的組成可分為四部分 1.c c 仍然以c語言為基礎。區塊 語句 預處理 內建資料型別 陣列 指標等都來自c。2.object oriented c c with classes所訴說的 classes 包括構造和析構 封裝 繼承 多型 virtu...
讀書筆記 Effective C
部分條款過於深奧,部分條款已了然於心,僅記錄當下所識所學 對於常量巨集定義,最好用const代替 define 對於函式巨集定義,最好用inline代替 define include ifdef ifndef仍被需要 內建物件記得手動初始化 使用成員初始列替換賦值操作 以local static替換...