MoreEffectiveC++笔记 5异常

普通的代码在异常出现的时候,出现一般考虑不到的致命错误。

1使用析构函数防止资源泄露

当我们使用指针时要时刻记得使用delete释放资源,但是当异常发生,程序末尾的delete语句可能并未被执行。所以必须使用try语句来对可能异常的语句进行捕获。

while(dataSource){
	MyClass *p = readMyClass();
	try{
		p->dosomething();
	}catch(...){
		delete p;
		throw;
	}
	delete p;//旧版本没有final语句的话只能把delete语句写两遍。

对这种情形进行优化的方案就是使用智能指针,让智能指针对象的析构函数对其中保存的raw指针进行释放。

while(dataSource){
	auto_ptr<MyClass> pa(readMyClass(dataSource));
	pa->dosomething();
}

除了指针以外,所有C-like接口申请释放资源的代码都可以用智能指针的思路将释放的逻辑封装在一个对象的析构函数里面,由析构函数完成资源释放。

2构造函数中防止资源泄露

当一个类聚合其他多种对象时,该类的构造函数刚构造了其中几个对象就抛出异常,那么已经构造的对象将没有代码负责释放,导致了资源泄露。

class A{
public:
	A(const string& bname,const string& cname)
		:pb(new B(bname)),pc(new C(cname))
	{}
	~A(){
		delete pb;
		delete pc;
	}
private:
	B* pb;
	C* pc;

如果new C构造过程出现了问题抛出异常,那么之前构造好的B对象(初始化列表构造顺序是pb和pc在类体内的定义顺序)将会无法被释放。
基于try代码块可以有多种方法解决这种问题,不过都会有一些缺点和限制。最终的结论就是让pb、pc定义成上一节的智能指针。即便A对象未能构造完成,其中包含的智能指针对象也能够正常析构,完成对raw指针的释放操作。

3禁止异常传递到析构函数外面

第一个原因是当异常没有在析构函数内捕获,就会传递到析构函数外面,这时terminate会自动调用终止程序。第二个原因是当异常被抛出,那意味着析构函数内的末尾的某些代码可能没有被执行完毕。所以在析构函数内用try…catch来保证异常的正确处理。

4抛出异常、传递参数、调用虚函数三者的差异

throw的对象会被传递到catch语句中,这个过程看起来与调用函数进行传参是非常接近的。但是底层操作是有差别的。

  • 首先catch执行完毕后程序一定不会回到抛出异常的地方。
  • 其次抛出的异常对象无论是传值、传引用还是全局静态类型(就是说无论是否在原先的位置被释放),在catch中永远会比函数传参多一次复制的过程,如果传值那么拷贝两次,如果传引用会拷贝一次,这会导致catch的过程回避函数调用慢。而且拷贝的过程会调用对象的静态类型对应的拷贝构造函数,例如下面意图重新抛出的语句,假设接受到了Widget的一个子类异常:
class subWidget: public Widget{...};
...
catch(Widget& w){
	throw;
}
catch(Widget& w){
	throw w;
}

看起来两种catch是一样的,不过由于静态类型的问题,第一个throw的类型还会是用子类copy得到的同一个子类类型对象,不会再进行一次复制;第二个由于显式写出是对象w,而对象w静态类型是widget,这时throw会执行拷贝,用的就是widget类型的拷贝函数了。

  • 异常对象是一个临时对象,但是在catch捕获不需要被const修饰,而临时变量传入到普通函数时形参必须要用const修饰。
  • catch过程一般不支持内置类型隐转,比如抛出整形int,不能被浮点double形参进行捕获,但是有继承关系的类仍然可以转换,而且所有指针也可以用无类型指针void* 指针进行捕获。
  • 捕获顺序是catch字段的从上到下匹配,而虚函数多态的机制是会调用最接近该对象的类中的最优成员函数。

5通过引用捕获异常

如果我们使用指针捕获异常,可能会出现指向局部对象的地址的错误情况;如果指向了一个堆上的异常对象,我们无法决定是否应该在catch子句中对这个对象进行释放;C++四种标准异常(bad_alloc, bad_cast, bad_typeid, bad_exception)。
捕获异常在使用基类形参捕获的时候,由于sliced的原因会导致抛出的派生异常对象变成sliced基类对象,不再使用派生类定义的成员函数。
引用捕获异常就不会出现slice的问题,可以用基类引用捕获派生类异常对象;而且传引用可以减少拷贝次数。

6审慎使用异常规格

异常规格给出函数可能抛出的异常,但是当内部抛出了未说明的异常时就会导致terminal的调用。所以我们需要审慎使用异常规格。

  • 不要混用模板和异常规格。
  • 函数调用了其他没有异常规格的函数,那么自身也不要写异常规格。
  • 替换unexpected为bad_exception:
void convertUnexpected(){
	throw;
}
set_unexpected(convertUnexpected);

7了解异常处理的系统开销

减少try语句;使用不支持异常处理的编译方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值