智能指针学习总结

来源:互联网 发布:知行家 编辑:程序博客网 时间:2024/05/23 11:26

智能指针是C++面试官最常考的一类问题,今天我就以下几个问题来理解C++中的智能指针。
为什仫要有智能指针?
我们都知道传统的内存分配函数都会配套一定的空间释放函数 ,比如new/delete,malloc/free,但是如果在程序中出现异常而无法执行到空间释放函数,此时就会出现内存泄漏的问题,所以此时就需要一个出作用域就能够自动释放的内存空间的指针–智能指针的提出就是为了解决这个问题。
什仫是智能指针?
智能指针其实并不是一个指针,而是一个类,在构造函数中完成资源的分配以及初始化,在析构函数中完成资源的清理。智能指针其实是RAII的一类实例,所谓的RAII其实就是资源分配以及初始化,定义一个类来实现资源的分配以及初始化。
几个常见的智能指针以及实现?
1>、auto_ptr
auto_ptr的设计特点是管理权限转移,但是它存在设计缺陷,一般不建议使用;
auto_ptr
由上图可以看出,auto_ptr实现权限转移主要体现在拷贝构造和赋值运算符的重载上,当拷贝构造一个新的智能指针的时候,它会使得这个新的智能指针指向已经存在的那块空间,而之前指向这块空间的那个指针就会指向NULL,这也就是auto_ptr的设计缺陷了,当我们在类外再次访问*ap1的时候此时就是错误的操作。
auto_ptr的模拟实现:

//权限转移template<class T>class AutoPtr{public:    AutoPtr(T *ptr=0)        :_ptr(ptr)    {}    ~AutoPtr()    {        delete _ptr;    }    AutoPtr(AutoPtr<T>& ap)   //ap2(ap1)    {        _ptr=ap._ptr;        ap._ptr=0;    }    AutoPtr<T>& operator=(AutoPtr<T>& ap)  //ap2=ap1    {        if(this != &ap)        {            delete _ptr;            _ptr=ap._ptr;            ap._ptr=0;        }        return *this;    }    T& operator*()    {        return *_ptr;    }    T *operator->()    {        return _ptr;    }protected:    T *_ptr;};void TestAutoPtr(){    AutoPtr<int> ap1(new int(10));    cout<<*ap1<<endl;    AutoPtr<int> ap2(ap1);    cout<<*ap2<<endl;    AutoPtr<int> ap3(new int(20));    ap3=ap2;    cout<<*ap3<<endl;}

2>、scoped_ptr/unique_ptr
scoped_ptr是boost库里实现的,unique_ptr是C++11中实现的,这两个为了解决auto_ptr权限转移而出现空指针的问题的解决方案是防赋值防拷贝。
scoped_ptr的防赋值防拷贝必须满足以下几个条件:
1、只给出函数的声明而不实现,防止友元函数对类中的私有函数进行访问;
2、将这两个函数的声明给成私有的,这样是为了防止函数在类外进行函数体的实现;
scoped_ptr的模拟实现:

//防赋值防拷贝#include<string>template<class T>class ScopedPtr{public:    ScopedPtr(T* ptr=0)        :_ptr(ptr)    {}    ~ScopedPtr()    {        delete _ptr;    }    T& operator*()    {        return *_ptr;    }    T* operator->()    {        return _ptr;    }private:    ScopedPtr(const ScopedPtr<T>&);    ScopedPtr& operator=(const ScopedPtr<T>&);protected:    T *_ptr;};void TestScopedPtr(){    ScopedPtr<int> sp1(new int(10));    cout<<*sp1<<endl;    //ScopedPtr<int> sp2(sp1);     //error    ScopedPtr<pair<string,int>> sp2(new pair<string,int>("ScopedPtr",1));    cout<<sp2->first<<endl;    cout<<sp2->second<<endl;    (sp2.operator->())->first="Change";    cout<<(*sp2).first<<endl;}

3>、shared_ptr
shared_ptr解决多个指针指向同一块空间会被析构多次这个问题的方法是引用计数,在shared_ptr的模拟实现中设置了一个_pcount指针用来记录该空间被多少个指针所指向,当然在释放该空间时只要该_pcount不为0则只需要将该pcount空间的内容减减就可以了,只有该指针是最后一个管理该内存的指针时才析构这块空间。
shared_ptr的模拟实现:

//引用计数template<class T>class SharedPtr{public:    SharedPtr(T *ptr=0)        :_ptr(ptr)        ,_pcount(new int(1))    {}    ~SharedPtr()    {        cout<<"析构"<<endl;        if(--GetCount() == 0)        {            cout<<"删除"<<endl;            Destroy();        }    }    SharedPtr(const SharedPtr<T>& sp)   //sp2(sp1)    {        _ptr=sp._ptr;        _pcount=sp._pcount;        //使得this指针指向该空间,而且引用计数加1        ++GetCount();    }    SharedPtr<T>& operator=(const SharedPtr<T>& sp)  //sp3=sp2    {        if(this != &sp)    //不能自己给自己赋值        {            //this指针的引用计数-1.如果是最后一个指向该空间的只能指针则销毁该空间            if(--GetCount() == 0)            {                Destroy();            }            //重新给this指针赋予新的智能指针            _ptr=sp._ptr;            _pcount=sp._pcount;            ++GetCount();        }        return *this;    }    T& operator*()    {        return *_ptr;    }    T *operator->()    {        return _ptr;    }private:    void Destroy()    //销毁该智能指针    {        delete _ptr;        _ptr=0;        delete _pcount;        _pcount=0;    }    int& GetCount()   //获取引用计数的值    {        return *_pcount;    }protected:    T *_ptr;    int *_pcount;};void TestSharedPtr(){    SharedPtr<int> sp1(new int(10));    SharedPtr<int> sp2(sp1);    cout<<*sp1<<endl;    cout<<*sp2<<endl;    SharedPtr<int> sp3(new int(20));    cout<<*sp3<<endl;    sp3=sp2;    cout<<*sp3<<endl;}

