我發現,第一遍讀書總是不能清楚的認識到問題的本質,我們還需要回過頭去總結,因此,我決定以後每讀一部分的書,寫一點的總結,一方面可以讓自己整理知識,另一方面方便以後對比思想。
資源管理
前言
資源洩露就是程序中常見的事情,這一章主要就是告訴我們如何去消除資源管理問題,值得注意的是,這裡的資源不僅是指動態分配內存,其他常見的資源還包括數據庫的連接,網絡sockets,還有互斥鎖等,我們在用完這些資源之後,必須將他們還給系統。尤其是發生異常、程序員維護軟件時,會產生這種問題。
條款13:以對象管理資源
先看段代碼:
[cpp]
void test()
{
int *t = new int;//獲取資源
...
delete t;//釋放資源
}
如果我們在"..."中產生了異常,或者存在return,就可能導致資源的洩露。也許你會說,我很謹慎,我不會讓自己的代碼出現這種問題。好吧,那如果這是一個項目,之後這段代碼可能會被維護人員修改,他可能會在“...”中加入異常,或者return。也許,你又會說,你會將這段代碼需要注意的地方寫入文檔,可是我覺得我們應該把自己當成“客戶”(這裡指使用這段代碼和維護代碼的人),我們需要為自己著想,如果我們能做好,就不應該甩手把麻煩的事情留給別人。
為防止以上的現象,我們可以將資源放入對象中,依賴c++的“析構函數自動調用機制”確保資源被釋放。
[cpp]
void test
{
auto_ptr<int> t (new int);//注意初始化方式
...
}//auto_ptr的析構函數自動delete t
auto_ptr是個只能指針,析構函數會自動對其所指的對象調用delete。
書中推薦使用auto_ptr和shared_ptr(我在這裡就不在介紹這個,有興趣的可以自己google),但是我們也可以自己寫資源管理類,但是其中涉及到需要考慮的細節,將在後面的條款討論。
條款14:在資源管理類中小心copying行為
像上一條款說的,有的時候auto_ptr不適合資源管理類,我們需要自己創建資源管理類。
[cpp]
void lock(Mutex* pm);//鎖定pm所指的互斥器
void unlock(Mutex* pm);//將互斥器接觸鎖定
class Lock
{
public:
explicit Lock(Mutex* pm):mutexPtr(pm)
{
lock(mutexPtr);//獲得資源
}
~Lock()
{
unlock(mutexPtr);//釋放資源
}
private:
Mutex *mutexPtr;
};
如上,我們將會為Mutex自動釋放資源,但是我們需要考慮的一個問題就是,如果Lock發生了復制,會發生什麼?
Lock m1(&m);//鎖定
Lock m2(m1);//復制
導致的惡果就是 將會對同一個資源釋放兩次。
那我們面對這樣的問題,該如何選擇:
1.禁止復制
當資源管理類對象被復制時,如果不合理,我們就會選擇禁止復制。可以將copying操作聲明為private而不實現它,達到禁止的目的。(條款6中詳細說了)
2.對底層資源祭出“引用計數法”
用一個變量保存引用個數,當引用個數為0時,才銷毀它。這就是shared_ptr的做法(此外,shared_ptr允許指定刪除器,當引用次數為0時,便會調用這個刪除器)
3.復制底部資源
也就是“深度拷貝”,不僅指針會被制作出一個復件,而且會創建一個新的內存。
4.轉移底部資源的擁有權
簡單點說,就是擁有權會從被復制的對象轉移到復制的對象,而被復制的對象失去所有權,這是auto_ptr所實現的。
比如
[cpp
auto_ptr<int> t (new int);
auto_ptr<int> a = t;//所有權從t轉向a,t將會指向NULL
條款15:在資源管理類中提供對原始資源的訪問
我們需要面對的一個問題就是,現實中很多的API的參數直接涉及到資源,而我們把資源放在資源管理類中,因此我們需要提供對原始資源的訪問。
[cpp]
auto_ptr<int> t(new int);
int test(int *t);//直接涉及資源
[cpp] view plaincopy
<pre></pre><span style="font-size:18px"><span style="font-family:Microsoft YaHei"></span></span>
<pre></pre>
<pre></pre>
<pre></pre>
<pre></pre>
<pre></pre>
我們有兩種方法來達到目標:顯示轉換和隱式轉換
1.顯示轉換
通常的做法就是提供一個get()成員函數,返回資源。auto_ptr和shared_ptr就是這麼做。
但是這麼做的後果,就是會導致頻頻使用get()。
2.隱式轉換
提供隱式轉換函數
[cpp]
class Font {
public:
...
operator FontHandle() const { return f; } // 進行隱式轉換的函數
...
};
但是會導致錯誤問題的增多:
[cpp]
<pre></pre>
<pre></pre>
<pre></pre>
<pre></pre>
<pre></pre>
FontHandle f2 = f1;//f1是一個Font對象
本來我只是想復制一個Font,但是不小心寫成了FontHandle,這時候f2會隱式轉換成FontHandle,再復制
我的個人意見是,根據需要選擇方法。
條款16:成對使用new和delete時采用相同形式
這個沒什麼好說的,簡單的說,就是如果你在new表達式中使用[],必須在相應的delete表達式中也使用[]。如果你在new表達式中不使用[],一定不要在相應的delete表達式中使用[] 。
特別要注意的就是使用typedef時,
[cpp]
typedef std::string AddressLines[4];
std::string* pal = new AddressLines; //注意,“new AddressLines”返回一個string*,就像“new string[4]”一樣
delete pal; //行為未定義
delete [] pal; //很好
條款17:以獨立語句將new對象置入智能指針
[cpp]
processWidget(std::tr1::shared_ptr<Widget> pw(new Widget), int priority);
可能會導致資源的洩露,為什麼呢?
因為語序的問題!
這個語句總共完成了3件事情:
調用 priority 。
執行 “new Widget” 。
調用 tr1::shared_ptr 的構造函數。
他們的操作順序有很大的彈性,如果按以下順序執行:
1. 執行 “ new Widget ” .
2. 調用 priority 。
3. 調用 tr1::shared_ptr 的構造函數。
[cpp]
std::tr1::shared_ptr<Widget> pw(new Widget); // 在一個單獨的語句中創建 Widget
// 並存入一個智能指針
processWidget(pw, priority()); // 這樣調用就不會洩漏了。、
總結:這一章就這麼結束了,感覺學到蠻多的,我發現在總結的過程中,將作者的話轉換出來,也能夠收獲一些東西,以後要寫寫。