一:内存泄漏的问题
考虑如下的程序
void func() { ClassA *a = new ClassA; ... delete a; }
应该使用delete语句以保证new分配的空间一定会被释放。我几乎总会忘记delete语句,尤其函数要写return语句时,更容易忘记。
另外即使你加上了delete语句,你也无法完全避免空间无法释放的问题(这种问题的统一叫法:内存泄漏)。
void func() { ClassA *a = new ClassA; ... //产生异常 delete a; }
如果在这个函数执行时发生异常,依照C++处理异常的规则,会立刻退出当前函数,返回到其调用函数以期望异常能够被捕捉并处理。由于函数直接结束,delete语句甚至都没有机会执行,空间仍然释放不了。当然可以采用如下的方法
void func() { try{ ClassA *a = new ClassA; ... //发生异常 ... } catch(...){ //捕捉该异常 delete a; throw; //重新抛出该异常 } }
但如果你的程序可能抛出多个异常,或者使用了多个new来分配空间呢?会增加代码量。C++为了解决这个问题,设计了一种智能指针auto_ptr,虽然在C++11中已经被淘汰,但是它的思想值得学习一下。
二:使用auto_ptr解决异常发生时的内存泄漏问题
异常发生时,会自动在当前函数中查找捕捉该异常的语句。如果在当前函数中没有找到,就要清除当前函数栈空间中所有的局部变量然后在其调用函数找。局部变量的清除方法:
- 基本变量:直接释放所占用的空间
- 类:调用其析构函数
普通的指针只是基本变量,只保存内存空间的地址。于是在自身被删除时,所指向的空间的地址也没有了。系统无法再找到那块内存(当然是只有一个指针指向该空间),也就无法释放。
那,不如把指针包装到类中,在其析构函数中,将内存空间释放即可。C++添加了auto_ptr实现这种想法。本文最后是auto_ptr的定义,这里看下auto_ptr的仅有的属性和析构函数。
私有元素:
private:
_Ty *_Myptr; // the wrapped object pointer
这个指针就是auto_ptr用来保存内存空间地址的地方。
析构函数
~auto_ptr()
{ // destroy the object
delete _Myptr;
}
delete语句可以确保这块空间在auto_ptr对象被清除时被释放掉。这可以解决异常发生时内存泄漏的问题。
于是,下面的程序也不再需要delete语句了。
void f() { std::auto_ptr<ClassA> i_ptr(new ClassA); ... }
三:auto_ptr的所有权概念
除了确保不会发生内存泄漏问题之外,auto_ptr还定义了一种严格的概念:拥有权(OwnerShip)。auto_ptr对于它所指的对象(或者说内存空间)有拥有权,并且不应该与任何其他的auto_ptr共享这个对象。即不应该出现多个auto_ptr指向一个对象的情况。当然,这种情况本身不可避免,因为auto_ptr在通过指针来创建时,是无法知道该指针是否已被其他的auto_ptr对象所使用。可以看它的构造函数如下:
构造函数一:
explicit auto_ptr(_Ty *_Ptr = 0) throw(): _Myptr(_Ptr){} // construct from object pointer
使用一个普通指针初始化auto_ptr,不可以隐式调用,另外在这个构造函数中,并不存在对_Ptr进行检查的机制。 如下所示
int *pi1 = new int;
auto_ptr<int> ap1 = pi1; //error,explicit初始化构造函数无法被隐式调用 auto_ptr<int> ap2(pi1); //ok, ap1指向pi1所指空间 auto_ptr<int> ap3(pi1); //还是ok,直接通过new操作符返回的指针初始化,该空间被初始化为23
所以在使用时,我们应该竭力避免这种使用一个指针初始化两个auto_ptr对象的情况。既然你要用auto_ptr对象指向你的对象,又何必再用普通指针呢?不如直接这样
auto_ptr<int> p(new int(234));
对象p指向这块新开辟的空间,以后也只使用p来指代它。
来说回OwnerShip。由于一个对象(或内存空间)只能被一个auto_ptr对象所指,要达到这样的要求,复制构造函数和赋值操作符又当如何操作,直接复制_Myptr对象吗?这样其实还是会导致多个auto_ptr指向同一个对象的情况。解决办法很简单,就是让这两类函数做好移交所有权的工作。如下所示
构造函数二(复制构造函数):
template<class _Other> auto_ptr(auto_ptr<_Other>& _Right) throw(): _Myptr(_Right.release()){}// construct by assuming pointer from _Right
auto_ptr::release()功能为返回自身所指对象的地址,并将自身_Myptr设为空指针。源码如下
_Ty *release() throw() { // return wrapped pointer and give up ownership _Ty *_Tmp = _Myptr; _Myptr = 0; return (_Tmp); }
那么复制构造函数的作用就是,新的auto_ptr指向_Right所指的对象,然后把_Right设置成空指针(即不指向任何对象)。就类似于交出了该被指对象的所有权。同时这个复制构造函数的目的也是为了让派生类的指针可以转换成基类的指针。
struct A{ int a; A(int _a=0):a(_a){} }; struct B:A{ char b; B(int _a=0, char _b='