effective C++筆記——資源管理
文章目錄
資源需要在使用完之後就歸還給系統,如果不這麼做,糟糕的是就會發生。C++程式中常用的資源就是動態記憶體分配(使用完不歸還會導致記憶體洩露),但是記憶體知識需要被管理的眾多資源之一。其他常見的資源還包括檔案描述器、互斥鎖、圖形介面中的字型和筆刷、資料庫連線以及網路sockets。
以物件管理資源
通常動態分配記憶體後,需要歸還時都知道只需要delete就行了,但是這麼做的問題在於必須確保程式確實有執行到這一步,可能程式在執行delete操作之前就執行了return語句或者丟擲了異常
為確保資源總能被釋放,可以將資源放入到物件內,當控制流離開當前函式,該物件的解構函式將自動釋放那些資源。許多資源被動態分配與heap內後被用於單一區塊或函式內。他們應該在控制流離開那個區塊或是函式時被釋放,標準庫中提供了智慧指標正是針對這種情況設計的產品,關於計數型智慧指標的相關知識之前也在別處看過了。
在資源類中小心copying行為
並非所有的資源都是heap-based(在堆中分配的物件),最那種資源而言,智慧指標往往不適合作為資源管理者,偶爾需要自己建立資源管理類。
假設使用C API函式處理型別為Mutex的互斥器物件,共有lock和unlock函式使用,並建立一個資源管理類:
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;
};
在正常呼叫的情況下沒有什麼問題,物件在區塊或函式中被建立並管理某一個互斥器物件,在區塊或函式結束時自動呼叫解構函式接觸鎖定。但是問題在於如果該Lock物件被複制,會發生什麼事?
這是建立資源管理類的時候一定要想到的問題,通常會選擇一下幾種可能:
1.禁止複製
2.對底層資源引用計數。有時會希望保有資源,直到它的最後一個使用者被銷燬,這種情況下應該將該資源的“被引用數”遞增。tr1::shared_ptr便是如此。所以通常只要在資源管理類中含有一個tr1::shared_ptr成員變數,就可以實現引用計數複製的行為。例如以上程式碼中將指標型別有Mutex改為tr1::shared_ptr<Mutex>,但是需要注意的是shared_ptr的預設行為是在引用計數為0的時候刪除其所指物,這並不一定是希望的操作,幸運的是shared_ptr是允許指定“刪除器”,那是一個函式或函式物件,當引用計數為0時便被呼叫:
class Lock{
public:
explicit Lock(Mutex* pm):mutexptr(pm,unlock){ //建構函式初始指標,指定刪除器
lock(mutexptr.get()); //並鎖定所指向的互斥器
}
//不需要再宣告解構函式,class的解構函式會自動呼叫非靜態成員的解構函式,
//而mutexptr的解構函式會在引用計數為0時自動呼叫刪除器
private:
std::tr1::shared_ptr<Mutex>* mutexptr;
};
.
3.複製底部資源。就是深拷貝,即複製物件的時候複製其所包裹的所有資源。
4.轉移底部資源的擁有權。有時可能會需要確保永遠只能有一個資源管理類物件指向一個未加工資源,即使在被複制的時候,也應該保證資源的擁有權從被複制物轉移到目標物。
在資源管理類中提供對原始資源的訪問
. 資源管理類可以幫助處理和資源之間的互動,避免資源洩露帶來的問題。但是直接處理資源的情況也是時常發生的,需要繞過資源管理器物件直接訪問原始資源。
比如使用智慧指標指向某個物件,外部函式需要操作這個資源時需要的引數是該資源本身的型別引數,而不是這個指標物件,這時就需要一個函式可以將資源管理物件進行型別轉換(轉換為內含的原始資源)了。
幾乎所有的智慧指標都提供一個get()成員函式,用來執行顯式轉換,也就是它會返回智慧指標內部的原始指標(的復件)。
幾乎所有的智慧指標也過載了取值操作符(->和*),他們允許隱式轉換至底部原始指標。
對原始資源的訪問可能經由顯式轉換或隱式轉換,一般而言顯式轉換比較安全,但隱式轉換對客戶比較方便。
成對使用new和delete要使用相同型式
也就是new和delete對應,new[] 和 delete[]對應。
以獨立語句將newed物件置入指標
. 主要是為了避免型別轉換失敗以及異常發生時對資源管理失效的麻煩。
例如,有一個表示處理程式優先順序的函式和一個接受資源物件對優先順序做處理的函式:
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw,int p);
. 現在呼叫第二個函式的形式如下:
processWidget(new Widget,priority());
. 以上呼叫將無法通過編譯,tr1::shared_ptr構造喊蘇需要一個原始指標,且該函式是一個explicit的,無法進行隱式轉換,因此需要直接寫出:
processWidget(std::tr1::shared_ptr<Widget>(new Widget),priority());
. 以上程式碼雖然能通過編譯,但是還是存在資源洩露的問題,這是因為對編譯器來說,產出一個processWidget的呼叫碼前,必須先核算即將被傳遞的各個實參,因此對以上程式碼來說需要先做一下三步:
- 呼叫priority()
- 執行new Widget
- 呼叫tr1::shared_ptr建構函式
. 但是C++以什麼次序來完成這三步呢,可以肯定的是new操作在呼叫建構函式前,但是呼叫priority函式則可能排在第一第二或第三的步。假設在第二步呼叫了priority函式,並且發生了異常,則new產生的指標將會被遺失,因為它尚未被置入智慧指標內,這就可能導致資源洩露了。
避免這種問題的方法就是使用分離語句,將建立物件、建立指標和傳參的步驟分開執行:
std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw,priority());