zoukankan      html  css  js  c++  java
  • 智能指针

    什么是智能指针

    智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。
    提到智能指针就会想到RAII,但是要明白,智能指针并不等同于RAII,智能指针只是RAII的一种应用。而RAII则是一种规范。
    是一种用来解决问题的思想,定义类来封装资源并进行分配和释放,构造函数完成资源的分配和初始化,析构函数完成资源的清理 智能指针不是指针,是一个类,可以实现:智能管理指针的释放,能够想一个指针一样使用,完成正常的复制拷贝。

    早期的智能指针是auto_ptr,下面来模拟实现它。

    template <class T>
    class AutoPtr
    {
    public:
        T * _ptr;
    public:
        AutoPtr(T* ptr)//构造函数
            :_ptr(ptr)
        {
            cout<<"构造函数"<<endl;
        }
        AutoPtr(AutoPtr<T>&ap)//拷贝构造函数
            :_ptr(ap._ptr)
        {
            cout<<"拷贝构造函数"<<endl;
            ap._ptr = NULL;
        }
        ~AutoPtr()//析构函数
        {
            cout<<"析构函数"<<endl;
            delete[]_ptr;
        }
        AutoPtr<T> & operator = (AutoPtr<T>&ap)//赋值运算符的重载
        {
            if(this!=&ap)
            {
                delete _ptr;//赋值前先释放它
                _ptr = ap._ptr;
                ap._ptr = NULL;//转移管理权
            }
            cout<<"赋值运算符的重载"<<endl;
            return *this;
        }
        T &operator *()//*的重载
        {
            return *_ptr;
        }
        T *operator ->()//->的重载
        {
            return _ptr;
        }
    
    
    };
    
    void test()
    {
        AutoPtr<int> p1(new int (4));
        AutoPtr<int> p2(new int);
        p2 = p1;    
    }
    int main ()
    {
        test();
        return 0;
    }

    这里写图片描述
    但是这个智能指针的设计是失败的,他是存在缺陷的。因为会有管理权的转移。也就是说只要我一赋值,或者拷贝构造就会出现管理权转移的情况。
    所以我们又有了另一种智能指针,scoped_ptr,他的实现原理就是简单粗暴的,既然你会有拷贝和赋值的问题,那我就不让怒拷贝,也不让你赋值。下面我们来模拟实现一下它。

    模拟实现scoped_ptr

    template <class T>
    class ScopedPtr
    {
    protected:
        T * _ptr;
    public:
        ScopedPtr(T *ptr)//构造函数
            :_ptr(ptr)
        {
            cout<<"构造函数"<<endl;
        }
        ~ScopedPtr()//析构函数
        {
            cout<<"析构函数"<<endl;
            delete[]_ptr;
        }
        T &operator *()//*的重载
        {
            return *_ptr;
        }
        T *operator ->()//->的重载
        {
            return _ptr;
        }
    private:
        ScopedPtr(const ScopedPtr<T>&sp);//只声明,不定义
        ScopedPtr<T> &operator = (const ScopedPtr<T>&);
    };
    struct A
    {
        int _a;
        int _b;
    };
    void test()
    {
        ScopedPtr<int> sp1(new int(4));
        //ScopedPtr<int> sp2(p1);//在这里是无法调用拷贝构造函数的,因为他并没有定义,而且还是一个私有的函数,这样又可以保证即使在类外也不会被修改。
        ScopedPtr<A> sp2(new A);
        sp2->_a = 10;
        sp2->_b = 20;
    }
    int main()
    {
        test();
        return 0;
    
    }

    这里写图片描述
    显然scoped_ptr虽然解决了管理权转移的问题,但是,在实际应用中难免会用到拷贝构造函数,和赋值运算符的重载。所以,为了解决这个问题,我们又出现了一个更加强大的智能指针,shared_ptr.
    shared_ptr 是用了共享引用计数的原理,它支持赋值运算符的重载,拷贝构造。下面我们来模拟实现一下它。

    模拟实现shared_ptr

    template<class T>//实现仿函数
    struct Delete//删除的是new出来的对象
    {
        void operator ()(T* ptr)
        {
            delete ptr;
        }
    };
    template<class T>//实现仿函数
    struct DeleteArray//删除数组
    {
        void operator ()(T* ptr)
        {
            delete [] ptr;
        }
    };
    template<class T,class D = Delete<T>>//传一个缺省的参数
    class SharedPtr
    {
    protected:
        T * _ptr;
        int * _countRef;
        D _del;//定制一个删除器
    
    public:
        SharedPtr(T * ptr)//构造函数
            :_ptr(ptr)
            ,_countRef(new int (1))
        {
            cout<<"构造函数"<<endl;
        }
        SharedPtr<T,D>(const SharedPtr<T,D>&sp)//拷贝构造函数
            :_ptr(sp._ptr)
            ,_countRef(sp._countRef)
        {
            cout<<"拷贝构造函数"<<endl;
            ++(*_countRef);//既然调用了拷贝构造函数,肯定是拷贝构造了一个对象,所以直接引用计数加1
        }
        ~SharedPtr()//析构函数
        {
            cout<<"析构函数"<<endl;
            Release();
        }
        SharedPtr<T,D>&operator = (SharedPtr<T,D> & sp)
        {
            if (_ptr != sp._ptr)//判断是否自己给自己赋值或者是指向同一块空间里的对象见得赋值。
            {
                Release();
                _ptr = sp._ptr;
                _countRef = sp._countRef;
                ++(*_countRef);//进行了赋值后就指向了这个对象,所以当前对象的引用计数加1
            }
            return *this;
        }
        T &operator *()
        {
            return *_ptr
        }
        T *operator ->()
        {
            return _ptr;
        }   
    private:
        void Release()
        {
            if(--(_countRef)==0)//当引用计数的值为0时就说明已经没有对象在用这个空间了,就可以释放掉了。
            {
                cout<<_ptr<<endl;//这里主要是为了看一下是否调到了这个函数,,把它的地址打印一下。
                _del(_ptr);//这里就用到了定制的删除器
                delete _countRef;
            }
        }
    
    };
    void test()
    {
        SharedPtr<int> sp1(new int(2) );
        //这里构造了一个对象会调用一次构造函数
        SharedPtr<int> sp2(sp1);
        //sp2对象是用sp1对象拷贝构造出来的所以这里调用了一次拷贝构造函数
        SharedPtr<int> sp3(new int(4));
        SharedPtr<int> sp4(new int(5));
        sp1 = sp4;
        sp2 = sp3;
        SharedPtr<string> sp5(new string);
        SharedPtr<string,DeleteArray<string>> sp6(new string[10]); //这个主要是为了看是否解决了深浅拷贝的问题。因为string类有深浅拷贝的情况
    
    }
    int main()
    {
        test();
        return 0;
    }

    从上面带代码中我们可以看到,这个职能制针可以实现拷贝构造,复制运算符的重载。而且不会有管理权转移或者说不能拷贝之类的情况。下面看一下它的运行结果。
    这里写图片描述

    这样的代码看起来是挺好的,但是他也有一个很严重的缺陷,那就是不能循环引用,如果循环引用的话就会出现内存泄露的问题了。为了解决这个问题我们又相应有了一个weak_ptr 智能指针,他就是专门来解决shared_ptr 中的循环引用的问题的,它不会增加引用计数。但是这个指针只能在shared_ptr中使用。

    存在循环引用的shared_ptr

     #include <iostream>
     #include <boost/shared_ptr.hpp>  //这里用到了boost库,可以自己下载一下源代码
     #include <boost/weak_ptr.hpp>
     using namespace std;     
     class parent;  
     class children;  
     typedef boost::shared_ptr<parent> parent_ptr;  
     typedef boost::shared_ptr<children> children_ptr;    
     class parent  
    {  
     public:  
         ~parent(){
          std::cout <<"destroying parent
    "; 
    }   
     public:  
         children_ptr children;  
     };  
    
     class children  
     {  
     public:  
         ~children() { std::cout <<"destroying children
    "; }  
    
     public:  
         parent_ptr parent;  
     };  
    
    
     void test()  
     {  
        parent_ptr father(new parent());  
         children_ptr son(new children);  
    
         father->children = son;  
         son->parent = father;  
     }  
    
     void main()  
     {  
         std::cout<<"begin test...
    ";  
        test();  
         std::cout<<"end test.
    ";  
    }  
    

    这里写图片描述
    运行该程序可以看到:即使退出了test函数后,由于parent和children对象互相引用,它们的引用计数都是1,不能自动释放,并且此时这两个对象再无法访问到
    <1>一般来讲,解除这种循环引用有下面有三种可行的方法:
    1. 当只剩下最后一个引用的时候需要手动打破循环引用释放对象。
    2. 当parent的生存期超过children的生存期的时候,children改为使用一个普通指针指向parent。
    3. 使用弱引用的智能指针打破这种循环引用。
    虽然这三种方法都可行,但方法1和方法2都需要程序员手动控制,麻烦且容易出错。
    下面主要介绍一下第三种方法和boost中的弱引用的智能指针boost::weak_ptr。
    <2>什么是强引用和弱引用?
    一个强引用是指当被引用的对象仍活着的话,这个引用也存在(也就是说,只要至少有一个强引用,那么这个对象就不会也不能被释放)。boost::share_ptr就是强引用。
    相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它自身存在的时的一个引用。
    弱引用并不修改该对象的引用计数,这意味这弱引用它并不对对象的内存进行管理。

    在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。
    boost::weak_ptr boost::weak_ptr 是boost提供的一个弱引用的智能指针,它的声明可以简化如下:

    weak_ptr

     template<typename T>
      class weak_ptr  
     {  
     public:  
       template <typename Y>  
       weak_ptr(const shared_ptr<Y>& r);  
       weak_ptr(const weak_ptr& r);  
             ~weak_ptr();     
        T* get() const;   
         bool expired() const;   
        shared_ptr<T> lock() const;  
     };     
    

    可以看到,boost::weak_ptr必须从一个boost::share_ptr或另一个boost::weak_ptr转换而来,这也说明,进行该对象的内存管理的是那个强引用的boost::share_ptr。
    boost::weak_ptr只是提供了对管理对象的一个访问手段。
    boost::weak_ptr除了对所管理对象的基本访问功能(通过get()函数)外,还有两个常用的功能函数:
    1. expired() 用于检测所管理的对象是否已经释放;
    2. lock() 用于获取所管理的对象的强引用指针。
    通过boost::weak_ptr来打破循环引用
    由于弱引用不更改引用计数,类似普通指针,只要把循环引用的一方使用弱引用,即可解除循环引用。
    对于上面的那个例子来说,只要把children的定义改为如下方式,即可解除循环引用:

    解决循环引用的问题

    class children  
    
     {  
        public:  
         ~children() 
       {
         std::cout <<"destroying children
    "; 
       }     
       public:  
       boost::weak_ptr<parent> parent;//这里改变为弱指针就好了。  
    };  

    虽然通过弱引用指针可以有效的解除循环引用,但这种方式必须在程序员能预见会出现循环引用的情况下才能使用,也可以是说这个仅仅是一种编译期的解决方案。如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的。因此,不要认为只要使用了智能指针便能杜绝内存泄漏。我们在写代码的时候尽量避免循环引用。

  • 相关阅读:
    求一个整数的划分
    HDU 1028 Ignatius and the Princess III
    HDU1215
    博弈论(2)
    阶乘的位数
    母函数详解
    SpragueGrundy FunctionSG函数博弈论(3)
    图的基本操作邻接表类型
    HDU 1536 SG函数应用
    顺序栈的实现
  • 原文地址:https://www.cnblogs.com/chan0311/p/9427352.html
Copyright © 2011-2022 走看看