在实际生活中最常使用的就是shared_ptr,但是这个智能指针存在几个问题:
1、循环引用的问题;
2、引用计数的更改存在线程安全的问题;
shared_ptr线程安全性分析
3、定制删除器;
shared_ptr循环引用问题:
请看下面的一个例子:

//shared_ptr中循环引用的问题#include<boost/shared_ptr.hpp>#include<boost/weak_ptr.hpp>struct Node{    Node(const int& data=0)        :_data(data)    {}    ~Node()    {        cout<<"~Node()"<<endl;    }    int _data;    boost::shared_ptr<Node> _next;    boost::shared_ptr<Node> _prev;};void Testshared_ptr(){    boost::shared_ptr<Node> n1(new Node(10));    boost::shared_ptr<Node> n2(new Node(20));    cout<<n1.use_count()<<endl;   //1    cout<<n2.use_count()<<endl;   //1    n1->_next=n2;    n2->_prev=n1;    cout<<n1.use_count()<<endl;    //2    cout<<n2.use_count()<<endl;    //2}

我们在定义一个循环链表的时候,它的前后结点指针使用shared_ptr来维护的,在主函数中定义了两个结点指针n1,n2也是shared_ptr类型的,如果我们使得n1->_next=n2;n2->_prev=n1;此时这两个结点都会有两个shared_ptr的指针来维护,更具shared_ptr引用计数的特点,那仫这两个空间的引用计数值都是2,请看下图:
循环引用
如上图所示,当销毁这两块空间的时候会出现内存泄漏的问题,因为该块空间n1分别是由两个同类型的智能指针维护的,当销毁n1时需要n2的引用计数降下来,而销毁n2时需要n1的引用计数降下来,这就导致了循环引用的问题。为了解决这个问题就要提到另一个智能指针weak_ptr了。
weak_ptr是弱指针,它使得引用计数不会增长;而且weak_ptr不可以单独使用,必须与shared_ptr配合使用。
解决循环引用的代码:

#include<boost/shared_ptr.hpp>#include<boost/weak_ptr.hpp>struct Node{    Node(const int& data=0)        :_data(data)    {}    ~Node()    {        cout<<"~Node()"<<endl;    }    int _data;    boost::weak_ptr<Node> _next;    boost::weak_ptr<Node> _prev;};void Testshared_ptr(){    boost::shared_ptr<Node> n1(new Node(10));    boost::shared_ptr<Node> n2(new Node(20));    cout<<n1.use_count()<<endl;   //1    cout<<n2.use_count()<<endl;   //1    n1->_next=n2;    n2->_prev=n1;    cout<<n1.use_count()<<endl;    //1    cout<<n2.use_count()<<endl;    //1}

shared_ptr定制删除器:
shared_ptr的定制删除器主要是针对析构函数来说的,因为在上面实现的shared_ptr中析构函数中只是简单的进行单个指针的释放,但是如果是针对打开的文件或者是指向的连续存储的对象时显然是不满足这种情况,因此对于不同的情况需要不同的析构函数进行处理,而C++标准库中正是采用这种定制删除器的方式;

//定制删除器版本的shared_ptr#include<string>class Fclose{public:    void operator()(void *ptr)    {        fclose((FILE *)ptr);        cout<<"fclose"<<endl;    }};struct Free{    void operator()(void *ptr)    {        free(ptr);        cout<<"free"<<endl;    }};class DefaultDel{public:    void operator()(void *ptr)    {        delete ptr;        cout<<"delete"<<endl;    }};template<class T,class D=DefaultDel>   //默认的定制删除器是DefaultDelclass SharedPtr{public:    SharedPtr(T *ptr=0,D del=DefaultDel())        :_ptr(ptr)        ,_pcount(new int(1))        ,_del(del)    {}    ~SharedPtr()    {        if(--GetCount() == 0)   //当只有最后一个指针维护这块空间的时候才能删除它        {            Destroy();        }    }    SharedPtr(const SharedPtr<T,D>& sp)   //sp2(sp1)    {        _ptr=sp._ptr;        _pcount=sp._pcount;        //使得this指针指向该空间,而且引用计数加1        ++GetCount();    }    SharedPtr<T,D>& operator=(const SharedPtr<T,D>& sp)  //sp3=sp2    {        if(this != &sp)    //不能自己给自己赋值        {            //this指针的引用计数-1.如果是最后一个指向该空间的只能指针则销毁该空间            if(--GetCount() == 0)            {                Destroy();            }            //重新给this指针赋予新的智能指针            _ptr=sp._ptr;            _pcount=sp._pcount;            ++GetCount();        }        return *this;    }    T& operator*()    {        return *_ptr;    }    T *operator->()    {        return _ptr;    }private:    void Destroy()    {        _del(_ptr);  //_del指定不同的删除函数        _ptr=0;        delete _pcount;        _pcount=0;    }    int& GetCount()   //获取引用计数的值    {        return *_pcount;    }protected:    T *_ptr;    int *_pcount;    D _del;};void TestSharedPtrDel(){    SharedPtr<int> sp1(new int(10));    cout<<*sp1<<endl;    SharedPtr<FILE,Fclose> sp2(fopen("test.txt","w"),Fclose());      SharedPtr<string,Free> sp3((string *)malloc(sizeof(string)),Free());  }

在这里就分享结束了~~~

原创粉丝点击