more-effective-c++ 序列2(c++异常处理 汇总篇)

来源:互联网 发布:软件测试论坛网站 编辑:程序博客网 时间:2024/06/05 09:05

异常


之前说过要把异常这一大节做个汇总,这个是汇总篇,尽量用比较精简的话来描述各个小章节的主要的内容,便于自己记忆,也便于大家查阅,可以对自己感兴趣的章节重点去看,毕竟并不是每一个人都喜欢抱着书一章一章的啃的。学会挑自己需要的,也是一种艺术。

个人认为:

9,10,11非常重要,是一个做c++编程应该知道的技术,12,14,15作为了解即可,不必要做过多的研究,13是一个很实用的小tip,也很容易记忆与采纳。

9.利用destructors 避免资源泄露

使用的是局部对象管理资源的思想。以一个局部对象存放[必须自动归还的资源],利用对象的析构函数归还资源。
auto_ptr, scoped_ptr, auto_lock, FilePtr, HandlePtr 都是这样的思想。
以一个简单的FilePtr为例子:
class FilePtr
{
FilePtr(File* f = NULL):m_hFile(f){}
~FilePtr() {if (m_hFile) fclose(f);}
private:
File* m_hFile;
}
void UseFilePtr()
{
FilePtr filePtr(fopen("data.txt", "a+"));
....//deal with filePtr
}
这样如果deal with filePtr这段代码出现异常的时候,文件句柄还是会通过局部对象的析构函数调用,保证资源的安全。
但是如果异常在正在获取资源的过程中,也就是正在抓取资源的类的constructor抛出异常,获取析构函数抛出异常,这样就很难处理了。


10.在构造函数内防止资源泄露

在构造函数内抛出异常,导致对象还没有被完全构造出来。如果是栈对象,那么对象的析构函数将不会执行。如果是堆对象,那么返回的对象指针是NULL。
这样会导致对象内部已经分配的堆内存等等资源得不到释放,导致资源泄露。
由于c++不自动清理构造期间抛出exceptions的对象,所以你必须设计你的constructors,使它们在那种情况下可以完成自我清理。
如果以auto_ptr代替pointer class members,可以免除异常出现时候发生资源泄露,不再需要在destructors内亲自动手释放资源,并且const pointer和non-const
pointers 有一样的处理方式。很好用。


11.禁止异常抛出到destructors之外

异常一旦从析构函数中被抛出,而没有在当时被捕捉,那么之后的代码就不会被调用,直接导致析构函数没有执行完毕。如果后面是一些清理资源的代码,这会
导致资源得不到清理,导致资源泄露。因此,永远不要让exceptions抛出destructors之外。


12.了解掷出一个异常与传递一个参数,调用虚函数之间的差异

12.1 异常对象总是会被copy,如果以by value方式被捕获,那就会被copy两次。而传递一个参数并不一定会复制。
12.2 被掷出异常的对象,其在被捕捉的时候,其允许的转换动作比传递到参数少。主要有两种转换,一个是继承结构的转换,基类捕捉其及其派生类的所有异常,一个是
以void*方式捕捉所有的指针异常
12.3 catch捕捉的顺序采用的是最先匹配,第一个匹配成功便执行,调用虚函数采用的是最佳匹配,被选中的是与对象型别最吻合的函数。


13.以by reference的方式捕捉exceptions

以by pointer的方式捕捉异常,需要考虑是否释放捕获的指针内存。因为那个抛出者可能抛出的是堆内存,也可能是指向一个全局或者静态对象(不可能指向栈对象,栈内存会在
抛出的方法执行之后就释放栈内存)
以by value的方式捕捉异常,需要考虑对象的切割问题,catch(exception ex)会使所有抛出的exception的派生类的对象被切割成为ex对象,而且还会多一次copy。
用by reference能对上述的问题有一个比较完美的解决方案,为何不用呢?


14.明智的运用exception specifications

如果一个函数带有exception specifications,而这个函数抛出了未预期的异常,这会导致unexpected函数被调用,而unexpected函数的默认操作是调用terminate函数
终止程序,这当然不是我们想要的。
遵循的原则:函数A调用函数B,函数B没有exception specifications,那么函数A也不要有exception specifications。
阻止未预期的异常发生是不切实际的事情,但是捕捉所有的未预期异常确实可能的。通过
class UnexpectedException{}
void convertUnexpected()
{
throw UnexpectedException();
}
set_unexpected(convertUnexpected);
通过这样的设定之后,一个函数的所有未预期异常都会以UnexpectedException类型的异常被抛出,我们的调用者在进行exception specifications的时候,需要设定
UnexpectedException异常,这样才会有用。还有一个情况是重新直接throw; 这样未预期的异常会以bad_exception代替,于是任何exception specifications只要含有
bad_exception或者其基类即可。
不过针对上面的情况,我在vs2008进行了测试,并没有发现出现调用convertUnexpected情况,比较奇怪。没在linux下测试过,不敢妄言。


15.了解exception handling的成本

加入异常的代码会膨胀5-10%,整体执行速度会下载这个数字。
和正常的函数返回动作相比,由于出现异常而导致的函数返回,其执行速度可能比正常下慢三个数量级。
只要能够不支持异常,应该明令的告诉编译器,这样它可能对代码做优化。
如果你的代码有性能的问题,请使用分析工具分析,以决定是否对exception支持,如果需要,那么可以考虑换一个对exception处理效率更高的编译器。
原创粉丝点击