Chinaunix首页 | 论坛 | 博客
  • 博客访问: 423487
  • 博文数量: 45
  • 博客积分: 4075
  • 博客等级: 上校
  • 技术积分: 666
  • 用 户 组: 普通用户
  • 注册时间: 2007-04-24 18:09
个人简介

百度网页搜索部高级工程师 我的微博:http://weibo.com/pengwh85

文章分类

全部博文(45)

文章存档

2012年(3)

2011年(1)

2010年(19)

2009年(10)

2008年(12)

我的朋友

分类: C/C++

2009-11-11 16:25:03

条款9:使用析构函数防止资源泄漏

每一个auto_ptr类的构造函数接受一个指向堆对象的指针作为参数,并且在它的析构函数里删除这个堆对象。

资源应该被封装在对象里,遵循这个原则,你通常就能在异常出现的时候避免资源泄漏。

 

条款10:防止构造函数里的资源泄漏

C++只销毁构造完全的对象,所谓构造完全的对象是指它的构造函数被完全执行的对象。

如果把那些声明为指针的类成员替换成它们相应的auto_ptr对象,在发生异常的时候构造函数就可以避免资源泄漏,而且免去了在析构函数中手工释放资源的必要,此外,还可以像处理非常量指针那样以得体的方式来处理常量成员指针。

 

条款11:阻止异常传递到析构函数以外

有两种情况会涉及到析构函数的调用。第一种情况是当对象在“正常”情况下,也就是超出它的作用域范围或者被显式删除的时候,它会被销毁。第二种情况是在异常传递过程中执行到堆栈展开部分,异常处理机制会销毁这个对象。

关于阻止异常传播到析构函数外面,首先,在异常传递进行到堆栈展开部分的时候,它可以阻止terminate函数被调用。其次,它确保了析构函数总是做完它们应该完成的事情。

 

条款12:理解抛出异常与传递参数或者调用虚函数之间的不同

函数调用的时候,控制权最终返回给调用者(除非函数没能正常返回),而当抛出异常的时候,控制权不会返回给抛出者。

C++要求对象必须以拷贝的形式抛出异常,这种对于异常对象的强制性拷贝帮助我们解释了传递形参与抛出异常之间的另外一个不同之处:后者通常要比前者慢的多。

对于函数调用来说,传递一个临时对象给一个非常量的引用参数是不被允许的,但是对于异常却可以这么做。

如果要捕获int类型的异常,必须得是其他的(动态闭合的)被声明为捕获int或者int&(可能还有constvolatile修饰符)的catch语句。

         catch语句针对异常进行类型匹配的时候,有两种类型转换可能会发生。第一种是基于继承的类型转换。第二种可以采用的转换是从一种类型转换到无类型的指针,所以一个针对const void*catch语句可以捕获任何异常类型的指针。

         总结一下,传递一个对象到一个函数或者通过这个对象触发一个虚函数与把一个对象作为异常抛出之间有三个主要的不同之处。第一,异常对象总是要被拷贝,当以传值的方式被捕获的时候,它们被拷贝了两次。而传递给函数参数的对象根本不需要被拷贝。第二,作为异常被抛出的对象较之传递给函数的对象没有那么多的类型转换形式。最后,catch语句是按照它们在代码里出现的顺序被测试的,被执行的是第一个符合条件的catch语句。当一个对象用于触发一个虚函数时,实际被选中的函数是与该对象类型最为匹配的那个函数,即使这个函数在代码里的顺序不是第一个。

 

条款13:通过引用捕获异常

         程序员定义异常对象的时候必须保证在控制权离开抛出指针的那个函数以后这个对象仍然存在。全局对象和静态对象可以完成这件事件,但是写程序的人很容易忘记这个限制。

抛出指针是惟一一种不用拷贝对象就可以传递异常信息的方法。另外一种方法是在堆上创建一个对象,抛出一个指向这个对象的指针。如果异常对象是分配在堆上,那就必须得删除,否则就会有异常泄漏。一些客户可能传递的是全局或者静态对象的地址,而另外一些可能传递的是一个分配在堆上的异常对象的地址,没有办法知道该不该删除。

         通过值捕获异常可以解决上述关于异常对象删除的问题,并且适用于标准异常类型。但是当异常对象被抛出时需要对它们进行两次拷贝。而且它还有可能会产生对象切割的问题,即派生类的异常对象被做为基类异常对象捕获时,派生类臫的一些东西就被切掉了。这些被切割后的对象实际上是基类对象:它们没有派生类的数据成员,而且当它们调用它们的虚函数时,也会解析成基类的虚函数。

         与通过指针捕获异常不同,通过引用捕获异常没有对象删除的问题,而且捕获标准异常也没什么困难。与通过值捕获异常不同,通过引用捕获异常没有对象切割的问题,而且异常对象只被拷贝了一次。

 

条款14:审慎地使用异常规格

         如果一个函数抛出了一个不在它的异常规格列表上的异常,这个错误可以在运行时刻被检测到,然后一个叫做unexpected的特殊函数会被自动调用。异常规格既可以像文档那样帮助你理解代码,同时也是使用异常时的强制性约束机制。unexpected函数在默认情况下会调用terminate函数,而terminate在默认情况下会调用abort

         throw(int)通过异常规格说明只可能抛出int类型的异常,throw()的异常规格表示函数不会抛出任何异常。

         不要把模板和异常规格混合使用。

         编译器针对它们使用上的一致性只做部分的检查,它们应用于模板的时候会有很多问题,一不小心就会违反异常规格,而且默认情况下,违反异常规格将导致程序意外终止。异常规格还有另外一个缺点,即便是有一个更高层的调用者可以处理被抛出的异常,它仍然会导致unexpected函数被触发。

         异常规格以文档化的形式很好地指明了某个函数可能会抛出哪些类型的异常,而在违反异常规格的情况下,默认行为是终止程序的运行。同时,编译器仅仅部分地检测它们的一致性,一不小心就会违反异常规范。而且,它们会阻止高层次的异常处理函数来处理(低层次函数)所导致的unexpected异常,即使高层次函数知道如何处理。

 

条款15:理解异常处理所付出的代价

         为了使与异常相关的开销降到最小,当去除异常支持可行的时候就在去掉对它的支持;限制try块和异常规格的使用,仅在确实需要它们的时候才使用;只在真正异常的情况下才抛出异常。

 

 

阅读(1202) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~