《Effective C++》资源管理:条款16-条款17

条款16:成对使用new和delete时要采取相同形式

看下面一段代码有什么问题:

string* stringArray=new string[100];
……
delete string;
程序使用了new开辟内存,然后使用delete释放内存,貌似没有什么问题。但是有某样东西完全错误:程序有不明确行为。stringArray内含有100个string对象,但是delete只是删除了一个,剩余的99个没有删除,它们的析构函数没有被调用。


当使用new(即用new动态创建一个对象),有两步。第一步,开辟内存(通过operator new,参考条款49、条款51)。第二步,在开辟的内存处调用构造函数。同样使用delete时也有两步,第一步调用析构函数,第二步释放内存(通过operator delete)。具体可以参考这里。当使用new时,会指定有多少个对象被创建,但是使用delete时没有指定有多少对象被删除(有多少对象的析构函数被调用)。

这个问题可以再简单一些:即被删除的那个指针,所指的是单一对象还是数组。单一对象的内存布局就是单个对象,而数组还要多出一个参数:多少个单一对象(即数组大小)。通常在数组指针(第一个指针,即指向数组头的指针)的前一个字节保存了数组的大小,以前面例子为例:*((int *)stringArray-1)的值为100。但使用delete[]时,会取得这个值,然后依次在头指针后面调用delete。

那么如果一个非数组指针,对它调用delete[]会发生什么?结果未定义,但应该不会让你愉快。

所以应该按照规则来做:如果使用new开辟内存,就使用delete释放。如果使用new[]开辟内存,就使用delete[]释放。


在编写含有指针的class时,上面规则尤为重要。因为析构函数只有一个,但是构造函数可以有多个。构造函数可以使用new或new[]来初始化指针,但是析构函数只能使用一种方法来释放delete/delete[]。

这个规则对于喜欢使用typedef的人也很重要,因为typedef的作者要说清楚,当程序员用new创建typedef类型对象时,应该使用delete/delete[]释放。例如:

typedef string AddressLines[4];//有4行,每行是个string

string* pal=new AddressLines;//这里返回一个string*,相当于new string[4]

那么在释放时,就应该是delete[],而不是delete了。

为了避免这样的错误,最好尽量不使用对数组做typedef动作。在C++的STL中有string、vector等templates(条款54),可以将数组需求降至几乎为零。


条款17:以独立语句将newed对象置入智能指针

在使用智能指针时,应该用独立的语句把新创建的对象指针放入智能指针,否则可能会造成内存泄露。看下面一个例子。

假设有个函数了处理某个程序,它有两个参数,一个是动态分配的Widget,另一个是优先级。

int processWidget(shared_ptr<Widget> pw, int priority);

现在有个函数可以获得优先级

int priority();

如果需要调用时,这样调用:

processWidget(shared_prt<Widget>(new Widget), priority());
虽然上面使用了对象管理资源,但是却可能会有资源泄露。

编译器在产出processWidget之前,必须先核算即将被传进去的各个参数。上面第二个参数是对函数priority()的调用;第一个参数确实有两部分组成,已不是是执行new Widget表达式,另一部分是执行shared_ptr构造函数。所以在调用processWidget之前有三件事:

1、执行priority()函数

2、执行new Widget

3、执行shared_ptr构造函数

C++编译器会以什么样的次序来完成这些事情呢?弹性很大。在Java和C#中,总是以特定的次序来完成这样函数参数的计算,但在C++中却不一定。唯一可以确定的是new Widget在shared_ptr之前调用。但是函数priority排在第几却不一定。假设排在第二,那么顺序就是1、执行new Widget。2、执行函数priority()。3执行shared_ptr构造函数。

如果对函数priority()调用出现异常,那么new Widget返回的指针还没来得及放入shared_ptr中。这样会造成内存泄露。

这是因为在创建资源(new Widget)和把资源放置到资源管理对象两个操作之间发送异常。避免这个问题很简单:将创建Widget和将Widget放入一个智能指针这两步不要分开

shared_prt<Widget> pw(new Widget);
processWidget(pw,priority());

在“跨越语句的各项操作”中,编译器无权重新排列各个语句,所以上面的调用不会发送内存泄露。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值