1、VC+的异常异常处理作者:未知 来源:月光软件站 加入时间:2005-2-28月光软件站-C+ Exception Handler2001-12-11 异常处理的基本思想是简化程序的错误代码,为程序键壮性提供一个标准检测机制。也许我们已经使用过异常,但是你会是一种习惯吗,不要老是想着当我打开一个文件的时候才用异常判断一下,我知道对你来说你喜欢用return value或者是print error message来做,你想过这样做会导致Memory Leak,系统退出,代码重复/难读,垃圾一堆.吗?现在的软件已经是n*365*24小时的运行了,软件的健壮已经是一个很要考虑的时候了。自序:对写程序
2、来说异常真的是很重要,一个稳健的代码不是靠返回Error Message/return Value来解决的,可是往往我们从C走过来,习惯了这样的方式。仅以本文献给今天将要来临的流星雨把,还好我能在今天白天把这写完,否则会是第4个通宵了;同时感谢Jeffrey大师,没有他的SEH理论这篇文章只能完成一半,而且所有SEH列子的构想都来自他的指导;另外要感谢Scott Meyers大师,我是看他的书长大的;还要感谢Adamc / Darwin / Julian ,当然还有Nick的Coffee内容导读:(请打开文档结构图来读这篇文章。)本文包括2个大的异常实现概念:C+的标准异常和SHE异常。C+标
3、准异常:也许我们了解过他,但你有考虑过,其实你根本不会使用,你不相信,那我问你:垃圾回收在C+中怎么实现?其实不需要实现,C+已经有了,但是你不会用,那么从开始看把。也许很高兴看到错误之后的Heap/Stack中对象被释放,可是如果没有呢?有或者试想一下一个能解决的错误,需要我们把整个程序Kill掉吗? 在C+标准异常中我向你推荐这几章: 以及一个深点的SHE异常: 我要问你你是一个WIN32程序员吗?如果不是,那么也许你真的不需要看这块内容了,SHE是Windows的结构化异常,每一个WIN32程序员都应该要掌握它。SHE功能强大,包括Termination handling和Excepti
4、on handling两大部分,强有力的维护了代码的健壮,虽然要以部分系统性能做牺牲(其实可以避免)。在SHE中有大量的代码,已经在Win平台上测试过了。这里要提一下:在_finally处理中编译器参与了绝大多数的工作,而Exception则是OS接管了几乎所有的工作,也许我没有提到的是:对_finally来说当遇到ExitThread/ExitProcess/abort等函数时,finally块不会被执行。另,我们的代码使用软件异常是比return error message好2*32的方法。另,使用析构函数防止资源泄漏这个节点引用了More effective C+的条款9,用2个列子,讲
5、述了我们一般都会犯下的错误,往往这种错误是我们没有意识到的但确实是会给我们的软件带来致命的Leak/Crash,但这是有解决的方法的,那就是使用“灵巧指针”。如果对照的37条条款,关于异常的高级使用,有以下内容是没有完成的:l使用构造函数防止资源Leak(More effective C+ #10)l禁止异常信息传递到析构Function外 (More effective C+ #11)l通过引用捕获异常 (More effective C+ #13)l谨慎使用异常规格 (More effective C+ #14)l了解异常处理造成的系统开销 (More effective C+ #15)l
6、限制对象数量 (More effective C+ #26)l灵巧指针 (More effective C+ #28)声明:节点: 和 节点:中有大量的关于More effective C+的条款,所以本文挡只用于自我阅读和内部交流,任何公开化和商业化,事先声明与本人无关。C+异常C+引入异常的原因C+新增的异常机制改变了某些事情,这些改变是彻底的,但这些改变也可能让我们不舒服。例如使用未经处理的pointer变的很危险,Memory/Resource Leak变的更有可能了(别说什么Memory便宜了,那不是一个优秀的程序员说的话。),写出一个具有你希望的行为的构造函数和析构函数也变的困难(
7、不可预测),当然最危险的也许是我们写出的东东狗屁了,或者是速度变慢了。大多数的程序员知道Howto use exception 来处理我们的代码,可是很多人并不是很重视异常的处理(国外的很多Code倒是处理的很好,Java的Exception机制很不错)。异常处理机制是解决某些问题的上佳办法,但同时它也引入了许多隐藏的控制流程;有时候,要正确无误的使用它并不容易。在异常被throw后,没有一个方法能够做到使软件的行为具有可预测性和可靠性(这句话不是我说的,是Jack Reeves写的Coping with Exception和Herb Sutter写的Exception-Safe Generi
8、c Containers中的。)一个没有按照异常安全设计的程序想Run 正常,是做梦,别去想没有异常出现的可能,对C程序来说,使用Error Code就可以了,为什么还要引入异常?因为异常不能被忽略。如果一个函数通过设置一个状态变量或返回错误代码来表示一个异常状态,没有办法保证函数调用者将一定检测变量或测试错误代码。结果程序会从它遇到的异常状态继续运行,异常没有被捕获,程序立即会终止执行。在C程序中,我们可以用int setjmp( jmp_buf env );和 void longjmp( jmp_buf env, int value );这2个函数来完成和异常处理相识的功能,但是MSDN中
9、介绍了在C+中使用longjmp来调整stack时不能够对局部的对象调用析构函数,但是对C+程序来说,析构函数是重要的(我就一般都把对象的Delete放在析构函数中)。所以我们需要一个方法:能够通知异常状态,又不能忽略这个通知,并且Searching the stack以便找到异常代码时,还要确保局部对象的析构函数被Call。而C+的异常处理刚好就是来解决这些问题的。有的地方只有用异常才能解决问题,比如说,在当前上下文环境中,无法捕捉或确定的错误类型,我们就得用一个异常抛出到更大的上下文环境当中去。还有,异常处理的使用呢,可以使出错处理程序与“通常”代码分离开来,使代码更简洁更灵活。另外就是程
10、序必不可少的健壮性了,异常处理往往在其中扮演着重要的角色。C+使用throw关键字来产生异常,try关键字用来检测的程序块,catch关键字用来填写异常处理的代码。异常可以由一个确定类或派生类的对象产生。C+能释放堆栈,并可清除堆栈中所有的对象。C+的异常和pascal不同,是要程序员自己去实现的,编译器不会做过多的动作。throw异常类编程抛出异常用throw, 如:throw ExceptionClass(“my throw“);例句中,ExceptionClass是一个类,它的构造函数以一个字符串做为参数。也就是说,在throw的时候,C+的编译器先构造一个ExceptionClass的
11、对象,让它作为throw的值抛出去。同时,程序返回,调用析构。看下面这个程序:#include class ExceptionClass char* name;public:ExceptionClass(const char* name=default name) coutConstruct namename=name; ExceptionClass() coutDestruct nameendl;void mythrow() throw ExceptionClass(my throw);void main() ExceptionClass e(Test); try e.mythrow();
12、catch(.) cout”*”endl; 这是输出信息:Construct TestConstruct my throwDestruct my throw*Destruct my throw (这里是异常处理空间中对异常类的拷贝的析构)Destruct Test=不过一般来说我们可能更习惯于把会产生异常的语句和要throw的异常类分成不同的类来写,下面的代码可以是我们更愿意书写的:.class ExceptionClasspublic:ExceptionClass(const char* name=Exception Default Class)coutException Class Con
13、struct Stringendl;ExceptionClass()coutException Class Destruct Stringendl;void ReportError()coutException Class: This is Report Error Messageendl;class ArguClasschar* name;public:ArguClass(char* name=default name)coutConstruct String:namename=name;ArguClass()coutDestruct String:nameendl;void mythrow
14、()throw ExceptionClass(my throw); ;_tmain()ArguClass e(haha);try e.mythrow(); catch(int)coutIf This is Message display screen, This is a Error!endl;catch(ExceptionClass pTest) pTest.ReportError();catch(.)cout*endl;输出Message:Construct String:hahaException Class Construct StringException Class Destruc
15、t StringException Class: This is Report Error MessageException Class Destruct StringDestruct String:haha使用异常规格编程如果我们调用别人的函数,里面有异常抛出,用去查看它的源代码去看看都有什么异常抛出吗?这样就会很烦琐。比较好的解决办法,是编写带有异常抛出的函数时,采用异常规格说明,使我们看到函数声明就知道有哪些异常出现。异常规格说明大体上为以下格式:void ExceptionFunction(argument) throw(ExceptionClass1, ExceptionClass2
16、, .)所有异常类都在函数末尾的throw()的括号中得以说明了,这样,对于函数调用者来说,是一清二楚的。注意下面一种形式:void ExceptionFunction(argument) throw()表明没有任何异常抛出。而正常的void ExceptionFunction(argument)则表示:可能抛出任何一种异常,当然,也可能没有异常,意义是最广泛的。异常捕获之后,可以再次抛出,就用一个不带任何参数的throw语句就可以了。构造和析构中的异常抛出这是异常处理中最要注意的地方了先看个程序,假如我在构造函数的地方抛出异常,这个类的析构会被调用吗?可如果不调用,那类里的东西岂不是不能被释
17、放了?#include #include class ExceptionClass1 char* s;public: ExceptionClass1() coutExceptionClass1()endl; s=new char4; coutthrow a exceptionendl; throw 18; ExceptionClass1() coutExceptionClass1()processAdoption(); file:/处理收容动物delete pa; file:/删除readALA返回的对象 这个函数循环遍历dataSource内的信息,处理它所遇到的每个项目。唯一要记住的一点是
18、在每次循环结尾处删除ps。这是必须的,因为每次调用readALA都建立一个堆对象。如果不删除对象,循环将产生资源泄漏。现在考虑一下,如果pa-processAdoption抛出了一个异常,将会发生什么?processAdoptions没有捕获异常,所以异常将传递给processAdoptions的调用者。转递中,processAdoptions函数中的调用pa-processAdoption语句后的所有语句都被跳过,这就是说pa没有被删除。结果,任何时候pa-processAdoption抛出一个异常都会导致processAdoptions内存泄漏。堵塞泄漏很容易,void processAd
19、options(istream& dataSource)while (dataSource) ALA *pa = readALA(dataSource);try pa-processAdoption();catch (.) / 捕获所有异常delete pa; / 避免内存泄漏 / 当异常抛出时throw; / 传送异常给调用者delete pa; / 避免资源泄漏 / 当没有异常抛出时但是你必须用try和catch对你的代码进行小改动。更重要的是你必须写双份清除代码,一个为正常的运行准备,一个为异常发生时准备。在这种情况下,必须写两个delete代码。象其它重复代码一样,这种代码写起来令人心
20、烦又难于维护,而且它看上去好像存在着问题。不论我们是让processAdoptions正常返回还是抛出异常,我们都需要删除pa,所以为什么我们必须要在多个地方编写删除代码呢?我们可以把总被执行的清除代码放入processAdoptions函数内的局部对象的析构函数里,这样可以避免重复书写清除代码。因为当函数返回时局部对象总是被释放,无论函数是如何退出的。(仅有一种例外就是当你调用longjmp时。Longjmp的这个缺点是C+率先支持异常处理的主要原因)具体方法是用一个对象代替指针pa,这个对象的行为与指针相似。当pointer-like(类指针)对象被释放时,我们能让它的析构函数调用dele
21、te。替代指针的对象被称为smart pointers(灵巧指针),下面有解释,你能使得pointer-like对象非常灵巧。在这里,我们用不着这么聪明的指针,我们只需要一个pointer-lik对象,当它离开生存空间时知道删除它指向的对象。写出这样一个类并不困难,但是我们不需要自己去写。标准C+库函数包含一个类模板,叫做auto_ptr,这正是我们想要的。每一个auto_ptr类的构造函数里,让一个指针指向一个堆对象(heap object),并且在它的析构函数里删除这个对象。下面所示的是auto_ptr类的一些重要的部分:templateclass auto_ptr public:auto_ptr(T *p = 0): ptr(p) / 保存ptr,指向对象auto_ptr() d
copyright@ 2008-2023 冰点文库 网站版权所有
经营许可证编号:鄂ICP备19020893号-2