Effective C++(14) 在資源管理類中小心copying行爲

問題聚焦:
    上一條款所告訴我們的智能指針,只適合與在堆中的資源,而並非所有資源都是在堆中的。
    這時候,我們可能需要建立自己的資源管理類,那麼建立自己的資源管理類時,需要注意什麼呢?。

在詳述這一章的主題之前,先回憶一下上一節所提到的一個名詞——RAII(Resource Acquisition Is Initialization)
含義就是:資源取得時機便是初始化時機。
如果上一節對這個觀念的理解還不是很深的話,那麼下面這個例子可以讓你更好地理解。

Demo 假設我們使用C API函數處理類型爲Mutex的互斥器對象,共有lock和unlock兩函數可用。
void lock(Mutex* pm);
void unlock(Mutex* pm);

爲了確保不會忘記將一個被鎖住的Mutex解鎖,你可能會希望簡歷一個class用來管理鎖。
這樣的class的基本結構由RAII守則支配,也就是在“資源構造期間獲得,在析構期間釋放”
class Lock {
public:
    explicit Lock(Mutex* pm) : mutexPtr(pm)
    { lock(mutexPtr);    }     // 獲得資源
    ~Lock() 
    { unlock(mutexPtr); }    // 釋放資源
};

// 客戶對Lock的正確用法符合RAII方式
Mutex m;         // 定義你需要的互斥器
....
{                         // 建立一個區塊用來定義critical section
    Lock ml(&m);      // 鎖定互斥器
    ......
}                        // 在區塊最末尾,自動接觸互斥器鎖定

上面的用法自然沒有什麼問題,那麼問題是什麼呢?——如果Lock對象被複制,會發生什麼事呢?就像下面這樣:
Lock ml1(&m);
Lock ml2(ml1);


一般化這個問題就是:當一個RAII對象被複制時, 會發生什麼事情呢?

大多數情況下,有兩種解決方法:
1 禁止複製:複製動作對RAII class並不合理。如果阻止複製操作,可以轉到:Effective C++(6) 如何拒絕編譯器的自動生成函數
2 對底層資源祭出“引用計數法:
    通常,只要內含一個tr::shared_ptr成員變量,就可以實現引用技術。代碼是下面這個樣子的:
    class Lock {
    public:
        explicit Lock(Mutex* pm) : mutexPtr(pm, unlock)  
        {
            lock(mutexPtr.get());
        }
    private:
        std::tr1LLshared_ptr<Mutex> mutexPtr;
    };

需要注意的一點是:tr1::shared_ptr的缺省行爲是“當引用次數爲0時,刪除其所指對象”,或許這並不是我們想要的行爲。幸運的是,tr1::shared_ptr並不是只能刪除對象,而是允許指定我們想要的動作,只需要在第二個參數上傳遞一個對象或函數對象,當引用次數爲0時,便被調用。
在本例中,傳遞的就是解鎖函數,而不是刪除所指對象。

遇到RAII的複製動作時,我們還可以有別的處理方式:
1 複製底部資源
    複製資源管理對象時,同時也可以複製其所包含的資源,因此進行的應該是“深拷貝”。
    例如:當一個對象包含一個指針指向一塊堆內存時,複製這個對象,同時其指針和指向的內存都會被複製出一個復件。
2 轉移底部資源的擁有權
    在某些場合下,你可能希望確保永遠只有一個RAII對象指向一個未加工的資源,就像auto_ptr的複製行爲。詳見: Effective C++(13) 用對象管理資源

小結:
  • 複製RAII對象必須一併複製它所管理的資源,所以資源的拷貝行爲決定RAII對象的拷貝行爲
  • 一般的複製行爲是:阻止拷貝行爲,使用引用計數法(tr1::shared_ptr)

參考資料:
《Effective C++ 3rd》


發佈了95 篇原創文章 · 獲贊 45 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章