一、基本概念
1、什么是内存泄漏
内存泄漏是指程序中已动态分配的堆内存由于某种原因未释放或无法释放,导致程序运行速度减慢甚至系统崩溃等问题。
内存泄漏分为以下两类:
(1)堆内存泄露:我们经常说的内存泄漏就是堆内存泄露,在堆上申请了资源,在结束使用的时候,没有释放归还给操作系统,从而导致该块内存不会再次被使用。
(2)资源泄露:通常指的是系统资源,比如socket,文件描述符等,因为这些在系统中都是有限制的,如果创建了而不归还,久而久之,就会耗尽资源,导致其他程序不可用。
2、什么是智能指针?
C#和java中都有自动垃圾回收机制,GC可以管理分配的堆内存,在对象失去引用时自动回收,因此,在C#和java中,内存管理不是大问题。
C++没有垃圾回收机制,必须自己去释放动态分配的堆内存,否则就会内存泄漏。
解决这个问题最有效的方法就是使用智能指针。在智能指针对象中有一个指针,此指针存储的是动态创建对象的地址,用于生存期控制,能够确保智能指针对象离开所在作用域时,自动正确地销毁动态创建的对象,防止内存泄漏。
只要我们能够正确使用智能指针就不会担心内存泄漏的问题,因为智能指针可以自动释放分配的内存。
智能指针和裸指针的用法类似,只是不需要手动释放内存,而是通过智能指针自己管理内存的释放,这样就不用担心忘记释放内存从而导致内存泄漏了。
3、使用裸指针存在的问题:
(1)难以区分指针指向的是单个对象还是一组对象;
(2)使用完指针之后无法判断是否应该销毁指针,因为无法判断指针是否拥有指向的对象;
(3)在已经确定需要销毁指针的情况下,也无法确定是用delete关键字删除,还是有其他特殊的销毁机制,例如通过将指针传入某个特定的销毁函数来销毁指针所指资源。
(4)即便已经确定了销毁指针的方法,由于(1)的原因,仍然无法确定到底是用delete还是delete[]销毁一组对象。
(5)假设上述问题都解决了,也很难保证在代码的所有路径中(分支结构,异常导致的跳转),有且仅有一次销毁指针操作;任何一条路径遗漏都可能导致内存泄漏,而销毁多次会导致未定义行为。
C++11提供了4种智能指针:auto_ptr,unique_ptr,shared_ptr和weak_ptr;使用时需要引用头文件<memory>
二、auto_ptr详解
1、auto_ptr源代码
template<class _Tp>
class auto_ptr
{
public:
typedef _Tp element_type;
private:
_Tp* _M_ptr;
public:
explicit auto_ptr(_Ty* _P=0):_M_ptr(_P){}
~auto_ptr() {delete _M_ptr;}
//返回指向被管理对象的指针
_Ty* get()const
{
return _M_ptr;
}
//替换被管理对象
void reset(_Ty* _P=0)
{
delete _M_ptr;
_M_ptr=_P;
}
//释放被管理对象的所有权
_Tp* release()
{
_Tp* _tmp=_M_ptr;
_M_ptr=nullptr;
return _tmp;
}
//访问被管理对象
_Tp& operator*()const
{
return *_M_ptr;
}
_Tp* operator->()const
{
return _M_ptr;
}
auto_ptr(auto_ptr& _Y):_M_ptr(_Y.release()) { }
auto_ptr& operator=(auto_ptr& _Y)
{
if(&_Y!=this)
{
reset(_Y.release());
}
return *this;
}
};
2、auto_ptr使用分析
(1)构造函数与析构函数
auto_ptr在构造时获取对某个对象的所有权,在析构时释放该对象。
(2)辅助函数
A、get返回指向被管理对象的指针;
B、重载operator*(取值),operator->(成员访问),访问被管理对象;
C、reset替换被管理对象;
D、release释放被管理对象的所有权。
(3)拷贝构造与赋值
C98还没加入右值引用,也没有move语义。
auto_ptr的拷贝构造和赋值重载陷入困境:浅拷贝和浅赋值,指针重复释放;
深拷贝和深赋值,语义矛盾。最终选择所有权的转移。
auto_ptr要求其对裸指针的完全占有性。也就是说一个裸指针不能同时被两个以上的auto_ptr所拥有。那么,在拷贝构造或赋值操作时,我们必须作特殊的处理来保持这个特性。auto_ptr的做法是“所有权转移”,即拷贝或赋值的源对象将失去对裸指针的所有权,所以,与一般拷贝构造函数,赋值函数不同,auto_ptr的拷贝构造函数,赋值函数的参数为引用而不是常引用。
当然,一个auto_ptr也不能同时拥有两个以上的裸指针,所以,拷贝或赋值的目标对象将先释放其原来所拥有的对象。
(4)auto_ptr的析构函数的问题:
析构函数删除指针用的是delete,而不是delete[],所以auto_ptr不能管理一个数组指针。
3、总结
auto_ptr主要有三大问题:
(1)赋值和复制操作会改变资源的所有权,不符合人的直觉。
(2)在STL容器中使用auto_ptr存在重大风险,因为容器内的元素必须支持可复制和可赋值。
(3)不支持对象数组的操作。
在C++11中已经舍弃auto_ptr。