c++ 代理類與句柄類實現思想

指針是 C 與其他語言區別的重要特徵之一,在 C++ 中,指針也被廣泛運用,我們通過指針實現多態。然而,衆所周知,指針的使用必須小心,否則很容易造成內存泄漏 Memory Leak。當我們有幾個指針指向同一個對象時有其應該注意,關於何時釋放這個對象:
(1) 如果釋放的太早,那麼其它的指針仍然指向這片內存,如果再使用它會造成未定義行爲。
(2) 如果一直不釋放可能會丟失最後一個指向這個對象的指針 導致內存無法被釋放。

用 C++ 的方法來解決這種問題就是建立一個類來包含需要管理的指針 ,由於這些類往往與被管理者相綁定 ,所以它們被稱爲 handel 類 ,人們再建立這種 handel 類的同時一般保留了它包含的指針的種種特性,所以也稱這種類爲 智能指針 smart pointer。最簡單的 handel 這種 handel 只是一個包含了對象指針的容器,當對象的指針綁定到 handel 上後 ,就不需要手動delete 對象 ,handel 類負責對象的析構(在 handel 離開作用域時)。stl 中 的 auto_ptr 就是這種例子。

下面給出一個簡單的 AutoPtr 實現:
/*
* File : auto_prt.h
* Discription : 智能指針 
* 指針存儲的最簡單策略 , 將指針存入對象中當對象被析構指針自動被delete
* AutoPtr 和 指針是 一對一的關係
* Usage : AutoPtr< ClassType > ap_type = AutoPtr< ClassType >( new ClassType() );
* ap_type->method();
*/
#ifndef _PATTERN_AUTOPTR_H
#define _PATTERN_AUTOPTR_H


#include "../common/common.h"


namespace c_toto
{
template<class T> class AutoPtr
{
public:
AutoPtr( AutoPtr<T> & ap ): ptr( ap.ptr ) {   ap.ptr = NULL; }


AutoPtr<T> & operator=( AutoPtr<T> & ap )
{
if( ptr )
{  
delete ptr;
ptr = NULL;
}
ptr = ap.ptr;
ap.ptr = NULL;
}


public:
AutoPtr( T * p = NULL ) : ptr( p ) {}
~AutoPtr() { delete ptr; }


bool Valid()
{
if( ptr )return true;
return false;
}


T & operator*() { return *ptr; }
const T & operator*() const { return *ptr; }
T * operator->() { return ptr; }
const T * operator->() const  { return ptr; }
private:
T * ptr;
};


}; // namespace c_toto


#endif // #ifndef _PATTERN_AUTOPTR_H


    需要注意的是,由於 AutoPtr 和指針是一對一的關係,那麼 AutoPtr 類中的賦值操作符和拷貝構造函數必須保證只有一個 AutoPtr 指向對應的指針,在這裏我們的策略是:
AutoPtr( AutoPtr<T> & ap ) 中的參數 AutoPtr ap 作廢 ,構造的新 AutoPtr 接管原 AutoPtr 的指針。AutoPtr<T> & operator=( AutoPtr<T> & ap ) 中的= 左值如果有指針 ,則 delete 掉原指針,接管右值的指針 ,右值作廢。 這種簡單的 AutoPtr 可以用於異常處理。當我們的函數執行中拋出異常,在異常前分配的資源需要在 catch 中手動釋放,這樣往往會有遺漏.如果我們把分配的資源(往往是指針)存放在 AutoPtr 中,那麼資源在超出它們的作用於時會自動釋放,AutoPtr 會自動調用它們各自的析構函數。
引用計數句柄
    這種句柄的目的是實現句柄和對象的多對一關係(對應於指針的情形就是多個指針指向同一對象),這樣我們就可以按照常規定以來通過複製句柄來複制對象的指針。爲了保證對象能夠被釋放,我們的句柄必須知道同時有多少個其他的句柄正指向當前的對象,所以我們引入引用計數策略。
  (1) 這個引用計數功能不能放在句柄中. 因爲如果句柄被複制,它的引用信息也會被複制,那麼綁定了同一指針的句柄們的引用信息就無法統一管理。

  (2) 把引用計數放在對象中也不合適,這需要我們改寫現有類。我們可以建立一箇中間類用來包含引用計數功能和需要用句柄綁定的指針。


     可以看到,這個中間類和我們的指針是一對一關係 ,和我們的句柄是一對多關係。
     現在然我們看看如何實現 Handel 中的基本操作:
  (1) 默認構造函數
     由於句柄現在面對的只是我們添加的中間類,所以只需簡單的調用中間類的默認構造即可。在中間類的默認構造函數中我們將指針清零,引用置一。
  (2) 拷貝構造函數
     拷貝是對於句柄而言,我們通過將引用計數自加來避免對指針所值的內容拷貝。
  (3) 賦值操作符
     句柄間進行賦值操作時,=左邊的句柄所指內容會被改寫,所以需先讓它的引用--(當引用爲一時注意delete), 然後在++等號右邊的句柄引用 。
用一個 handel 對 handel 自身的賦值是無意義的行爲。
  (4) 析構函數
     每次析構時檢查引用計數是否爲一,如果是,說明當前句柄是最後一個保存這個指針的句柄,在析構函數中需要 delete 。
     實際上我們可以將引用計數功能抽象成一個類,直接由句柄管理,這樣就可以去掉中間層,減少程序複雜度。

/*
* File : sharedptr
* Discription : 加入了引用記數的指針存儲策略 
*/


#ifndef _PATTERNS_SHAREDPTR_H_
#define _PATTERNS_SHAREDPTR_H_


#include "../common/common.h"


namespace c_toto
{


template<class T>
class SharedPtr;


class Reference
{
public:
  Reference() : ref_count( new int(1) ) {  }
  Reference( const Reference & r ) : ref_count( r.ref_count ) { (*ref_count)++; }


~Reference()
{
(*ref_count)--;
if( (*ref_count) == 0 )
delete ref_count; 
}


bool Only() { return ( *ref_count == 1 ); }


bool Rebind( const Reference & r )
{
(*ref_count)--;
(*r.ref_count)++;


if( *ref_count == 0 )
{
delete ref_count;
ref_count = r.ref_count;
return true;
}
ref_count = r.ref_count;
return false;
}


private:
   Reference & operator=( const Reference & r_ );
   int * ref_count;
};


////////////////////////////////////////////////////////////////////////////////


template<class T> class SharedPtr
{
public:
SharedPtr( T * p = NULL ): ptr( p ) {}
~SharedPtr() { if( ref.Only() ) { delete ptr; } }
SharedPtr<T> & operator=( const SharedPtr<T> & sp )
{
if( ref.Rebind( sp.ref ) )
{
delete ptr; 

ptr = sp.ptr;
return *this;  
}


private:
Reference ref;
T * ptr;
}; // class


}; // namespace c_toto


 #endif // #ifndef _PATTERNS_SHAREDPTR_H_
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章