天天看點

《Effective C++》讀書筆記之中資源管理

                                                                                                                                By Ryui Liu 2011/11/28

所謂資源就是一旦用了它,将來必須還給系統,如果不這樣,糟糕的事情就會發送,如記憶體洩漏等等,c++中最常使用的資源就是動态記憶體,但記憶體隻是必須管理的資源之一,其他常見資源如檔案描述器(file descriptors)、互斥鎖、圖形界面中的字型和筆刷、資料庫連接配接以及網絡sockets等等。不論那種資源,當你不使用它時,必須将它還給系統。

一、以對象管理資源:

    假設我們有一個root class.

      Class Investment{.....};

然後通過一個工場函數供應特定的Investment繼承體系内的對象。

      Investment * createInvestment();

一般情況下我們使用方法都是: 

      Investment *pInv = createInvestment();

      .....

      delete pInv;

這樣看起來毫無問題,但是當有人需要變更程式,在“...”處加入流控制,如return 語句、goto語句,此時就可能導緻跑不到delete pInv語句。

為確定createInvestment傳回的資源總是被釋放,我們就需要把資源放進管理對象内,這些對象的析構函數會自動釋放那些資源。

std::auto_ptr<Investment> pInv(createInvestment()); //調用factory函數 建立管理對象pInv

auto_ptr是“類指針對象”,也就是所謂的智能指針,其析構函數自動對其所指對象調用delete。

到此為止,可以總結“以對象管理資源”的關鍵:

1、獲得資源後立刻放進管理對象(managing object)内:以上代碼中createInvestment傳回的資源被當作其管理者auto_ptr的初值。“以對象管理資源”的觀念常被稱為“資源取得時機便是初始化時機”(Resource Acquisition is Initiali RAII),資源管理對象也稱為RAII對象  

2、管理對象運用析構函數確定資源被釋放:不論控制流如何離開區塊,一旦對象被銷毀,其析構函數自然會被自動調用,于是資源被釋放。

   但是auto_ptr有個特性:受auto_ptr管理的資源必須絕對沒有一個以上的auto_ptr同時指向他。是以在調用copy構造函數或copy assignment操作符複制它們的時候,原管理器會變成null,而複制所得的指針将取得資源的唯一管理權。在STL中要求其元素有正常的複制行為,是以這些容器容不得auto_ptr,由此可見auto_ptr并非管理資源的神兵利器。

Std::auto_ptr<Investment> pInv1(pInv);//此時pInv被設為null;

pInv = pInv1; //此時pInv1被設為null;   

auto_ptr的替代方案是“引用計數型智慧指針(reference-counting smart pointer: RCSP)”。RCSP将持續追蹤有多少個對象指向某資源,當無人指向它是會自動删除該資源。TR1的tr1::share_ptr就是個RCSP,其複制行為比較正常。

二、資源管理類中copying行為:

    下面有一個類對類型為Mutex的互斥對象進行管理

Class Lock{

  Public: 

     explicit Lock(Mutex *pm):mutexptr(pm)

     {  lock(mutexPtr);}

     ~Lock(){ unlock(mutexPtr)}

  Private:

     Mutex * mutexPtr;  

}

此時調用構造函數時會鎖定互斥器,在區塊末尾時,自動接觸互斥器鎖定。

但是當Lock對象被複制時:Lock m11(&m);  Lock ml2(ml1); 這時會怎麼樣?

“當一個RAII對象被複制時,會發生什麼?”,我們有如下兩種選擇

1、禁止複制: 當允許RAII對象被複制時并不合理,此時需要禁止複制,即将coping操作聲明為私有。

2、對底層資源使用“引用計數法”:

上面的示例中就可以将Mutex* 變為std::tr1::shared_ptr<Mutex> mutexPtr; 并将tr1::shared_ptr的“删除器”指定為unlock函數,這樣在引用計數為0時,解除鎖定,而不是删除對象。構造函數改為:

 explicit Lock(Mutex *pm): mutexPtr(pm,unlock) { lock(mutexPtr.get());}

3、複制底部資源:當希望一份資源可以有任意數量的複件時,可以複制底部資源。例如某些标準字元串類型是由“指向heap記憶體”的指針構成。這種字元串對象還含有指針指向的一塊heap記憶體,當這樣的字元串被複制時,不論指針還是指針指向的記憶體都會被制作出一個複件。

4、轉移底部資源的擁有權:auto_ptr 就是這麼處理的。

   三、在資源管理類中提供對原始資源的通路:

   有時候需要繞過資料總管對象直接通路原始資源,例如一個函數int daysHeld(const Investment *pi)需要直接通路Investment指針,而現在隻有tr1::share_ptr<Investment>的對象。這時候就需要一個函數将RAII class對象轉換為其所包含的原始資源。

1、顯式轉換: tr1::shared_ptr和auto_ptr都提供了一個get成員函數,用來顯示轉換,他傳回智能指針内部的原始指針(的附件)。 Int daysHeld(pInv.get());

2、隐式轉換: 如果需要用原始指針的函數,智能指針可以直接通過操作符->和*進行引用,因為智能指針重載了取值操作符,運作隐式轉換為底部原始指針。這裡的隐式轉換是從類類型轉換為成員變量類型,不同于無explicit聲明的單參數構造函數将成員變量類型轉換為類類型

示例:FontHandle getFont();

      FontHandle releaseFont();

 資源管理類

      class Font{

         public:

           //C++中單參數構造函數若不聲明為explict,

           //在合适的場合可以産生隐式轉換:由成員變量類型轉換為類類型。 

           explicit Font(FontHandle fh):f(fh){}

           ~Font(){releaseFont(f);}

           FontHandle get()const {return f;}//顯示轉換函數

           //可以提供一個隐式轉換函數,由類類型轉換為成員變量類型

           Operator FontHandle()const{ return f;}

         Private:

            FontHandle f;

      }

四、以獨立語句将new操作符建立的對象置入智能指針,如果不這麼做,一旦異常被抛出,有可能導緻難以察覺的資源洩漏。例如:

       int priority();

       void processWidget(std::trl::shared_ptr<widget> pw, int priority);

調用上面函數時:

        processWidget(std::trl::shared_ptr<widget>(new Wigdet), priority());

上面的調用看似不會出現記憶體洩漏,實則不然:C++編譯器調用之前必須先處理實參,由于c++編譯器的不确定性,可能産生的操作順序為:

        1、執行"new Widget" 2、調用 priority 3、調用 trl::shared_ptr構造函數。

假如上述過程第2步産生異常,此時"new Widget"傳回的指針将會遺失,是以可能導緻資源洩漏。解決這類問題的辦法即是:以獨立語句将new操作符建立的對象置入智能指針.上述調用過程分拆為:

          std::trl::shared_ptr<widget>  pw(new  Widget);

          processWidget(pw, priority());    

    五、成對使用new和delete時要采用相同形式:即當你在new表達式中使用[],必須在相應的delete表達式中也使用[],如果你在new表達式中不使用[],一定不要在相應的delete表達式中使用[]。

繼續閱讀