天天看點

Effective C++ 讀書筆記(3.資源管理)條款13:以對象管理資源條款14:在資源管理類中小心copying行為條款15:在資源管理類中提供對原始資源的通路條款16 成對使用new和delete時要采用相同的形式條款17:以獨立語句将newed對象置入智能指針

條款13:以對象管理資源

假設我們使用一個類來表示投資行為,各式各樣的投資類型繼承自一個root class Investment:

通過一個工廠函數供應我們的Investment對象:

調用createInvestment的對象後,有責任删除之。用一個f函數:

void f()
{	
	Inverstment * pInv=CreateInvestment();
	...
	delete pInv;
}
           

但是在發生如下情況的時候,f可能無法删除從CreateInvestment獲得的對象。

  1. …區域中一句return
  2. 位于某個循環内,一旦break或者continue就無法執行delete
  3. …區域内抛出異常
  • 為了確定CreateInvestment傳回的資源總是被釋放,我們需要将資源放進對象内,當控制流離開f後自動調用析構函數自動釋放資源。
  • 例1智能指針:auto_ptr(類指針對象)

    其析構函數自動對其所指的對象調用delete。

    void f()
    {
    	std::auto_ptr<Investment>PInv(CreateInvestment());//調用factory函數,使用Pinv
    	...
    }//f函數結束時經由auto_ptr的析構函數自動删除pInv
               

    獲得資源後立即放入管理對象;

    管理對象運用析構函數確定資源被釋放;

    注意:和指針一樣,不要讓多個auto_ptr指向同一個資源位址,這樣釋放資源是就會反複釋放同一個對象,導緻未定義行為。為了預防這個問題,auto_ptrs有一個不尋常的性質:如果通過copy構造函數或者copy assignment操作符複制他們,那麼他們就會變成null,而複制之後所得的指針就會獲得資源的唯一擁有權。

    std::auto_ptr<Investment>pInv1(CreateInvestment());
    std::auto_ptr<Investment>pInv2(pInv1);//pInv1變為null
    pInv1=pInv2;//pInv2變為null
               
  • 一種auto_ptr的替代方案就是使用“引用計數型智慧指針”(reference-counting smart pointer;RSCP)(tr1::shared_ptr):持續追蹤有多少指針指向某個對象,當沒有指針指向該對象時自動删除該資源。用法和auto_ptr差不多,但是複制操作可行。
  • auto_ptr和RSCP都是delete而不是delete[],是以不适用于指向數組。

條款14:在資源管理類中小心copying行為

上一條款描述了auto_ptr和tr1::shared_ptr如何将這個觀念表現在heap-based資源上。然而并非所有資源都在heap_based上。

  • 例如:我們使用c API函數處理類型為Mutex的互斥器對象,有lock和unlock兩個函數可以用。

    (Application Programming Interface,應用程式接口)是一些預先定義的函數,或指軟體系統不同組成部分銜接的約定。 用來提供應用程式與開發人員基于某軟體或硬體得以通路的一組例程,而又無需通路源碼,或了解内部工作機制的細節。

    void lock(Mutex* pm);//鎖定pm所指的互斥器
    void unlock(Mutex* pm);//将互斥器解出鎖定
               
    為了確定我麼你不會忘記将一個被鎖住的Mutex解鎖,我們希望建立一個class來管理機鎖。這個class的基本結構由RAII,資源在構造期間獲得,在析構期間釋放。
    class Lock{
    public:
    	explicit Lock(Mutex * pm):Mutexptr(pm){lock(MutexPtr);}
    	~Lock(){unlock(mutexPtr);}
    private:
    	Mutex* MutexPtr;
    };
    //客戶對Lock的用法複合RAII方式
    Mutex m;//定義所需要的互斥器
    ...
    {//定義一個區塊用來定義cirtical section(臨界區,一次隻能讓一個程序通路的代碼)
    	Lock m1(&m);//鎖定互斥器
    	...//執行cirtical section内的操作
    }//在區塊的最末尾,自動解除互斥器的鎖定
               
  • 但當一個RAII對象被複制,會發生什麼事情?
  1. 禁止複制

    條款6中用一個抽象基類,将copy和copy assignment都放在pirvate中再用派生類繼承,這樣就會禁止複制行為。

  2. 對底層資源祭出“引用計數法”(reference-count)

    有時候我們希望保有資源,直到他的最後一個使用者被銷毀,這種情況下複制RAII對象時,應該将資源的被引用數遞增,shared_ptr:

    如果Lock打算使用Reference counting,他可以改變mutexPtr的類型,将它從Mutex*改為std::shared_ptr < Mutex >,但是shared_ptr在引用數為零的時候執行的操作是删除對象,而Lock需要的是解鎖操作,是以我們可以更改shared_ptr的删除器(第二個可有可無的參數)。

    class Lock{
    public:
    	explicit Lock(Mutex* pm):mutesPtr(pm,unlock)//第二個函數
    	{
    		lock(mutexPtr.get());
    	}
    private:
    	std::tr1::shared_ptr<Mutex> mutesPtr;
    };
               

條款15:在資源管理類中提供對原始資源的通路

資源管理類,排除洩漏。但許多APIs直接指涉資源,需要提供對原始資源的通路。

條款13中,使用auto_ptr和tr1::shared_ptr儲存factory函數如createInvestment的調用結果。

有時候你希望處理某個函數如Investment對象,

int daysHeld(const Investmetn*pi);//傳回投資天數

int days=dayHeld(Pinv)//不能通過編譯
           

類型不比對

  • 這時候你就需要一個函數可以将RAII class對象轉化為其所内含原始資源。
  1. 顯示轉換:

    智能指針提供一個get()成員函數,用來執行顯示轉換,也就是會傳回智能指針内部的原始指針。

  2. 隐式轉換:

    operator->和operator*都可以隐式的轉換至底部的原始指針。

注意

  1. APIs往往需要通路原始資源,是以每一個PAII class都需要提供一個“取得其所管理之資源”的辦法。
  2. 對原始資源的通路可能經由顯示轉換(智能指針.get();用顯示函數傳回資源)或者隐式轉換(->或者*,隐式函數傳回資源),一般而言顯示轉換比較安全。但隐式轉換對客戶比較友善。 、

條款16 成對使用new和delete時要采用相同的形式

  • new配delete
  • new[]配delete[]
  • 對數組使用typedef(不建議)需要delete[]

條款17:以獨立語句将newed對象置入智能指針

假設我們有一個函數來揭示處理函數的優先權,另一個函數用來在某動态配置設定所得的Widget上進行某些帶有優先權的處理

int pirority();
void processWiget(std::tr1::shared_ptr<widget>pw,int priority);
           

用對象管理資源,調用processWidget

上述代碼将不能通過,由于shared_ptr構造函數需要一個原始的指針,而其構造函數又是explicit,不允許隐式轉換,是以我們需要顯示地将原始指針轉換為shared_ptr。

  • 接下來我們考慮這個函數各個參數的處理過程:
  1. priority()
  2. new Widget
  3. tr1::shared_ptr構造函數

    我們知道new Wiget一定在shared_ptr構造函數之前調用,但priority()函數有可能在任意順序被調用,一旦在中間而且出現異常,那麼new Widget所獲得的指針将會遺失:“資源被建立”到“資源被轉換為資源管理對象”過程中發生異常

    避免這一類問題的方法就是使用分離語句,先将new Widget放入智能指針,再将指針指針傳遞給函數參數。

繼續閱讀