effective C++筆記——資源管理

資源需要在使用完之後就歸還給系統,如果不這麼做,糟糕的是就會發生。C++程序中常用的資源就是動態內存分配(使用完不歸還會導致內存泄露),但是內存知識需要被管理的衆多資源之一。其他常見的資源還包括文件描述器、互斥鎖、圖形界面中的字型和筆刷、數據庫連接以及網絡sockets

以對象管理資源

通常動態分配內存後,需要歸還時都知道只需要delete就行了,但是這麼做的問題在於必須確保程序確實有執行到這一步,可能程序在執行delete操作之前就執行了return語句或者拋出了異常,則可能不會對內存進行釋放。因此單純的依賴函數會執行delete語句是行不通的。
  爲確保資源總能被釋放,可以將資源放入到對象內,當控制流離開當前函數,該對象的析構函數將自動釋放那些資源。許多資源被動態分配與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());	
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章