<<More Effective C++>>读书笔记3: 异常

《More Effective C++》+《Effective C++》,两本经典双剑合璧,必然威力无穷。


    Item M9:使用析构函数防止资源泄漏
1. 隐藏在auto_ptr 后的思想是:用一个对象存储需要被自动释放的资源,然后依靠对象的析构函数来释放资源,这种思想不只是可以运用在指针上,还能用在其它资源的分配和释放上。
2. 资源应该被封装在一个对象里,遵循这个规则,你通常就能避免在存在异常环境里发生资源泄漏。
   [如果使用指针操作堆内存产生了异常,已经申请了的内存将无法正确释放;而使用RAII可以在析构函数中释放]

    Item M10:在构造函数中防止资源泄漏
1. C++确保删除空指针是安全的。
2. C++拒绝为没有完成构造操作的对象调用析构函数原因是:这么做是没有意义的,如果为没有完成构造操作的对象调用析构函数,析构函数如何去做呢?
   仅有的办法是在每个对象里加入一些字节来指示构造函数执行了多少步,然后让析构函数检测这些字节并判断该执行哪些操作。这样的记录会减慢析构函数的运行速度,并使得对象的尺寸变大。C++避免了这种开销,但是代价是不能自动地删除被部分构造的对象。
3. 如果你用对应的auto_ptr对象替代指针成员变量,就可以防止构造函数在存在异常时发生资源泄漏,你也不用手工在析构函数中释放资源,并且你还能象以前使用非const 指针一样使用const指针,给其赋值。
   在对象构造中,处理各种抛出异常的可能,是一个棘手的问题,但是auto_ptr(或者类似于auto_ptr 的类)能化繁为简。它不仅把令人不好理解的代码隐藏起来,而且使得程序在面对异常的情况下也能保持正常运行。
   [这一小节中讲了许多在构造函数中使用异常处理,有一些很好的思想]

    Item M11:禁止异常信息(exceptions)传递到析构函数外
1. 两种情况下会调用析构函数。第一种是在正常情况下删除一个对象,例如对象超出了作用域或被显式地delete。第二种是异常传递的堆栈辗转开解(stack-unwinding)过程中,由异常处理系统删除一个对象。
   [stack-unwinding我还是有点模糊,是在说异常嵌套使用后产生的吗?]
2. 禁止异常传递到析构函数外有两个原因,第一能够在异常转递的堆栈辗转开解(stack-unwinding)的过程中,防止terminate 被调用。第二它能帮助确保析构函数总能完成我们希望它做的所有事情。
   [网上很多人说:析构函数不能抛异常???]

    Item M12:理解“抛出一个异常”与“传递一个参数”或“调用一个虚函数”间的差异
1. 调用函数时,程序的控制权最终还会返回到函数的调用处,但是当抛出一个异常时,控制权永远不会回到抛出异常的地方。
2. 不论通过传值捕获异常还是通过引用捕获都必须进行对象的拷贝操作,因为当对象离开了生存空间后,其析构函数将被调用。如果把对象本身(而不是它的拷贝)传递给catch 子句,这个子句接收到的只是一个被析构了的对象,这是无法使用的。
3. 对异常对象进行强制复制拷贝,哪怕是static,全局对象。
4. 一个被异常抛出的对象(总是一个临时对象)可以通过普通的引用捕获;它不需要通过指向const 对象的引用(reference-to-const)捕获。在函数调用中不允许转递一个临时对象到一个非const 引用类型的参数里,但是在异常中却被允许。
5. 函数传参会发生隐式类型转换,而异常只捕获指定的类型。
6. catch 子句中进行异常匹配时可以进行两种类型转换:
   第一种是继承类与基类间的转换。一个用来捕获基类的catch 子句也可以处理派生类类型的异常。
   第二种是允许从一个类型化指针(typed pointer)转变成无类型指针(untyped pointer),所以带有const void* 指针的catch 子句能捕获任何类型的指针类型异常,当你调用一个虚拟函数时,被调用的函数位于与发出函数调用的对象的动态类型(dynamic type)最相近的类里。你可以这样说虚拟函数采用最优适合法,而异常处理采用的是最先适合法。如果一个处理派生类异常的catch 子句位于处理基类异常的catch 子句后面,编译器会发出警告。
7. 传递参数和传递异常间最后一点差别是catch 子句匹配顺序总是取决于它们在程序中出现的顺序。当调用一个虚拟函数时,被调用的函数位于与发出函数调用的对象的动态类型(dynamic type)最相近的类里。可以说虚拟函数采用最优适合法,而异常处理采用的是最先适合法。

    Item M13:通过引用(reference)捕获异常
1. 为了能让程序正常运行,程序员定义异常对象时必须确保当程序控制权离开抛出指针的函数后,对象还能够继续生存。全局与静态对象都能够做到这一点,但是局部对象不行。
2. 通过指针捕获堆内存异常,将遇到一个哈姆雷特式的难题:是删除还是不删除?这是一个难以回答的问题。所以你最好避开它。
3. 如果你通过引用捕获异常(catch by reference),你就不会为是否删除异常对象而烦恼;能够避开slicing 异常对象;能够捕获标准异常类型;减少异常对象需要被拷贝的数目。

    Item M14 审慎使用异常规格
1. 异常规格是一个应被审慎使用的特性。在把它们加入到你的函数之前,应考虑它们所带来的行为是否就是你所希望的行为。
   [我在调试程序时发现异常规格并不是想象中的那样,大概是因为有的编译器干脆就只支持语法而不实现功能]
   [参考一下这篇文章“C++异常之异常说明”,结论是:因为编译器只支持语法而不实现功能,编译器不会对异常规格进行检测,异常规格更多的是写给函数的用户看。]
    
    Item M15 了解异常处理的系统开销
1. 如果程序的任何一部分使用了异常,其它部分必须也支持异常。否则在运行时程序就不可能提供正确的异常处理。
2. 如果你使用try 块,代码的尺寸将增加5%-10%并且运行速度也同比例减慢。这还是假设程序没有抛出异常。
3. 不同的编译器的异常方法也不同,程序的尺寸将增大5%-10%,它的速度也同比例减慢;如果有大量的异常被抛出,程序运行速度会呈数量级的减慢。
4. 为了使异常开销最小化,尽量采用不支持异常的方法编译程序,把使用try 块和异常规格限制在你确实需要的地方,并且只有在确为异常的情况下才抛出异常。

   [如何使用异常呢?"知乎"中的一个问题“对使用 C++ 异常处理应具有怎样的态度?”,各位大牛众说纷纭,会加强一些理解]
   [虽然我没有完全想通异常,但是异常是一种解决问题的思想,好好体会一下]

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值