C++程序对内存资源的管理,直接影响到整个程序的性能。内存泄漏可以说是每一个程序员的天敌。然后只要了解C++的特性,养成良好的编程习惯,我们还是可以将这种风险降至最低的。为了使自己编写的程序内存资源管理更加安全可靠,故写此文,总结几个内存资源管理的绝招。
1. 用类指针(point-like)对象代替原始指针(raw point)
大家都清楚,要对于C#,指针是C++的一种特性。使用指针能给我们带来巨大的方便,但如果使用不旦,内存动态分配以后没有及时收回,那么系统的内存资源极有可能泄漏,直至造成程序崩溃。
例如:
class A {……}; //定义一个类型A
定义好一个对象后,我们一般可以用new方法,来创造一个对象实例。
A *pA = new A; //动态分配一个内存,用new方法,相应的就需要调用delete方法,将该内存进行回收.
然而,在大量的程序代码中,直接针对每一个new,使用一个delete的方式,存在以下几个问题:
1) 程序进行维护期间,开发人员若不慎增加循环、条件控制流语句,程序在未触及到delete时,就运行了return;
2) 在new和delete之间,由于运行了某些操作,导致出现异常。
因此,我们不能百分百保证,delete语句总是会执行。为了确保内存资源总是能够回收,我们可以将指针,用一个对象来进行操作,从而利用C++的“析构函数自动调用机制”自动释放内存资源。在标准的程序库中,提供了auto_ptr和tr1::shared_ptr两种指针对象,我们也可以称之为智能指针,采用这种指针对象,当对象的生命周期结束时,其析构函数将自动调用delete。其用法如下:
std::auto_ptr<A> pA(new A);
std::tr1::shared_ptr<A> pA(new A);
这两个指针对象的区别之处在于:多个auto_ptr不能同时指向同一对象;而多个shared_ptr则可以指向同一对象。正是因为这个不同,造成两者在处理copy构造函数和copy assignment操作符时,也不尽相同。auto_ptr的复行为具有特殊性:
std::auto_ptr<A> pA1(new A);
std::auto_ptr<A> pA2(pA1); //pA2指向对象,而pA1为空
pA1 = pA2; //pA1指向对象,而pA2为空
2. 成对使用new与delete时应采用相同的形式
一般来讲,new方法可以用于创建单对对象,也可以用于创建数组对象。同样,针对单个对象与数组,调用delete的形式也不尽相同。如下所示的一个例子,就是new与delete调用形式不匹配,从而造成内存没有成功释放。
string* sArray = new string[100];
……
delete sArray;
由于sArray是一个数组,因而上述的100个string对象,就必须调用100次析构函数才能完全释放内存,因而正确的做法是:
delete [] sArray;
因此,如果你在new表达式中使用[ ],必须在相应的delete表达式中也使用[ ]。如果你在new表达式中不使用[ ],那相应的delete表达式也不能使用[ ]。
3. 在独立的语句中构建智能指针
考虑到如下的函数:
void f(std::tr1::shared_ptr<A> pA(new A), fun());
编译器在调用f函数的具体内容前,首先要处理被传递的各个实参。上述第一个参由两部分构成:执行new A,然后调用std::tr1::shared_ptr构造函数。所在编译器在调用f之前,必须做三件事:
1. 调用函数fun
2. 执行new A
3. 调用tr1::shared_ptr构造函数
然后,对于C++来讲,编译器的执行顺序是不确定的,如果最终的操作顺序是这样:
1. 执行new A
2. 调用函数fun
3. 调用tr1::shared_ptr构造函数
如果是这样,假如在调用函数fun时,发现了异常,此时new A返回的指针将会遗失,而且没有放入tr1::shared_ptr中,所以就可能在调用这个函数时引发内存泄漏。为了避免这种问题,一般采用两条语句来实现上述代码,如下:
std::tr1::shared_ptr<A> pA(new A);
f(pA, fun());