天天看點

Effective C++條款14:資源管理之(在資源管理類中小心拷貝行為)一、“不希望拷貝”示範案例二、當RAII對象被拷貝時的做法四、引用計數法五、總結

前言

  • 在前面的文章中我們介紹了智能指針類來管理資源,但是對于某些資源,它們不是動态配置設定的(不是堆上的),是以智能指針就是不适合這種資源的管理了,是以你可能會設計自己的資源管理類

一、“不希望拷貝”示範案例

  • 假設我們使用C的API函數處理類型為Mutex的互斥器對象,其中有兩個函數如下:
void lock(Mutex* pm);   //鎖定pm所指的互斥器
void unlock(Mutex* pm); //将互斥器解鎖
           
  • 現在我們設計一個類,來管理Mutex對象,并且使用了上一篇文章的RAII規則,即“在構造期間獲得資源,在析構期間釋放資源”,原型如下:
class Lock
{
public:
    //獲得資源
    explicit Lock(Mutex* pm) :mutexPtr(pm) {
        lock(mutexPtr);
    }

    //釋放資源
    ~Lock() { unlock(mutexPtr); }
private:
    Mutex *mutexPtr;
};
           
  • 如果用下面的函數調用Lock對象是正确的
Mutex m;

void f()
{
    //管理Mutex對象
    Lock ml(&m);
    //...

}//函數結束後,自動調用ml的析構函數解鎖
           
  • 但是如果發生下面的拷貝行為,那麼程式就會産生意想不到的後果:
    • 因為m2拷貝時會調用lock函數對Mutex對象進行加鎖,但是m對象此時處于使用狀态,因為其會阻塞等待,等待m1釋放互斥量,但是m1此時也無法向下執行,是以這個程式就産生死鎖了
    • 是以對于我們設計的Lock這個類,我們不希望其有拷貝行為
Mutex m;

void f()
{
    Lock m1(&m);//管理Mutex對象
    //...
    
    Lock m2(m1); //拷貝

}//函數結束後,自動調用ml的析構函數解鎖
           

二、當RAII對象被拷貝時的做法

  • 當RAII對象被複制時,我們通常有以下兩種做法:
    • 禁止複制:許多時候,RAII對象被複制時不合理的,就像上面我們那個Mutex一樣。是以我們可以根據條款6的做法,将拷貝操作聲明為private或者讓其繼承于Uncopyable這樣的基類
    • 對底層資源做出“引用計數法”:我們可以參考shared_ptr類那樣的機制,對資源設計“引用計數法”

四、引用計數法

  • 如果我們希望對管理的資源進行引用計數法,那麼可以重新設計上面的Lock類:
    • 使用shared_ptr管理Mutex對象
    • 當引用計數為0時,我們希望做的是解鎖資源而不是删除資源,但是我們的shared_ptr提供一個“删除器”,删除器可以是一個函數或函數對象,是以下面我們為mutexPtr提供了unlokc函數,當引用計數為0時就會調用unlock函數,将Mutex對象解鎖
    • 不再設計析構函數:因為預設的析構函數會調用其非靜态成員的析構函數,是以預設的析構函數會在互斥器的引用計數為0時自動調用shared_ptr的删除器(此處為unlock函數)
class Lock
{
public:
    explicit Lock(Mutex* pm) 
        :mutexPtr(pm,unlock) //提供unlock函數為删除器
    {
        lock(mutexPtr.get()); //條款15會介紹get函數
    }
private:
    std::shared_ptr<Mutex> mutexPtr;
};
           

五、總結

  • 指派RAII對象必須一并複制它所管理的資源,是以資源的拷貝行為決定RAII對象的拷貝行為
  • 普遍而常見的RAII類拷貝行為是:抑制拷貝、試行引用計數法。不過其他行為也都可能被實作

繼續閱讀