Effective C++ 條款14 在資源管理類中小心copying行為
1. 條款13中講到“資源取得的時機便是初始化時機”並由此引出“以物件管理資源”的概念,資源會在不需要的時刻被銷燬。通常情況下使用std中的auto_prt(智慧指標)和tr1::shared_ptr(引數智慧指標)作為資源管理的物件,這種做法通常都十分有效。但是,auto_ptr和shared_ptr只能管理基於堆(heap-based)的資源,而非heap-base的資源往往不合適。因此有必要建立自己的資源管理類。
2.在C API中處理Mutex的互斥物件,有lock何unlock兩個函式可用:
void lock(Mutex* pm); // 鎖定pm指向的互斥量 void unlock(Mutex* pm); // pm指向的互斥量解鎖
假設我們寫了Lock類來管理鎖。
class Mutex { public: Mutex() : Count( 0 ) {} public: int Count; }; void lock( Mutex* pm ) { pm->Count++; } void unlock( Mutex* pm ) { pm->Count--; } class Lock { public: explicit Lock( Mutex* pm ) : mutexPtr( pm ) { lock( mutexPtr ); } // 將mutexPtr指向的互斥變數加鎖 ~Lock() { unlock( mutexPtr ); } // 將mutexPtr指向的互斥變數解鎖 private: Mutex* mutexPtr; };
上面程式碼滿足RAII(Resource Acquisition is Initialization)原則即,資源在獲取時既是初始化時,失去時既是清理時。 想象下面的場景時,程式的輸出結果是什麼。
Mutex m; cout << "Mutex is " << m.Count << endl; Lock m1(&m); cout << "Mutex is " << m.Count << endl; Lock m2(m1); cout << "Mutex is " << m.Count << endl; m1.~Lock(); cout << "Mutex is " << m.Count << endl;
Mutex is 0
Mutex is 1
Mutex is 1
Mutex is 0
這是為什麼呢?前兩個0和1輸出無可厚非,第三個的輸出為拿m1作為例項物件去賦值給m2,操作物件為m1,不會直接影響m;第四個互斥量m的管理者m1被銷燬了,那麼m也就被解鎖了。
在上面的例子中,m的值不斷被變更,顯然,這種資源的管理的方式是不合理的。
可能的解決方法:
1.禁止複製。禁止複製的做法具體的可參照條款6的說明。
class UnCopyable {
public:
UnCopyable(){}
private:
UnCopyable(const UnCopyable& ths) {
}
};
class Lock:private UnCopyable {
...
}
2.使用引用計數智慧指標:tr1::shared_ptr。
從條款13我們已經知道引用計數智慧指標會跟蹤使用該資源的所有物件數,計數為0時,資源會被刪除。注意,這裡刪除互斥量m不是我們所期待的,我們期待是解鎖互斥量
幸運的是tr1::shared_ptr允許自定義所謂的“刪除”動作,該動作是在計數為0時執行的。於是類Lock可以是下面的樣子。
class Lock
{
public:
explicit Lock(Mutex* pm):mutexPtr(pm,unlock)
{lock(mutexPtr.get());} // 將mutexPtr指向的互斥變數加鎖
private :
shared_ptr<Mutex> mutexPtr;
};
有沒有發覺貌似少了點東西?對,解構函式沒有了。因為share_ptr會幫你完成這一工作。
3.複製管理物件時也複製所管理的資源。
請回頭想一個問題:為什麼需要自己的資源管理類?那麼,可能的理由是當不需要某個資源時,資源能被正常釋放(刪除,其他動作)。資源存在多個復件並不可怕,可怕的是復件在該銷燬的時候卻沒有銷燬。也就是,管理物件與所管理的資源要一一對應。為了保證這種對應關係,在複製管理物件時也複製所管理的資源。
4.轉移資源的管理權。
在某些特殊場合下,你可能希望資源只被一個物件擁有,也就是管理物件在copying時要進行資源所有權的轉移。從條款13中講到的auto_ptr可以完美的實現這個需求。
■ 總結 1.複製管理物件時,請一併複製物件所管理的資源,資源的copy行為決定了管理物件的copy行為 2.普遍的RAII class的copy行為是抑制複製,使用引用計數