先来看一段代码:
void Test()
{
int* p1 = new int(5);
bool End = true;
//DoSomething
if(End = true)
//此时如果条件成立,直接return,就会造成我们前面用new开辟的空间不能释放,从而导致内存泄漏。
{
return;
}
//DoSomething
delete p1;
}
以上存在在的问题我们可以在return之前释放空间,也可以通过下面抛异常来解决这个问题,但是尽管如此,我们有时也很难避免内存泄漏,这时就引入了智能指针。
void DoSomeThing ()
{
//...
throw 2 ;
//...
}
void Test2 ()
{
int* p1 = new int(2);
//...
try
{
DoSomeThing();
}
catch(...)
{
delete p1 ;
throw;
}
//...
delete p1 ;
}
1:智能指针的发展历史
auto_ptr/scoped_ptr/shared_pr/weak_ptr的设计思想、缺陷
auto_ptr:
template<class T>
class Auto_ptr
{
public:
Auto_ptr(T* ptr=NULL) //构造函数
:_ptr(ptr)
{
}
Auto_ptr(Auto_ptr<T>& ap) //拷贝构造函数
:_ptr(ap._ptr)
//当进行拷贝构造时,直接把ap._ptr赋值为NULL,将管理权交给_ptr
{
ap._ptr = NULL;
}
Auto_ptr<T>& operator=(Auto_ptr<T>& ap) //赋值运算符的重载
{
if (_ptr != ap._ptr) //判断是否是自己给自己赋值
{
if (_ptr) //不为空直接释放-ptr
{
delete _ptr;
}
//把ap._ptr给_ptr,ap._ptr赋值为NULL,将管理权交给_ptr
-ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
~Auto_ptr() //析构函数
{
if (_ptr)
{
delete _ptr;
_ptr = NULL;
}
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
T* _ptr;
};
当我们进行一般简单的使用操作时并不会出现什么问题,但是,因为auto_ptr的设计思想是管理权的转移,当我们利用p1赋值或者拷贝构造给p2时,p1就会置NULL,当我们再去访问旧的指针时就会出错。
scoped_ptr:
为了解决auto_ptr拷贝构造和赋值后对旧指针操作出错问题,我们引入了scope_ptr,它的设计思想就是防拷贝,当用户需要拷贝或者赋值时,程序直接报错。我们模拟实现时,将拷贝构造和赋值运算符重载只声明不实现,并将其设为私有。
template<class T>
class Scoped_ptr
{
public:
Scoped_ptr(T* ptr = NULL)
:_ptr(ptr)
{}
~Scoped_ptr()
{
if (_ptr)
{
delete _ptr;
_ptr = NULL;
}
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
//拷贝构造和赋值运算符重载只声明不实现,并声明为私有防止类外实现
Scoped_ptr<T>(Scoped_ptr<T>& sp);
Scoped_ptr<T>& operator=(Scoped_ptr<T>& sp);
T* _ptr;
};
shared_pr:
设计思想:引用计数,多个指针指向同一块空间,
template<class T>
class Shared_ptr
{
public:
Shared_ptr(T* ptr=NULL) //构造函数,添加引用计数
:_ptr(ptr)
, _refCount(new int(1))
{}
Shared_ptr<T>(Shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _refCount(sp._refCount)
{
++(*_refCount);
}
Shared_ptr<T>& operator=(Shared_ptr<T>& sp)
{
if (_ptr != sp._ptr)
{
if (--(*_refCount) == 0)
{
delete _ptr;
delete _refCount;
}
_ptr = sp._ptr;
_refCount = sp._refCount;
(*_refCount)++;
}
return *this;
}
Shared_ptr<T>& operator=(Shared_ptr<T>& sp)
{
swap(_ptr, sp._ptr);
swap(_refCount, sp._ptr);
return *this;
}
~Shared_ptr()
{
if (--(*_refCount) == 0)
{
delete _ptr;
delete _refCount;
}
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
T* Getptr()
{
return _ptr;
}
private:
T* _ptr;
int* _refCount;
};
然而,shared_ptr存在循环引用的缺陷问题:
struct ListNode
{
Share_ptr<ListNode> _prev;
Share_ptr<ListNode> _next;
ListNode()
:_prev(NULL)
,_next(NULL)
{}
};
void Test()
{
Share_ptr<ListNode> cur = new ListNode;
Share_ptr<ListNode> next = new ListNode;
cur->_next = next;
next->_prev = cur;
}
运行该程序可以看到,即使退出了test函数后,由于cur和next对象互相引用,它们的引用计数都是1,不能自动释放,并且此时这两个对象再无法访问到。这就引起了内存泄漏。
一般来讲,解除这种循环引用有下面有三种可行的方法:
1、当只剩下最后一个引用的时候需要手动打破循环引用释放对象。
2、当cur的生存期超过next的生存期的时候,next改为使用一个普通指针指向cur。
3、使用弱引用的智能指针打破这种循环引用。
虽然这三种方法都可行,但方法1和方法2都需要程序员手动控制,麻烦且容易出错。这里主要介绍一下第三种方法。
weak_ptr:解决Shared_ptr的循环引用问题,只负责管理,不负责释放
template<class T>
class Weak_ptr
{
public:
Weak_ptr()
:_ptr(NULL)
{}
Weak_ptr(const Shared_ptr<T>& sp)
:_ptr(sp._ptr)
{}
T & operator*()
{
return *ptr;
}
T & operator->()
{
return _ptr;
}
private:
T* _ptr;
};