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