将到智能指针首先得讲下智能指针出现的原因,在C++中我们会动态开辟一些空间,而这些空间的释放就得程序员自己动手了,像java这些语言有自动回收机制,而C++就要求程序员自己释放空间,否则会导致内存泄漏(这个后果很严重!!!!)
下面举一些例子来讲下内存失败的场景:
1.在return之后释放空间
void test()
{
int *p = (int*)malloc(100);
//...
//...
return;
free(p);
像这种在程序返回return之后释放空间时会导致内存泄漏。
2.执行流跳转导致释放空间失败
void func1()
{
throw;
}
void test()
{
int* p1 = (int*)malloc(100);
try
{
func();
}
catch(...)
{
delete p1;
throw;
}
delete p1;
}
为了解决这些问题,C++98引入了auto_ptr,不过这个智能指针的是带有缺陷的它还存在这不少问题;在到后来就出现了boost库里的智能指针(scope_ptr、share_ptr、weak_ptr);而c++11在参考了boost库之后出了一个版本的智能指针(unique_ptr、shared_ptr、weak_ptr);
首先来模拟实现一下C++98的auto_ptr
class AutoPtr//实现方法管理权转移
{
public:
AutoPtr(T* ptr = T())
:_ptr(ptr)
{ }
AutoPtr(AutoPtr<T>& P)
{
_ptr = P._ptr;
P._ptr = NULL;//管理权转移
}
AutoPtr& operator = (AutoPtr<T>& P)
{
if (this != &P)
{
_ptr = P._ptr;
P._ptr = NULL;
}
return *this;
}
~AutoPtr()
{
delete _ptr;
}
T& operator *()
{
return *_ptr;
}
T* operator ->()
{
return _ptr;
}
public:
T *_ptr;
};
可以看到这里采用的是管理权转移的思路,那么何为管理权转移呢?即就是将管理这块空间权限给予最后一个指向这块空间的人,这样就会导致将p1 = p2时,p2为NULL,当对p2解引用时会出错!!!!
模拟实现scope_ptr(unique_ptr)//防拷贝
class ScopePtr
{
public:
ScopePtr(T* ptr = T())
:_ptr(ptr)
{ }
~ScopePtr()
{
if (_ptr != NULL)
delete _ptr;
}
T& operator* ()
{
return *_ptr;
}
T* operator-> ()
{
return _ptr;
}
private:
ScopePtr(const ScopePtr& p)//将拷贝构造写为私有
{ }
ScopePtr& operator=(const ScopePtr& p)
{ }
public:
T* _ptr;
};
既然auto_ptr出现了将指针赋给指针时,可能会出现一些错误那么这就得来解决,所以出现了防拷贝(scope_ptr),这就做的比较绝了,既然赋值会出错,那我就不让你赋值(①将拷贝构造、复制运算重载写为私有,在类外面调不到;②只声明不实现);缺点:不能将指针赋值给指针(这样会导致失去指针的一些功能);
模拟实现share_ptr//引用计数
template<class T>
class SharePtr
{
public:
SharePtr(T* ptr = new T())
:_ptr(ptr)
, _refcount(new int())
{
*_refcount = 1;
}
SharePtr(const SharePtr& p)
:_ptr(NULL)
{
_refcount = p._refcount;
_ptr = p._ptr;
(*_refcount)++;
}
SharePtr& operator=(const SharePtr& p)
{
if (this != &p)
{
if (--*_refcount == 0)
{
delete _ptr;
}
_ptr = p._ptr;
_refcount = p._refcount;
(*_refcount)++;
}
return *this;
}
~SharePtr()
{
if (--*_refcount == 0)
{
delete _ptr;
}
cout << "SharePtr析构函数" << endl;
}
T operator* ()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
public:
T* _ptr;
int* _refcount;
};
采用引用计数来控制空间的释放(防止多次释放同一块空间);这样就可以很好的来使用这块空间,解决了scope_ptr的缺陷,但问题又来了,这样的场景会导致内存泄露,如下场景:
struct Node
{
public:
Node()
:_next(NULL)
, _prev(NULL)
{}
public:
SharePtr<Node> _next;
SharePtr<Node> _prev;
};
void testSharePtr()
{
SharePtr<Node> cur(new Node);
SharePtr<Node> next(new Node);
cur->_next = next;
next->_prev = cur;
}
为甚末这里会导致内存泄漏呢?原因是:
cur指向的空间同时被next->_prev管理着,next指向的空间同时被cur->_next管理着,着就很坑了,当“释放”掉cur时cur的*_refcount– ,*_refcount = 1;同时“释放”掉next时next的*_refcount– ,*_refcount = 1;但是cur和next原本指向的空间并没有被释放而是被next->_prev和cur->_next管理着,因此导致内存泄露;故产生了weak_ptr来配合解决这一缺陷(循环引用);
模拟实现weak_ptr(share_ptr的辅助)
template<class T>
class WeakPtr
{
public:
WeakPtr(const SharePtr<T>& p)
:_ptr(p._ptr)
{ }
WeakPtr<T>& operator = (const SharePtr<T>& p)
{
_ptr = p._ptr;
return *this;
}
T* operator->()
{
return _ptr;
}
T operator*()
{
return *_ptr;
}
~WeakPtr()
{
cout << "WeakPtr析构函数" << endl;
}
public:
T* _ptr;
};
实现方法:将他的构造函数和赋值运算符重载通过share_ptr来实现;而对share_ptr的引用计数不采取操作;
场景验证:
struct ListNode
{
public:
ListNode()
:_next(NULL)
, _prev(NULL)
{}
public:
WeakPtr<ListNode> _next;
WeakPtr<ListNode> _prev;
};
void testWeakPtr()
{
SharePtr<ListNode> cur(new ListNode);
SharePtr<ListNode> next(new ListNode);
cur->_next = next;
next->_prev = cur;
}
因为_next 和 _prev并没有对引用计数进行任何操作所以,当释放掉cur和next时_refcount已经减为0,所以真正释放掉了cur和next;