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

首先来回顾一下智能指针的历史:
这里写图片描述
以及在《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的对象。
这里写图片描述
这样的话出了作用域调用析构函数,就可以清理干净,不会存在内存泄漏。

猜你喜欢

转载自blog.csdn.net/han8040laixin/article/details/78646250