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.事实上,你可以根据自己的需要.
            重载更多或加入功能成员函数.

  • 相关阅读:
    CodeForces 706C Hard problem
    CodeForces 706A Beru-taxi
    CodeForces 706B Interesting drink
    CodeForces 706E Working routine
    CodeForces 706D Vasiliy's Multiset
    CodeForces 703B Mishka and trip
    CodeForces 703C Chris and Road
    POJ 1835 宇航员
    HDU 4907 Task schedule
    HDU 4911 Inversion
  • 原文地址:https://www.cnblogs.com/carl2380/p/1941075.html
Copyright © 2011-2022 走看看