模拟实现的简单的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的对象。
这样的话出了作用域调用析构函数,就可以清理干净,不会存在内存泄漏。