C++ 智能指针(及循环引用问题)

来源:互联网 发布:淘宝掌柜直播 编辑:程序博客网 时间:2024/06/06 01:18

何所谓智能指针?
所谓的智能指针就是智能/自动化的管理指指针所指向的动态资源的释放。
智能指针的产生是由于C++中没有内存的自动回收机制,每次new出来的空间都需要delete,而个别情况下,总是无法及时的delete,或者异常导致程序提早退出,造成内存泄漏,故而产生智能指针。

智能指针的发展可分为三个阶段
(1)auto_ptr c++98
(2)scoped_ptr/shared_ptr/weak_ptr boost库
(3)unique_ptr/shared_ptr/weak_ptr c++11

一、auto_ptr
auto_ptr实际上是管理权限的转移,此设计本身带有缺陷,建议什么情况下都不要使用。

#include<iostream>using namespace std;template <typename T>class Auto_ptr{public:    Auto_ptr(T* ptr = 0)        :_ptr(ptr)    {}    Auto_ptr(Auto_ptr<T>& p)        :_ptr(p._ptr)    {        p._ptr = NULL;    }    ~Auto_ptr()    {        delete _ptr;    }    Auto_ptr<T>&operator=(Auto_ptr<T>&p)    {        if (_ptr != p._ptr)        {            if (_ptr)            {                delete _ptr;            }            _ptr = p._ptr;            p._ptr = NULL;        }    }    T& operator*()    {        return *_ptr;    }    T& operator->()    {        return _ptr;    }private:    T* _ptr;};int main(){    Auto_ptr<int> p1(new int(10));    cout << *p1 << endl;    Auto_ptr<int> p2(p1);   //p1拷贝构造出p2    cout << *p1 << endl;    //再次访问p1的空间    system("pause");    return 0;}

auto_ptr的核心思想就是管理权限的转移,通过赋值运算符的重载或拷贝构造时,把p1置空,即将p1开辟的空间的管理权限转交给p2后,我们进行简单操作时并不会有问题:
这里写图片描述

但管理权限转交给p2后,我们再去访问旧指针时,就会出错,这并不是我们所期待的。这里写图片描述

二、scoped_ptr
scoped_ptr又叫 守卫指针,用来防止拷贝,可以解决Auto_ptr的问题。
解决的方法:
(1)拷贝构造和赋值运算符的操作可以只声明,不定义。
(2)将声明为私有,防止定义。

template<class T>class Scoped_ptr{public:    Scoped_ptr(T*ptr)        :_ptr(ptr)    {}    ~Scoped_ptr()    {        if (_ptr)        {            delete _ptr;        }    }    T& operator*()    {        return *_ptr;    }    T* operator->()    {        return _ptr;    }private:    Scoped_ptr(const Scoped_ptr<T>& ptr);    Scoped_ptr<T>& operator=(Scoped_ptr& ptr);private:    T* _ptr;};int main(){    Scoped_ptr<int>p1(new int(1));    Scoped_ptr<int>p2(p1);    return 0;}

这里写图片描述
指针功能明显受到限制,后来人们就不用了;于是更合理的智能指针诞生了。

三、shared_ptr
当auto_ptr和scoped_ptr都不能满足我们实际需求时,这是我们引入shared_ptr,它的主要思想是引入了引用计数,使得多个指针可以指向同一块空间,当最后一个指针释放时才真正释放这块空间。

template<class T>class Shared_ptr{public:    Shared_ptr(T* ptr)        :_ptr(ptr)        ,_count(new int(1))      {}    ~Shared_ptr()    {        Release();    }    Shared_ptr(Shared_ptr<T>& p)        :_ptr(p._ptr)        , _count(p._count)    {        (*_count)++;    }    Shared_ptr<T>operator=(Shared_ptr<T>& p)    {        if (_ptr != p._ptr)        {            Release();  //先释放*this中的指针指向的空间            _ptr(p._ptr);               _count(p._count);            (*_count)++;        }        return *this;    }    T& operator*()    {        return *_ptr;    }    T* operator->()    {        return _ptr;    }    void Release()    {        if (--(*_count) == 0)        {            delete _ptr;            delete _count;            _ptr = NULL;            _count = NULL;        }    }    int GetCount()    {        return *_count;    }    T* GetPtr()const    //获取指针    {        return _ptr;    }private:    T* _ptr;    int* _count;};int main(){    Shared_ptr<int> p1(new int(1));    Shared_ptr<int> p2(p1);    *p1 = 10;    cout << *p1 << endl;    cout << p1.GetCount() << endl;    cout << p2.GetCount() << endl;    system("pause");    return 0;}

这里写图片描述
上面的代码虽然简单的实现引用计数版的简化版智能指针Shared_ptr;但是存在着以下问题:
(1)引用计数更新存在着线程安全。
(2)循环引用的问题。
(3)定制删除器。

四、循环引用:
首先我们来举一个循环引用的例子:

template <typename T>class Node{public:    Node(const T& value)        :_pPre(NULL)        , _pNext(NULL)        , _value(value)    {        cout << "Node()" << endl;    }    ~Node()    {        cout << "~Node()" << endl;        cout << "this:" << this << endl;    }    shared_ptr<Node<T>> _pPre;    shared_ptr<Node<T>> _pNext;    T _value;};void Funtest(){    shared_ptr<Node<int>> sp1(new Node<int>(1));    shared_ptr<Node<int>> sp2(new Node<int>(2));    cout << "sp1.use_count:" << sp1.use_count() << endl;    cout << "sp2.use_count:" << sp2.use_count() << endl;    sp1->_pNext = sp2;    sp2->_pPre = sp1;    cout << "sp1.use_count:" << sp1.use_count() << endl;    cout << "sp2.use_count:" << sp2.use_count() << endl;}int main(){    Funtest();    system("pause");    return 0;}

这里写图片描述
我们可以看出,并没有调用析构函数,也就是没有对空间进行释放。
从上面shared_ptr的实现中我们知道了只有当引用计数减减之后等于0,析构时才会释放对象,而上述情况造成了一个僵局,那就是析构对象时先析构sp2,可是由于sp2的空间sp1还在使用中,所以sp2.use_count减减之后为1,不释放,sp1也是相同的道理,由于sp1的空间sp2还在使用中,所以sp1.use_count减减之后为1,也不释放。sp1等着sp2先释放,sp2等着sp1先释放,二者互不相让,导致最终都没能释放,内存泄漏。

解决方案:使用弱指针—->weak_ptr(不增加引用计数)

template <typename T>struct Node{public:    Node(const T& value)        :_value(value)    {        cout << "Node()" << endl;    }    ~Node()    {        cout << "~Node()" << endl;    }    weak_ptr<Node<T>> _pPre;    weak_ptr<Node<T>> _pNext;    T _value;};void Funtest(){    shared_ptr<Node<int>> sp1(new Node<int>(1));    shared_ptr<Node<int>> sp2(new Node<int>(2));    cout << "sp1.use_count:" << sp1.use_count() << endl;    cout << "sp2.use_count:" << sp2.use_count() << endl;    sp1->_pNext = sp2;    sp2->_pPre = sp1;    cout << "sp1.use_count:" << sp1.use_count() << endl;    cout << "sp2.use_count:" << sp2.use_count() << endl;}int main(){    Funtest();    system("pause");    return 0;}

这里写图片描述

至于weak_ptr具体是如何工作的我们可以点击链接,看大神的解释:
Boost智能指针——weak_ptr