zoukankan      html  css  js  c++  java
  • C++深度探索系列:智能指针(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<      //            .....
          //            }
          //
          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 m_SMPTRx(new x(m_PARAMin));
          m_SMPTRx->DoSomething();
          }

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

          然后,还有人告诉你,可以这样用呢:
          ok1.
          auto_ptr m_SMPTR1(new x(m_PARAMin)); 
          auto_ptr m_SMPTR2(m_SMPTR1);  //#2
          May be you can code #2 like this :
              auto_ptr m_SMPTR2;
              m_SMPTR2 = m_SMPTR1;      
          ok2.
          auto_ptr m_SMPTR1(new int(32));
          
          ok3.
          auto_ptr m_SMPTR1;
          m_SMPTR1 = auto_ptr(new int(100));
          也可以:
          auto_ptr m_SMPTR1(auto_ptr(new int(100)));
          
          ok4.
          auto_ptr m_SMPTR1(new x(m_PARAMin));
          m_SMPTR1.reset(new x(m_PARAMin1));
          
          ok5.
          auto_ptr m_SMPTR1(new x(m_PARAMin));
          auto_ptr m_SMPTR2(m_SMPTR.release());
          cout<<(*m_SMPTR2).m_Idata<      
          ok6.
          auto_ptr fook(){
          return auto(new int(100));
          }
     
          ok7.............and so on
          
          但不可这样用:
          
          no1.   
          char* chrarray = new char[100];
          strcpy(chrarray,"I am programming.");
          auto_ptr m_SMPTRchrptr(chrarray);
          //auto_ptr并不可帮你管理数组资源     
           
          no2.
          vector> m_VECsmptr;
          m_VECsmptr.push_back(auto_ptr(new int(100)));
          //auto_ptr并不适合STL内容.
           
          no3.
          const auto_ptr m_SMPTR1(new x(100));
          auto_ptr m_SMPTR(new x(200));
          
          no4.
          x m_OBJx(300);
          auto_ptr m_SMPTR(&m_OBJx);
          
          no5
          x* m_PTR = new x(100);
          auto_ptr m_SMPTR = m_pTR;
          
          no6..........and so on

          预先提及所有权的问题,以便下面带着疑问剖析代码?

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

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

          //在上面的#5中,我要告诉你对象所有权转移了两次.
          //什么叫对象所有权呢?
       
        2. std::auto_ptr的设计原理
           
          上面的一片正确用法,它们在干些什么?
                一片非法,它们犯了什么罪?
                一片什么所有权转移,它的内部机智是什么?
          哦!一头雾水?下面我们就来剖析其实现机制.
          基础知识:
                  a.智能指针的关键技术:在于构造栈上对象的生命期控制
                    堆上构造的对象的生命期.因为在智能指针的内部,存储
                    着堆对象的指针,而且在构析函数中调用delete行为.
                    大致机构如下:
                    x* m_PTRx = new x(100);//#1
                    template
                    auto_ptr{
                    private:
                    T* m_PTR;//维护指向堆对象的指针,在auto_ptr定位后     
                    ....     //它应该指向#1构造的对象,即拥有所有权.
                    ~auto(){ delete m_PTR; }
                    ....
                    }
                 b.所有权转移之说
                   上面曾有一非法的程式片段如下:
                   auto_ptr m_SMPTR1(new x(100));
                   auto_ptr 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 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 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"<           //          /*..........*/                                   }; 
               //   class B : public A {
               //          virtual void fook(){ cout<<"I am working"<           //         /*...........*/                                  };  
               //   auto_ptr m_SMPTRa(new A(33));//实质:
               //   auto_ptr m_SMPTRb(m_SMPTRa); //基类的指针可以赋给派生类的指针          
               //              
               //   auto_ptr m_SMPTRb(new B(44));//实质:
               //   auto_ptr m_SMPTRa(m_SMPTRb); //派生类的指针不可赋给基类的指针
               //       
               //   auto_ptr m_SMPTRa(new B(33));  // ok!  
               //   m_SMPTRa->fook()将调用派生类B的fook()
               //   m_SMPTRa->A::fook()将调用基类A的fook()
               //    
               //   auto_ptr 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 
           #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 
          #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 
          #33 operator auto_ptr_ref<_Tp1>() __STL_NOTHROW 
          #34 { return auto_ptr_ref<_Tp>(this->release()); }
          #35 template 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
                             ~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 
             inline void construct(_T1* __p,

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

             }
             
             Part3.
             template 
             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 >的目的就在于维护对象,并不在乎      +
          + 所谓的完全对象.而且我用自己写的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 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& 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.事实上,你可以根据自己的需要.
            重载更多或加入功能成员函数.

  • 相关阅读:
    [转] HP大中华区总裁孙振耀退休感言
    [转] 在win下使用tagList插件
    [转] vim使用技巧如何把所编辑文件的编码方式改为utf8
    vim中文用户手册
    [转] vim使用技巧如何把所编辑文件的编码方式改为utf8
    OSCP Learning Notes Enumeration(3)
    OSCP Learning Notes Scanning(1)
    Burp Suite Report 报告功能
    OSCP Learning Notes Scanning(2)
    OSCP Learning Notes Information Gathering
  • 原文地址:https://www.cnblogs.com/carl2380/p/1941075.html
Copyright © 2011-2022 走看看