1. 程式人生 > >C++深度探索系列:智慧指標(Smart Pointer) [二]

C++深度探索系列:智慧指標(Smart Pointer) [二]

                                           深度探索智慧指標(Smart Pointer)

主題索引:

一、剖析C++標準庫智慧指標(std::auto_ptr)
   
    1.Do you Smart Pointer?
    2.std::auto_ptr的設計原理
    3.std::auto_ptr高階使用指南
    4.你是否覺得std::auto_ptr還不夠完美?

二、C++條件,尋找構造更強大的智慧指標(Smart Pointer)的
    策略
   
    1.支援引用記數的多種設計策略
    2.支援處理多種資源
    3.支援Subclassing
    4.支援多執行緒條件下,執行緒安全的多種設計策略
    5.其它多種特殊要求下,再構造

三、Generic Programming基礎技術和Smart Pointer
    1.回首處理資源中的Traits技術
    2.回首多執行緒支援的設計


四、COM實現中,Smart Pointer設計原理


五、著名C++庫(標準和非標準)中的Smart Pointer現狀

---------------------------------------------------------------------

二、C++條件,尋找構造更強大的智慧指標(SmartPointer)的策略
    
              
    
  1.支援引用記數的多種設計策略
     
      你聽說過COM和它著名的IUnknown介面吧?
      IUnknown是幹什麼的?我要告訴你,IUnknown介面三個函式簽名中,
      兩個是用來管理物件(CoClass Object,元件類物件)的記數來控制
      它的生命週期的.
 
      在實踐中,我們的物件並不是只用一次,只允許一個引用的.

      那麼,誰來管理它的生命週期呢?
     
      我們的策略是:引用記數. 當物件的引用記數為零時,就銷燬物件.
      在沒有託管環境的情況下,事實上,銷燬物件的往往還是auto_ptr.
      而COM中,銷燬物件的是物件自己.
     
      事實上,它和我們的智慧指標不是一個級別上的概念.
      我們的智慧指標負責的是物件級的引用.而COM是以介面引用為
      核心的.保證介面操作時,介面引用記數的自動管理.
 
      哦!是的!那麼我們怎樣給auto_ptr加上物件引用記數的功能?

      策略1:
        
         一個物件對應一個引用記數物件.
         智慧指標以記數物件為代理.
         想象,這又歸到經典的"新增中間層"解決方案上了.
         
         # 核心一:
          
         我們新增一個 "引用記數class".
         它的職責有二:
            a.維護物件的引用記數.
            b.維護物件的指標.
        
         結構示意如下:
         template<class T>
         class ObjRefCounted{
         private:
             T* m_OBJ_Delegate_Ptr;
             unsigned int m_UIcounted;
         public:
      explicit ObjRefCounted(T* m_Paramin = 0):
             m_UIcounted(1), m_OBJ_Delegate_Ptr(m_Paramin){};   
   
      template<class M> ObjRefCounted(ObjRefCounted<M>& x) {
             m_OBJ_Delegate_Ptr = x.m_OBJ_Delegate_Ptr);          };
        
         ObjRefCounted(const ObjRefCounted& x):m_UIcounted
             (x.m_UIcounted), m_OBJ_Delegate_Ptr(x.m_ObjDelegate_Ptr){};
      ~ObjRefCounted();
 
             void ReleaseRef ();
      void AddRef ();
      T* GetRealPointer () const;
         };
        
         # 核心二
           在智慧指標中維護一個引用記數class的指標
           template<class T>
           class SmartPointer{
           public:
                 ObjRefCounted* _m_ObjRefCounted;
           .....
           .....
           };
          
           通過上面的兩個策略,我們就可以在智慧指標構造時,為之付上一個
           引用記數物件.這個物件負責託管Smart Pointer原本應該維護
           的物件指標.並且負責最終消除物件.

           在Smart Pointer中,我們將會涉及大量的_m_ObjRefCounted的操作.
           下面簡敘一過程,詳細不訴,自己設計之.
           譬如:當你將一個物件指標賦給Smart Pointer將構建一輔助的
           引用記數託管物件,此時m_UIcounted為1,m_OBJ_Delegate_Ptr被賦
           以物件指標,假如現在我又將Smart Pointer 賦給另一SmartPointer2
           , 那麼SmartPointer2呼叫_m_ObjRefCounted->ReleaseRef();
           減少原來維護的物件的記數,將自己的_m_ObjRefCounted置為
           SmartPointer2依附的記數物件,再呼叫_m_ObjRefCounted->AddRef();
           OK!就是這樣的.


      策略2.
           在每一個智慧指標內部維護一個物件指標和一個引用記數值的
           的指標.
 
           這裡的重點在於維護一個引用記數值的指標,
           它使得Smart Pointer之間保持一致的記數值成為可能.
          
           結構示意如下:
           template<class T>
           class SmartPointer{
           private:
                  T* m_ObjPtr;
                  unsigned int* RefCounted;
           public:
           explicit SmartPoint(T* PARAMin = 0) : m_ObjPtr(PARAMin),
                          RefCounted(new int(1)) { }
           SmartPoint(const SmartPoint<T>& PARAMin2):
           m_ObjPtr(PARAMin2.m_ObjPtr),
           RefCounted(PARAMin2.RefCounted) { ++*RefCounted; }
           ....
           ...
           };
          
           不過這個方法的擴充套件性很差.
           因為引用記數功能結合到Smart Pointer中去了.
           一般不會用這種方法.
    
           以上面的兩種策略為基礎,根據實際情況,可設計出更多的記數方法.
           
      2.利用Traits(Partial Specialization)技術,
        支援處理多種資源

         
        在no1中,我們提到不可讓auto_ptr管理陣列,那是因為
        auto_ptr構析函式中呼叫的是delete的緣故.
        陣列不可,其它的如,檔案控制代碼、執行緒控制代碼等當然更不可以了.

        下面我們就這個問題來探討:

          策略1.
          通過函式指標來支援多種資源的處理.
          我們的智慧指標將設計成具有兩個引數的模板類.
          第一個引數指示:資源的型別
          第二個引數指示:處理資源的函式型別
          
          結構示意如下:

          typedef void FreeResourceFunction(void* p);
          void DealSingleObject(void* p);  
          void DealArray(void* p);
          void DealFile(void* p);
          //
          //  針對特殊的資源加入函式指標宣告
          //
          template<class Type , class DealFunction = DealSingleObject>
          class SmartPointer{                                              
          public:
          ~SmartPointer(){ DealFunction(); }
          ...
          ...
          /* Other codes */
          };

          inline void DealSingle(void* p)
          {  
              if(p)  delete p;
          }

          inline void DealArray(void* p){
       if(p)  delete[] p;                
          }
 
          inline void DealFile(void* p){
             if(p)   p->close();
          }  
          //
          //針對特殊資源加入處理函式
          //     

          oK!但是我們在使用這個策略的時候,一定要注意,
          傳遞進的指標不能是錯誤的,這個你必須保證.
          當然對上面的結構示意再改造,使之具有更強的
          辨錯能力也是可取的.

  3.支援Subclassing
        關於智慧指標中的Subclassing,是什麼?
        我們先來看一程式片段:
           
        class BaseClass {};
        class Derived : public BaseClass {};
         
        auto_ptr<Derived> m_Derived;
 auto_ptr<Base> m_Base;
         
 auto_ptr<Derived> pDerived = new Derived;
 m_Base = pDerived;
        //
        //m_Derived = (PDerived&)m_Base;   //#1
        //

        看到上面的#1沒有,你認為在auto_ptr中,
        它或者同等語義的行為可以執行?
        不可以.為什麼?
        它本質上,相當與這樣的操作:
        BaseClass* m_BaseClass;
        m_BaseClass = new DerivedClass(inParam);
        這顯然是非法的.
         
        在上面我們曾經,auto_ptr對具有虛擬特性的類,
        也能體現出虛擬性.

        然而那並不能訪問繼承的資料,實現的不是真正意義
        上的SubClassing.

        那麼,我們這樣來實現這樣的功能.
         
          策略1.
          在上述引用記數部分敘述的SmartPoint中,我們作如下的操作:
         
   template <class U> SmartPointer& operator = (const SmartPointer<U>& that)
          {
   if (m_pRep ! = reinterpret_cast<RefCountRep<T>* > (that.m_pRep))
   {
     ReleaseRef ();
     m_pRep = reinterpret_cast<RefCountRep<T>* > (that.m_pRep);
     AddRef ();
     }
     return *this;
  }
         };

         不錯,reinterpret_cast,就是它幫我們解決了問題.

         策略2.
         關於第二種方法,這裡不再詳細敘說.
         它涉及太多的細節,峰迴路轉的很難說清.
         大體上,它是利用引用記數物件中維護的物件指標為void*
         而在具體的呼叫是通過static_cast或reinterpret_cast轉化.
         總之,所謂的SubClassing技術離不開轉化.

      4.支援多執行緒條件下,執行緒安全的多種設計策略
 
        對於標準C++,多執行緒問題並不很受關注.
        原因在於目前,標準庫並不支援多執行緒.
       
        策略1:
          首先我們想到:對資料進行訪問同步.
          那麼,我們有兩種方案:
          a. 建立一個臨界區物件.將物件的執行傳遞給臨界區物件.
             以保證安全.
          b.利用臨時物件來完成任務,將臨界的責任留給被作用物件.
         
          下面分析第二種的做法:
          programme1:
          class Widget
          {
           ...
           void Lock();  //進入臨界區
           void Unlock(); //退出臨界區
          };
       
          programme2:
          template <class T>
          class LockingProxy
          {
            public:
            LockingProxy(T* pObj) : pointee_ (pObj)
            { pointee_->Lock(); }
            //    在臨時物件構造是就鎖定
            //    weight物件(臨界區).
            ~LockingProxy() { pointee_->Unlock(); }
            //           
            //   在臨時物件銷燬時,退出臨界區.
            //
            T* operator->() const
            { return pointee_; }
            //
            //  這裡過載->運算子.將對臨時物件的方法執行
            //  請求轉交給weight物件
            //
            private:
            LockingProxy& operator=(const LockingProxy&);
            T* pointee_;
         };

         programme3:
         template <class T>
         class SmartPtr
         {
            ...
            LockingProxy<T> operator->() const
            { return LockingProxy<T>(pointee_); }
            //
            //  核心就在這裡:產生臨時物件
            //  LockingProxy<T>(pointee_)
            private:  sT* pointee_;
         };

         Programme4.
         SmartPtr<Widget> sp = ...;
         sp->DoSomething();       //##1

         下面,我們模擬一下,執行的過程.
          ##1執行時,構建了臨時物件LockingProxy<T>(pointee_)
          此物件在構造期間就鎖定Weight物件,並將DoSomethin()
          方法傳遞給weight物件執行,在方法執行完,臨時物件消失,
          構析函式退出臨界區.

      4.其它特殊要求下的再構造
       
        a.回首當年,你是否覺的
          auto_ptr<x> m_SMPTR = new x(100);
          居然通不過.不爽!
          No problem !
          auto_ptr(T* m_PARAMin = 0) shrow() : m_Tp(m_PARAMin){}
          解決問題.
 
       b. Consider it:
          void fook(x* m_PARAMin){};
          可是我只有auto_ptr<x> m_SMPTR;
          No problem !
          T* operator T*(auto_ptr<T>& m_PARAMin) throw ()
          { return m_Tp; }
         
          fook(m_SMPTR); // ok !  now
      c.事實上,你可以根據自己的需要.
        過載更多或加入功能成員函式.

--------------------------------------------------------------
                       待續

三、Generic Programming基礎技術和Smart Pointer
    1.回首處理資源中的Traits技術
    2.回首多執行緒支援的設計


四、COM實現中,Smart Pointer設計原理


五、著名C++庫(標準和非標準)中的Smart Pointer現狀

--------------------------------------------------------------


--------------------------------------------------------------
                          鄭重宣告:
                 允許複製、修改、傳遞或其它行為
                 但不準用於任何商業用途.
                      寫於  20/3/2003
                      最後修改: 20/3/2003
                         By RedStar81
                      [email protected]
-------------------------------------------------------------