c++:分析智能指针shared_ptr存在的循环引用的缺陷

来源:互联网 发布:树莓派zerow ubuntu 编辑:程序博客网 时间:2024/06/08 12:36

首先来回顾一下智能指针的历史:
这里写图片描述
以及在《c++:分析智能指针与发展历史》一文中模拟实现的简单的shared_ptr:

template <class T>class SharedPtr{public:    SharedPtr(T* ptr)        :_ptr(ptr)        , _refCount(new int(1))    {}    SharedPtr(SharedPtr<T>& sp)        :_ptr(sp._ptr)        , _refCount(sp._refCount)    {        ++(*_refCount);    }    SharedPtr<T>& operator=(SharedPtr<T>& sp)    {        if (_ptr != sp._ptr)        {            if (--(*_refCount) == 0)            {                delete _ptr;                delete _refCount;            }            _ptr = sp._ptr;            _refCount = sp._refCount;            ++(*_refCount);        }        return *this;    }    T& operator*()    {        return *_ptr;    }    T* operator->()    {        return _ptr;    }    ~SharedPtr()    {        if (--(*_refCount) == 0)        {            delete _ptr;            delete _refCount;        }    }private:    T* _ptr;    int* _refCount;};

shared_ptr不仅体现了RAII思想,而且多了一个int*型成员变量,用来引用计数,解决了浅拷贝的两个问题。(具体看我之前写的c++写时拷贝对引用计数有解析,这里我只分析shared_ptr的循环引用的问题)

那么这么完美的智能指针,到底有什么问题呢?

我来创建一个场景:

struct ListNode{    ListNode()        :_data(1)        , _next(NULL)        , _prev(NULL)    {}    int _data;    SharedPtr<ListNode> _next;    Shared_ptr<ListNode> _prev;};

这是一个简单的双向链表类。

void TestSharedPtrError(){    SharedPtr<ListNode> cur(new ListNode);    SharedPtr<ListNode> next(new ListNode);    cur->_next = next;    next->_prev = cur;}

接下来,我利用SharedPtr实例化出对象,如图:
这里写图片描述
首先 SharedPtr cur(new ListNode);
SharedPtr next(new ListNode);
这两句代码构造了两个对象cur和next,它们分别有_ptr和_refcount,它们的_ptr指向一个ListNode类型的对象,_*refcount为1。
两个对象内有分别有两个对象_next和_prev它们的_ptr初始化为NULL,_refcount为1(参见构造函数)请看监视:
这里写图片描述
这两段代码:
cur->_next = next;
next->_prev = cur;
调用了operator=,因为_next和_prev已经被初始化。
所以说,在operaotor=函数里,我已经标明了指向关系,并且现在cur和next的引用计数都变为2。
请看监视:
这里写图片描述

    出了cur和next作用域,会调用析构函数,那么会先调用_next的析构函数,再调用_cur的析构函数,但是这会陷入一个循环,因为释放关系中你的释放需要我,我的释放需要你,谁都不肯放手,自然会出现内存泄漏。    如何解决问题,引入了wead_ptr,这里我也是简单的模拟实现它:
template <class T>class WeakPtr{public:    WeakPtr(const SharedPtr<T>& sp)        :_ptr(sp._ptr)    {}    WeakPtr<T>& operator=(SharedPtr<T>& sp)    {        _ptr = sp._ptr;        return *this;    }private:    T* _ptr;};

然后对以上代码进行修改:

struct ListNode{    ListNode()        :_data(1)        , _next(NULL)        , _prev(NULL)    {}    int _data;    WeakPtr<ListNode> _next;    WeakPtr<ListNode> _prev;};

那么

void TestSharedPtrError(){    SharedPtr<ListNode> cur(new ListNode);    SharedPtr<ListNode> next(new ListNode);    cur->_next = next;    next->_prev = cur;}

就是Shraed_ptr的对象赋值给WeakPtr的对象。
这里写图片描述
这样的话出了作用域调用析构函数,就可以清理干净,不会存在内存泄漏。

原创粉丝点击