详细分析智能指针shared_ptr存在的循环引用缺陷,以及通过weak_ptr来解决

模拟实现的简单的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思想,而且利用引用计数,解决了浅拷贝的问题。
至于它的问题,需要特定的场景。

创建一个场景:

struct ListNode
{
    ListNode()
        :_data(1)
        , _next(NULL)
        , _prev(NULL)
    {}

    int _data;
    SharedPtr<ListNode> _next;
    Shared_ptr<ListNode> _prev;
};

这是一个双向链表,next和prev都是SharedPtr智能指针,也就是说_next和_prev里不仅有一个ListNode*的指针,还有一个引用计数。
接下来实例化出对象:

SharedPtr<ListNode> cur(new ListNode);
SharedPtr<ListNode> next(new ListNode);

先来说两个SharedPtr对象:cur和next的对象模型:
首先我们创建了两个SharedPtr对象:cur,next,所以两个对象必须都各自有一个ListNode*类的指针和一个引用计数;
这里写图片描述
这两个对象的ListNode*类指针自然指向ListNode对象,引用计数初始化为1;
这里写图片描述

接下来需要说ListNode的对象模型:
由于使用得是new,所以开辟空间的同时还调用了ListNode的构造函数:
这里写图片描述
而在ListNode的构造函数里又分别调用了_next和_prev的构造函数:
这里写图片描述
所以说ListNode对象模型就是:两个SharedPtr对象_prev和_next,对象的_ptr被初始化为NULL,引用计数被初始化为0,另外还有一个数据_data:
这里写图片描述

综上,cur和next对象的_ptr分别指向了一个ListNode对象,而ListNode对象内有两个智能指针_prev和_next,它们分别又有一个_ptr和一个引用计数,_ptr被初始化为NULL,引用计数被初始化为1。
如下图所示:
这里写图片描述

接下来赋值:

    cur->_next = next;
    next->_prev = cur;

在这里是调用了SharedPtr的operator=,把next赋值给了cur的_next,把cur赋值给了next的_prev:
这里写图片描述

接下来通过监视看:

SharedPtr<ListNode> cur(new ListNode);
SharedPtr<ListNode> next(new ListNode);

这两句代码构造了两个对象cur和next,它们分别有_ptr和_refCount,它们的_ptr指向一个ListNode类型的对象,_*refcount初始化为1。
两个ListNode对象内有分别有两个对象_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的对象。
这里写图片描述
这样的话出了作用域调用析构函数,就可以清理干净,不会存在内存泄漏。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值