1. 程式人生 > >C++ 智慧指標(Smart Pointer)

C++ 智慧指標(Smart Pointer)

智慧指標具有非常強大的能力,謹慎而明智的選擇能帶來極大的好處。我不否認智慧指標的能力,雖然我在之前的否認過auto_ptr。可能由於我自身能力的限制,體會不到auto_ptr的好處,但這樣的可能性我覺得已經不大了。但auto_ptr是最簡單的智慧指標,在它的周圍存在大量的作品,這些作品包括Boost、Loki、ACE等等,但是可惜的是目前沒有一個我能夠說我很熟悉,那麼本篇只是作為一個入門,在此基礎上,應當閱讀Boost、Loki、ACE相關原始碼。

  Smart Pointer的核心是實現

template <class T>
T& SmartPointer<T>::operator*() const;
template <class T>
T& SmartPointer<T>::operator->() const;


  Smart Pointer的構造和析構是一門藝術,由此而衍生出很多不同型別的Smart Pointer。千萬不要指望Smart Pointer的表現象原生指標,雖然可以通過隱式轉換來實現它,然而往往帶來的後果是災難性的。

  Meyers給出了一個優雅的隱式轉換的辦法:

template<class T>
class TestTemplate
{
public:
TestTemplate(T* ptr = 0):pointee(ptr){}
template<class newType>
operator TestTemplate<newType>()
{
return TestTemplate<newType>(pointee);
}
private:
T* pointee;

};


  可惜這樣的程式在VC6中無法通過編譯,似乎VC6不支援將novirtual member function宣告成templates,但是VC7可以。這裡地方需要注意四點技術:

  (1)函式呼叫的自變數匹配規則

  (2)隱式型別轉換函式

  (3)template functions的暗自具現化

  (4)member function templates。我承認,這有點太深入了。

  在智慧指標中const和non-const之間的轉化也是很大的學問,我看了Meyers使用unions來做實現。這不是我喜歡的做法,我覺得風險還是比較大的。

  Smart Pointer值得使用麼?這不是我可以回答的問題,然而在以往的經驗中,我似乎很少用到。也許是我的孤陋造成了這樣的局面,但在更深層次來說,我需要閱讀更多的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++標準庫智慧指標(std::auto_ptr)
   
    1.Do you Smart Pointer?
      Smart Pointer,中文名:智慧指標, 舶來品?
      不可否認,資源洩露(resource leak)曾經是C++程式的一大噩夢.垃圾回收
      機制(Garbage Collection)一時頗受注目.然而垃圾自動回收機制並不能
      滿足記憶體管理的即時性和可視性,往往使高傲的程式設計者感到不自在.
      況且,C++實現沒有引入這種機制.在探索中,C++程式設計師創造了鋒利的
      "Smart Pointer".一定程度上,解決了資源洩露問題.

      也許,經常的,你會寫這樣的程式碼:
      //x擬為class:
      //            class x{
      //            public:       
      //                   int m_Idata;
      //            public:
      //                   x(int m_PARAMin):m_Idata(m_PARAMin){}
      //                   void print(){ cout<<m_Idata<<endl; }
      //            .....
      //            }
      //
      void fook(){
      x* m_PTRx = new A(m_PARAMin);
      m_PTRx->DoSomething();     //#2
      delete m_PTRx;
      }

      是的,這裡可能沒什麼問題.可在複雜、N行、m_PTRclassobj所指物件生命周
      期要求較長的情況下,你能保證你不會忘記delete m_PTRclassobj嗎?生活中,
      我們往往不應該有太多的口頭保證,我們需要做些真正有用的東西.還有一個
      更敏感的問題:異常.假如在#2方法執行期異常發生,函式執行終止,那麼new
      出的物件就會洩露.於是,你可能會說:那麼就捕獲異常來保證安全性好了.
      你寫這樣的程式:

      void fook(){
      A* m_PTRx = new A(m_PARAMin);
      try{
          m_PTRx->DoSomething();
      }
      catch(..){
          delete m_PTRx;
          throw;
      }
      delete m_PTRx;
      }
      哦!天哪!想象一下,你的系統,是否會象專為捕獲異常而設計的.

      一天,有人給你建議:"用Smart Pointer,那很安全.".你可以這樣重寫你的程式:
   
      void fook(){
      auto_ptr<x> m_SMPTRx(new x(m_PARAMin));
      m_SMPTRx->DoSomething();
      }

      OK!你不太相信.不用delete嗎?
      是的.不用整天提心吊膽的問自己:"我全部delete了嗎?",而且比你的delete
      策略更安全.

      然後,還有人告訴你,可以這樣用呢:
      ok1.
      auto_ptr<x> m_SMPTR1(new x(m_PARAMin));
      auto_ptr<x> m_SMPTR2(m_SMPTR1);  //#2
      May be you can code #2 like this :
          auto_ptr<x> m_SMPTR2;
          m_SMPTR2 = m_SMPTR1;     
      ok2.
      auto_ptr<int> m_SMPTR1(new int(32));
     
      ok3.
      auto_ptr<int> m_SMPTR1;
      m_SMPTR1 = auto_ptr<int>(new int(100));
      也可以:
      auto_ptr<int> m_SMPTR1(auto_ptr<int>(new int(100)));
     
      ok4.
      auto_ptr<x> m_SMPTR1(new x(m_PARAMin));
      m_SMPTR1.reset(new x(m_PARAMin1));
     
      ok5.
      auto_ptr<x> m_SMPTR1(new x(m_PARAMin));
      auto_ptr<x> m_SMPTR2(m_SMPTR.release());
      cout<<(*m_SMPTR2).m_Idata<<endl; 
     
      ok6.
      auto_ptr<int> fook(){
      return auto<int>(new int(100));
      }
 
      ok7.............and so on
     
      但不可這樣用:
     
      no1.  
      char* chrarray = new char[100];
      strcpy(chrarray,"I am programming.");
      auto_ptr<char*> m_SMPTRchrptr(chrarray);
      //auto_ptr並不可幫你管理陣列資源    
      
      no2.
      vector<auto_ptr<x>> m_VECsmptr;
      m_VECsmptr.push_back(auto_ptr<int>(new int(100)));
      //auto_ptr並不適合STL內容.
      
      no3.
      const auto_ptr<x> m_SMPTR1(new x(100));
      auto_ptr<x> m_SMPTR(new x(200));
     
      no4.
      x m_OBJx(300);
      auto_ptr<x> m_SMPTR(&m_OBJx);
     
      no5
      x* m_PTR = new x(100);
      auto_ptr<x> m_SMPTR = m_pTR;
     
      no6..........and so on

      預先提及所有權的問題,以便下面帶著疑問剖析程式碼?

      power1.
      auto_ptr<x> m_SMPTR1(new x(100));
      auto_ptr<x> m_SMPTR2 = m_SMPTR1;
      m_SMPTR2->print();
      //輸出:100.
      m_SMPTR1->print();
      //!! 非法的.

      power2.
      auto_ptr<x> m_SMPTR(new x(100));
     
      auto_ptr<x> returnfun(auto_ptr<x> m_SMPTRin){
      return m_SMPTRin;
      }
     
      auto_ptr<x> = returnfun(m_SMPTR);  //#5

      //在上面的#5中,我要告訴你物件所有權轉移了兩次.
      //什麼叫物件所有權呢?
  
    2. std::auto_ptr的設計原理
      
      上面的一片正確用法,它們在幹些什麼?
            一片非法,它們犯了什麼罪?
            一片什麼所有權轉移,它的內部機智是什麼?
      哦!一頭霧水?下面我們就來剖析其實現機制.
      基礎知識:
              a.智慧指標的關鍵技術:在於構造棧上物件的生命期控制
                堆上構造的物件的生命期.因為在智慧指標的內部,儲存
                著堆物件的指標,而且在構析函式中呼叫delete行為.
                大致機構如下:
                x* m_PTRx = new x(100);//#1
                template<typename T>
                auto_ptr{
                private:
                T* m_PTR;//維護指向堆物件的指標,在auto_ptr定位後    
                ....     //它應該指向#1構造的物件,即擁有所有權.
                ~auto(){ delete m_PTR; }
                ....
                }
             b.所有權轉移之說
               上面曾有一非法的程式片段如下:
               auto_ptr<x> m_SMPTR1(new x(100));
               auto_ptr<x> m_SMPTR2 = m_SMPTR1;
               m_SMPTR2->print();
               //輸出:100.
               m_SMPTR1->print();
               //!! 非法的.
               按常理來說,m_SMPTR->print();怎麼是非法的呢?
               那是因為本來,m_SMPTR1維護指向new x(100)的指標,
               可是m_SMPTR2 = m_SMPTR1;auto_ptr內部機制使得m_SMPTR1將物件的地址
               傳給m_SMPTR2,而將自己的物件指標置為0.
               那麼自然m_SMPTR->print();失敗.
               這裡程式設計者要負明顯的職責的.
               那麼auto_ptr為什麼採取這樣的策略:保證所有權的單一性.
                                               亦保證了系統安全性.
               如果多個有全權的auto_ptr維護一個物件,那麼在你消除一個
               auto_ptr時,將導致多個auto_ptr的潛在危險.
     
       下面我們以SGI-STL的auto_ptr設計為樣本(去掉了無關分析的巨集),來剖析其原理.
       #1  template <class _Tp> class auto_ptr {
       #2  private:
       #3  _Tp* _M_ptr;  //定義將維護堆物件的指標

       #4  public:
       #5  typedef _Tp element_type;  //相關型別定義
       #6  explicit auto_ptr(_Tp* __p = 0) __STL_NOTHROW : _M_ptr(__p) {}
       #7  auto_ptr(auto_ptr& __a) __STL_NOTHROW : _M_ptr(__a.release()) {}
       #8  template <class _Tp1> auto_ptr(auto_ptr<_Tp1>& __a) __STL_NOTHROW
                                                 : _M_ptr(__a.release()) {}
           //#6、#7、#8是auto_ptr建構函式的三個版本.
           //#6註釋:傳入物件的指標,構造auto_ptr.explicit關鍵字:禁止隱式轉換.
           //        這就是ok2正確,而no5(隱式轉換)錯誤的原因.
           //#7註釋:拷貝建構函式.
           //        傳入auto_ptr例項,構造auto_ptr. ok1、ok3使用了這個構造式.
           //        它是一個很關鍵的建構函式,在具體情況下,我們再分析
           //#8註釋:auto_ptr的模板成員,可在繼承物件過載的基礎上,實現特殊功能.
           //  
           //   舉例:
           //   class A{ public:
           //          virtual void fook(){cout<<"I am programming"<<endl;
           //          /*..........*/                                   };
           //   class B : public A {
           //          virtual void fook(){ cout<<"I am working"<<endl;
           //         /*...........*/                                  }; 
           //   auto_ptr<A> m_SMPTRa(new A(33));//實質:
           //   auto_ptr<B> m_SMPTRb(m_SMPTRa); //基類的指標可以賦給派生類的指標         
           //             
           //   auto_ptr<B> m_SMPTRb(new B(44));//實質:
           //   auto_ptr<A> m_SMPTRa(m_SMPTRb); //派生類的指標不可賦給基類的指標
           //      
           //   auto_ptr<A> m_SMPTRa(new B(33));  // ok! 
           //   m_SMPTRa->fook()將呼叫派生類B的fook()
           //   m_SMPTRa->A::fook()將呼叫基類A的fook()
           //   
           //   auto_ptr<B> m_SMPTRb(new A(33));  // wrong!
           //  
           //  
       #9  auto_ptr& operator=(auto_ptr& __a) __STL_NOTHROW {
       #10 if (&__a != this) { delete _M_ptr;  _M_ptr = __a.release(); }
       #11 return *this;
       #12 }
        
       #13 template <class _Tp1>
       #14 auto_ptr& operator=(auto_ptr<_Tp1>& __a) __STL_NOTHROW {
       #15 if (__a.get() != this->get()) { delete _M_ptr; _M_ptr = __a.release(); }
       #16 return *this;
       #16 } 
          //
          // #9~~#16 兩個版本的指派函式.
          //         delete _M_ptr; 在指派前,銷燬原維護的物件.
          //         _a.release() ; release操作,詳細程式碼參見#20~~#23.
          //                        用於*this獲得被指派物件,
          //                        且將原維護auto_ptr置空.
          //     no3使用了第一種指派.
          //     而許可權轉移正是_a.release()的結果.
         
       #17 ~auto_ptr() __STL_NOTHROW { delete _M_ptr; }
          //構析函式.消除物件.注意這裡對物件的要求!
         
       #17 _Tp& operator*() const __STL_NOTHROW {  return *_M_ptr; }
       #18 _Tp* operator->() const __STL_NOTHROW { return _M_ptr;  }
       #19 _Tp* get() const __STL_NOTHROW { return _M_ptr; }
         //
         //  操作符過載.
         // #17註釋:提領操作(dereference),獲得物件. 見ok5用法.
         // #18註釋:成員運算子過載,返回物件指標.
         // #19註釋:普通成員函式.作用同於過載->運算子
         //
       #20 _Tp* release() __STL_NOTHROW {
       #21 _Tp* __tmp = _M_ptr;
       #22 _M_ptr = 0;
       #23 return __tmp;                }
         //上面已經詳解     
 
       #24 void reset(_Tp* __p = 0) __STL_NOTHROW {
       #25 delete _M_ptr;
       #26 _M_ptr = __p;                          }
         //
         //傳入物件指標,改變auto_ptr維護的物件
         //       且迫使auto_ptr消除原來維護的物件
         //       見ok3用法.

         // According to the C++ standard, these conversions are required.  Most
         // present-day compilers, however, do not enforce that requirement---and,
         // in fact, most present-day compilers do not support the language
         // features that these conversions rely on.
        
         //下面這片段用於型別轉化,目前沒有任何編譯器支援
         //具體技術細節不訴.         

         #ifdef __SGI_STL_USE_AUTO_PTR_CONVERSIONS

      #27 private:
      #28 template<class _Tp1>
      #29 struct auto_ptr_ref { _Tp1* _M_ptr; auto_ptr_ref(_Tp1* __p) : _M_ptr(__p) {}
                             };

      #30 public:
      #31 auto_ptr(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW
                               : _M_ptr(__ref._M_ptr) {}
      #32 template <class _Tp1>
      #33 operator auto_ptr_ref<_Tp1>() __STL_NOTHROW
      #34 { return auto_ptr_ref<_Tp>(this->release()); }
      #35 template <class _Tp1> operator auto_ptr<_Tp1>() __STL_NOTHROW
      #36 { return auto_ptr<_Tp1>(this->release()); }
      #37 #endif /* __SGI_STL_USE_AUTO_PTR_CONVERSIONS */
      #38 };
     
      OK!就是這樣了.
      正如上面原理介紹處敘說,
      你需要正視兩大特性:
      1.構造棧物件的生命期控制堆上構造的物件的生命期
      2.通過release來保證auto_ptr對物件的獨權.
     
     在我們對原始碼分析的基礎上,重點看看:
     no系列錯誤在何處?
     no1.
         我們看到構析函式template<class _Tp>
                         ~auto_ptr() _STL_NOTHROW
                        { delete _M_ptr; }
         所以它不能維護陣列,
         維護陣列需要操作:delete[] _M_ptr;
     no2.
        先提部分vector和auto_ptr程式碼:
        a.提auto_ptr程式碼
         
        auto_ptr(auto_ptr& __a) __STL_NOTHROW : _M_ptr(__a.release()) {}
       
        b.提vector程式碼
         
          Part1:
          void push_back(const _Tp& __x) {
          if (_M_finish != _M_end_of_storage) {
          construct(_M_finish, __x);
          ++_M_finish;
          }
          else
         _M_insert_aux(end(), __x);
          }
       
         Part2:
         template <class _T1, class _T2>
         inline void construct(_T1* __p,

         //++++++++++++++++++++++++++++++++
         //         const _T2& __value) { +
         //++++++++++++++++++++++++++++++++
         //  new (__p) _T1(__value);      +
         //++++++++++++++++++++++++++++++++

         }
        
         Part3.
         template <class _Tp, class _Alloc>
         void
         vector<_Tp, _Alloc>::_M_insert_aux
         (iterator __position,

          //++++++++++++++++++++++++++++++++
          //        const _Tp& __x)       ++
          //++++++++++++++++++++++++++++++++  
 
         {
         if (_M_finish != _M_end_of_storage) {
         construct(_M_finish, *(_M_finish - 1));
         ++_M_finish;

         //++++++++++++++++++++++++++++++++
         //     _Tp __x_copy = __x;       +
         //++++++++++++++++++++++++++++++++

         copy_backward(__position, _M_finish - 2, _M_finish - 1);
         *__position = __x_copy;
         }
         else {
         const size_type __old_size = size();
         const size_type __len = __old_size != 0 ? 2 * __old_size : 1;
         iterator __new_start = _M_allocate(__len);
         iterator __new_finish = __new_start;
         __STL_TRY {
         __new_finish = uninitialized_copy
         (_M_start, __position, __new_start);
         construct(__new_finish, __x);
         ++__new_finish;
         __new_finish = uninitialized_copy
        (__position, _M_finish, __new_finish);
        }
        __STL_UNWIND((destroy(__new_start,__new_finish),
                  _M_deallocate(__new_start,__len)));
       destroy(begin(), end());
       _M_deallocate(_M_start, _M_end_of_storage - _M_start);
       _M_start = __new_start;
       _M_finish = __new_finish;
       _M_end_of_storage = __new_start + __len;
       }
       }

       從提取的vector程式碼,Part1可看出,push_back的操作行為.
       兵分兩路,可是再向下看,你會發現,無一例外,都
       通過const _Tp& 進行拷貝行為,那麼從auto_ptr提出的片段就
       派上用場了.
       可你知道的,auto_ptr總是堅持對物件的獨權.那必須修改
       原來維護的物件,而vector行為要求const _Tp&,這樣自然會產生
       問題.一般編譯器是可以發覺這種錯誤的.

       其實,STL所有的容器類都採用const _Tp&策略.
 
       //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
      + 看了sutter和Josuttis的兩篇文章中,都提及:                    +
      + STL容器不支援auto_ptr原因在於copy的物件只是獲得所有權的物件, +
      + 這種物件不符合STL的要求.可是本人總感覺即時不是真正的複製物件,+
      + 但我用vector<auto_ptr<x> >的目的就在於維護物件,並不在乎      +
      + 所謂的完全物件.而且我用自己寫的Smart Pointer配合STL容器工作, +
      + 很正常.那需要注意的僅僅是const問題.                          +
      +                                                              +
      //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

     no3.
        這個也是auto_ptr隱含的所有權問題引起的.
        const auto_ptr不允許修改.
        隨便提及:const物件不代表物件一點不可以改變.
                  在兩種const語義下,都有方法修改物件或物件內部指標維護的物件
                 或其它資源.
     no4.
        再看auto_ptr的構析函式.
        delete不可以消除棧上資源.

     no5.
        依賴傳入物件指標的建構函式被宣告為explicit,禁止隱式轉換.

   
    3.auto_ptr高階使用指南
     
      a.類成員auto_ptr,禁止建構函式以構建"完全物件"
        Programme1:
        struct Structx{
               int m_Idata;
               char m_CHRdata;
               /* and so on */
        };
        出於物件程式設計的理念,
        我們將Structx打造成包裹類:
        class StructWrapper{
        private:
        Structx* m_STRTxptr;
        public:
        StructWrapper():m_STRTxptr(new Structx){}
        ~StructWrapper(){delete m_SMRTxptr; }
        public:
        void Soperator1(){ /* 針對Structx物件的特性操作 */}
        void Soperator2(){ /* 針對Structx物件的特性操作 */}       
        /*  and so on */
        };
       
        Programme2:
        class StructWrapper{
        private:
        auto_ptr<Structx> m_SMPTRx;
        public:
        StructWrapper():m_SMPTRAx(new Structx){}
        public:
        void Soperator1(){ /* 針對Structx物件的特性操作 */}
        void Soperator2(){ /* 針對Structx物件的特性操作 */}       
        /*  and so on */
        };
       
        Programme3:
        StructWrapper::StructWrapper(const StructWrapper& other)
        : M_SMPTRx(new Struct(*other.m_SMPTRx)) { }
        StructWrapper& StructWrapper::operator=(const StructWrapper &other){
        *m_SMPTRx = *other.m_SMPTRx;
        };

        處於對構建於堆中的物件(new Structx)智慧維護的需要.
        我們將programme1改造為programme2:
        不錯,物件是可以智慧維護了.
        對於包裹類(StructWrapper)你是否會有這樣的構造或指派操作:
         StructWrapper m_SMPTRWrapper2(m_SMPTRWrapper1);
      
         StructWrapper mSMPTRWrapper2 = m_SMPTRWrapper1;
         那麼請注意:
         當你坦然的來一個:M_SMPTRWrapper1->Soperator1();的時候,
         系統崩潰了.
         不必驚訝,所有權還是所有權問題.
         問一下自己:當programme2預設拷貝建構函式作用時,又呼叫了auto_ptr的
         預設建構函式,那麼auto_ptr所有的預設行為都遵循獨權策略.對,就這樣.
         m_SMPTRWrapper1的物件所有權轉移給了m_SMPTRWrapper2.
         M_SMPTRWrapper1->Soperator1();那麼操作變成了在NULL上的.
         哦!系統不崩潰才怪.
         那麼你需要想,programme3那樣利用auto_ptr的提領操作符自己的
         構造"完全物件".

       b.利用const關鍵字,防止不經意的許可權轉移
        
         從上面的敘述,你可看出,所有權轉移到處可以釀成大禍.
         而對於一般應用來說,獨權又是很好的安全性策略.
         那麼我們就用const來修飾auto_ptr,禁止不經意的錯誤.
       
         當然上面提及:並不代表auto_ptr是不可修改的.
         處於需要,從兩種const語義,你都可實現修改.

         然,你還希望在函式傳入傳出auto_ptr那麼你可傳遞auto_ptr的引用,
         那就萬無一失了: void fook(const auto_ptr<x>& m_PARAMin);
         在返回後賦予其它時,使用引用是不行的.你得用指標.
         因為引用無論作為lvalue還是rvaluev,都會呼叫構造或指派函式.


    4.你是否覺得std::auto_ptr還不夠完美
     
      在實踐中,std::auto_ptr能滿足你的需求嗎?          
 
      Andrei Alexandrescu在一篇文章中,提及:有關Smart Pointer的技術就像
      巫術.Smart Pointer作為C++垃圾回收機制的核心,它必須足夠強大的、具有工業強度和安全性.
      但為了可一勞永逸我們還需要披荊斬棘繼續探索.

      下面在需求層面上,我們思索一下我們的智慧指標還需要些什麼?
 
        a. std::auto_ptr 能夠處理陣列嗎?我們可以用智慧指標來管理其它的資源嗎?
           譬如一個執行緒控制代碼、一個檔案控制代碼 and so on !
        b. 對於我們的物件真的永遠實行獨權政策嗎?
        c. Our 智慧指標還需要在繼承和虛擬層面上發揮威力 !
        d. 往往,需要擴充套件Our 智慧指標的功能成員函式來滿足動態的需要 !
        e. 也許,你需要的還很多.

二、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.事實上,你可以根據自己的需要.
        過載更多或加入功能成員函式.