异常处理的优缺点

laomai 2005-12-07 06:38:30
由于MoFanYingXiong (模范英雄)在http://community.csdn.net/Expert/topic/4444/4444318.xml帖子里攻击C++的异常处理机制,且题目有引起不必要的争论之嫌,我将该贴强制结贴。但为了保证有一个良好的学术讨论环境并以理服人,在此重新开贴。欢迎大家将自己对异常机制的理解写出来,无论你赞成还是返回异常,希望都言之有理,言之有物,呵呵。
另:再次强调本版版规,请不要发”mark、up、顶“之类的回复,因为那样会影响阅读的连贯性,如果有饼子犯规,将被扣信誉。
...全文
1313 26 打赏 收藏 转发到动态 举报
写回复
用AI写文章
26 条回复
切换为时间正序
请发表友善的回复…
发表回复
nicknide 2006-01-08
  • 打赏
  • 举报
回复
比起错误传递, 异常有时候速度更快。

防止频繁的比较中间结果.
Wolf0403 2005-12-26
  • 打赏
  • 举报
回复
try {
auto_handler<R1> h1 = getR1();
auto_handler<R2> h2 = getR2();
auto_handler<R3> h3 = getR3();
}
catch ( ... )
{}
fixopen 2005-12-26
  • 打赏
  • 举报
回复
由于异常时可以携带信息的,也就是说,异常可以表明异常的状况。
所以catch(...)这种方式使用异常并不被鼓励,这样丢失了所有的异常信息。
xiaocai0001(萧筱雨)第二次给出的异常处理的基本范式是常用的,也是有效的。而MoFanYingXiong(模范英雄)给出的异常机制显然是人为丑化的一种机制。

zcz0918()
>异常处理这个东西如果考虑的太多会破坏代码的风格而变得很丑

如果把异常看作接口的一部分考虑,它是必须的,代码风格也不过是为了保证正确性的一种约定而已。当然类似于Java 的Checked Exception这种方式的代码很难做到异常中立。但是,如果不是checked,异常很有可能被忽略。
zcz0918 2005-12-23
  • 打赏
  • 举报
回复
异常处理这个东西如果考虑的太多会破坏代码的风格而变得很丑
MoFanYingXiong 2005-12-19
  • 打赏
  • 举报
回复
>如果DoFirstThing()已经失败, 那么继续DoSecondThing()一般来说已经没有什么意义了.

事情并不像你所说的那样。举个实际的例子:
程序生成了三个临时文件“tmp1”、“tmp2”、“tmp3”,在退出的时候要删除这三个文件。
如果删除“tmp1”失败了,就不用删除“tmp2”和“tmp3”了吗?
再有,程序要求将每一个文件的删除结果记录到日志中。记录日志也可能会失败,但是这个失败可以忽略。
这样,最好就是写成下面的代码:
if(DelFile("tmp1"))
{
WriteLog("删除tmp1成功");
}
else
{
WriteLog("删除tmp1失败");
}

if(DelFile("tmp2"))
{
WriteLog("删除tmp2成功");
}
else
{
WriteLog("删除tmp2失败");
}

if(DelFile("tmp3"))
{
WriteLog("删除tmp3成功");
}
else
{
WriteLog("删除tmp3失败");
}

除此之外,我实在想不到,如果用异常处理会有什么更好的写法。
xuelong_zl 2005-12-19
  • 打赏
  • 举报
回复
异常处理的作用,很大程度上是出于设计上的考虑,还不单单是语法实现上的问题
xuelong_zl 2005-12-19
  • 打赏
  • 举报
回复
很同意楼上的看法,这句“通常要求我们对事物要有一个总体上的把握。”,喜欢中.....
whyglinux 2005-12-19
  • 打赏
  • 举报
回复
事物的存在都有其存在的道理,任何事物都是矛盾的统一体,C++中的异常处理也不例外。

由于人们的立场或者视野不同,对于同一事物的看法往往不会是完全相同的,有时甚至会完全相反。由此,就会产生争论。

我们在讨论一个关于程序或者语言问题的时候,一般不存在立场不同的问题,所以产生争论的原因通常是出于视野角度的差别。视角越窄,对事物信息的把握也就越少,这样得出的结论往往是片面的居多;相反,视角越宽,对事物信息的占有越多,也就越助于我们对事物的认知。因此,在对某一事物进行判断的时候要得出全面和令人信服的结论,通常要求我们对事物要有一个总体上的把握。

“是药三分毒”,药可治病,也可致病,说的就是事物矛盾的两重性。因为药有毒而否认药的价值从而不用它,这达不到治病的目的。矛盾的两重性还告诉我们,对事物进行完全肯定或者否定通常是不正确的,因为还存在着一个科学运用的问题。

下面回到现实中来进行分析。

MoFanYingXiong(模范英雄)等人的反对异常处理的观点之所以站不住脚,是因为他们把异常处理看成了是一种取代传统错误处理方式的完全替代方案,从而“发现”了异常处理的种种不足或者不便之处(与传统错误处理方式相比较),然后借此对异常处理存在的合理性进行否定。这说明了他们对于异常处理的理解还比较片面。

其实C++中的异常处理只是另一种形式的错误处理方式,和传统的错误处理方式并不排斥,他们是一种“共存”的关系。只是因为在某些方面或者某些时候,使用异常处理方式比传统的错误处理方式更具优越性,所以异常处理才有了使用的必要,它的存在也才是合理的。

消除对异常处理的偏见的方法就是对异常处理进行再学习和再认识,从总体上把握异常处理,明白异常处理的优缺点,特别是异常处理使用的前提条件等。只有这样,才不会因噎而废食,因为异常处理存在着使用的局限性就否认其价值,才能知道什么情况下使用异常处理比较合适,什么情况下使用异常处理方式和传统错误方式都可以,而什么情况下最好不要使用异常处理,等等。

最后的建议就是:明智地使用异常处理。

另外,这里有一篇很好的关于异常处理的文章,题目是《错误处理和异常处理,你用哪一个》,建议大家阅读一下。
http://bbs.chinaunix.net/viewthread.php?tid=142587
xiaocai0001 2005-12-17
  • 打赏
  • 举报
回复
>> 你把printf放到了try的里面。如果DoFirstThing成功了,但在执行printf时却产生了异常,则也会提示“第一件事失败”,而你就会真的以为第一件事失败了,而跑去DoFirstThing里面调试、找原因,误导了我们的工作。

这个问题实质上就回到 DoFirstThing()与 DoSecondThing()的关系, 既然你都知道DoFirstThing()与DoSecondThing()分开分别进行异常捕获, 那为什么还要纠缠在 DoFirstThing()与Printf()会同时出异常?

其实, 一般来说, 一个程序段需要执行它的功能,则它的每个功能段都要正确运行, 如果有一个功能段抛出异常, 则整体的程序段一般来说就不可能完成既定的功能了. 所以如果DoFirstThing()已经失败, 那么继续DoSecondThing()一般来说已经没有什么意义了.

所以以上程序段还可以改写为:
main()
{
try
{
DoFirstThing(); //可能抛出FirstException类型的异常
DoSecondThing(); //可能抛出SecondException类型的异常
DoThirdThing(); //可能抛出ThirdException类型的异常
}

catch(FirstException)
{
printf("第一件事失败");
}
catch(SecondException)
{
printf("第二件事失败");
}
catch(ThirdException)
{
printf("第三件事失败");
}
catch(...)
{
printf("Unexpected Exception");
}
}

这才是异常的典型使用方法
xiaocai0001 2005-12-16
  • 打赏
  • 举报
回复
还少删了一个标号 -_-!!!
xiaocai0001 2005-12-16
  • 打赏
  • 举报
回复
main()
{
try
{
DoFirstThing();
printf("第一件事成功");
}
catch(...)
{
printf("第一件事失败");
}

try
{
DoSecondThing();
printf("第二件事成功");
}
catch(...)
{
printf("第二件事失败");
goto lbl_fail2;
}

try
{
DoThirdThing();
printf("第三件事成功");
}
catch(...)
{
printf("第三件事失败");
}
}


这样不行么?
为什么要那么多的goto和标号?
MoFanYingXiong 2005-12-16
  • 打赏
  • 举报
回复
dup同志问道:你觉得那种写起来舒畅,那种给人的感觉一气呵成?

我的回答是:第一种更好。

不知你有没有注意到,第一种写法中有“//continue normal process”,而在第二种写法中却没了。
现在我举一个例子:如果要求你做三件事,它们可能成功也可能失败,如果某件事成功,则显示“某某事成功”,否则显示“某某事失败”。用第一种写法如下:
main()
{
if(DoFirstThing())
{
printf("第一件事成功");
}
else
{
printf("第一件事失败");
}

if(DoSecondThing())
{
printf("第二件事成功");
}
else
{
printf("第二件事失败");
}

if(DoThirdThing())
{
printf("第三件事成功");
}
else
{
printf("第三件事失败");
}
}

非常清晰的代码。
而用第二种写法,则要复杂得多,而且不得不使用行标号和goto语句。
写法如下:
main()
{
try
{
DoFirstThing();
}
catch(...)
{
printf("第一件事失败");
goto lbl_fail1;
}
printf("第一件事成功");
lbl_fail1:

try
{
DoSecondThing();
}
catch(...)
{
printf("第二件事失败");
goto lbl_fail2;
}
printf("第二件事成功");
lbl_fail2:

try
{
DoThirdThing();
}
catch(...)
{
printf("第三件事失败");
goto lbl_fail3;
}
printf("第三件事成功");
lbl_fail3:

}

MoFanYingXiong 2005-12-16
  • 打赏
  • 举报
回复
我不赞成你这种写法。
你把printf放到了try的里面。如果DoFirstThing成功了,但在执行printf时却产生了异常,则也会提示“第一件事失败”,而你就会真的以为第一件事失败了,而跑去DoFirstThing里面调试、找原因,误导了我们的工作。
而如果写成返回值的形式,一旦printf失败了,屏幕上没有任何显示,你绝不会盲目地去DoFirstThing里面找原因,而是会先检查DoFirstThing的返回值,很快,你就会发现错误发生在printf函数中。

另外说明一下:printf("第一件事成功")只是一个代表。当DoFirstThing成功时,不但要显示“第一件事成功”,还可能要做其它更复杂的操作,这里只是用 printf来代替,请不要在printf这个函数上做讨论。
fixopen 2005-12-13
  • 打赏
  • 举报
回复
引用

一个父函数为什么要调用子函数?因为它知道子函数可以实现某种功能,但父函数根本就不关心子函数是如何实现的,子函数只需向父函数报告结果成功与否。谁知这子函数又调用了孙函数,而这孙函数抛出了一个异常。
那么这个异常该由谁来处理呢?难道要它的祖函数来处理吗?根本不可能。祖函数根本就不知道孙函数是个什么东西,甚至根本不知道有这么一个孙函数的存在,你让它如何去处理这个异常?
一个父函数既然调用了子函数,就应该对这个子函数的行为负全部责任,绝不能把子函数造成的结果交给祖函数处理。所以,处理这个异常的只能是该函数的直接调用者,绝不可越级。

异常处理机制的引入,完全破坏了这个原则。于是,一个父函数,它不但必须了解子函数的行为,还必须了解孙函数、曾孙函数、玄孙函数的种种行为。简直就是不可能的。

异常所带来的另一个问题就是,孙函数直接将异常抛给祖函数,使得父函数根本没有机会做善后处理工作。狗城是个烂代理和dup两位同志都提到了智能指针,但智能指针并不能解决所有问题。比如打开一个临时文件写入数据,一旦写入失败,则应该关闭并删除临时文件,这不是智能指针所能解决的问题(请不要告诉我在析构函数中做这些工作,因为那样太麻烦了)。

不知道看到这一段话的时候我怎么挺过来的。:),我就不明白,为什么父函数没有机会善后而祖父才有机会?异常是逐级上递的。为什么一涉及到异常就有人以为祖父函数得关心孙子函数?返回至表示的异常如果没有在父函数中处理,祖父函数会怎样?就不用关心了?

引用:
另外,(萧筱雨)同志说:C()抛出的异常,应该在B()中处理,即使不好处理,也要转化成B()的异常抛出。
既然如此,那请问这种处理方法与用返回值逐层返回相比,有什么优点?

优点我已经说了,那就是正常流程和异常流程的分离。
比如:

HRESULT hr = Co...();
if (FAILED(hr))
//exception process
else
//continue normal process

try
{
Co...();
}
catch (ExceptType& e)
{
//exception process
}

你觉得那种写起来舒畅,那种给人的感觉一气呵成?

不过我还是重申一下我的观点:
当然,我还是反对异常的,至于为什么,我就不展开了,如果有兴趣看看我的blog
http://spaces.msn.com/members/fixopen/

我觉得你们反对异常都没有对准靶心,找的理由都经不起推敲。
Jinhao 2005-12-13
  • 打赏
  • 举报
回复
Sorry,我没把异常仅仅用在错误处理上...本来异常可以当作一个信使来用
比如废人翻译的ALP中,用异常来结束线程
  • 打赏
  • 举报
回复
我觉得回调函数的语义应该本来就是应该严格定义的,既然你的DLL要求回调函数不能抛出异常,在声明的时候就可以作出严格的规定,异常本身就没有定义二进制实现的标准,这种跨模块调用的时候抛出异常的行为我觉得是不可想象的。
fixopen 2005-12-08
  • 打赏
  • 举报
回复
我赞成原楼主的观点,C++的异常处理几乎没有价值,事实上,我认为现在的各种语言的异常处理都没有什么价值。

不过我反对楼主的攻击论据。这些论据都不足以表达EH的问题,事实上,楼主对异常处理的理解比较浅,认为异常处理扰乱了正常的流程,可是不扰乱正常的流程是异常处理提出来的原因。楼主不考虑异常流程,也没有提出错误值返回是如何复杂化了流程影响了代码清晰性,而异常处理至少把异常流程和正常流程个离开了。

楼主一个最有力的论据是异常处理导致了异常,可这个也是站不住脚的。假设没有异常,中间那个函数出错,你就能保证最终会释放?中间那个函数如果不返回了呢?同时,C++标准委员会也认为这是一个问题,故此提出了智能指针这个方案,依赖于析构函数的调用保证,智能指针解决了这个问题。

当然,我还是反对异常的,至于为什么,我就不展开了,如果有兴趣看看我的blog
http://spaces.msn.com/members/fixopen/
MoFanYingXiong 2005-12-08
  • 打赏
  • 举报
回复
同意(狗城是个烂代理)同志关于智能指针的说法。另外,你说“除非是把一个会抛出异常的函数的指针传递到 DLL 中调用的情况下才会出问题”,我说的也正是这种情况,因为一个链接库很可能要求调用者提供一个回调函数。

另外,(萧筱雨)同志说:C()抛出的异常,应该在B()中处理,即使不好处理,也要转化成B()的异常抛出。
既然如此,那请问这种处理方法与用返回值逐层返回相比,有什么优点?
xiaocai0001 2005-12-08
  • 打赏
  • 举报
回复
C++异常设计的初衷是, 在程序中出现错误时, 由程序自己处理错误, 尽量不要以exit(0)这种粗暴的方式中止程序.

对于
于是,一个父函数,它不但必须了解子函数的行为,还必须了解孙函数、曾孙函数、玄孙函数的种种行为。
----------------
出现这种混乱的情况, 只能说明写程序的是个白痴, 每个函数声明时, 都有一个异常规格说明, 通过这个声明, 父函数将很明确的知道所调用的函数会出哪些异常, 那么在这个函数内就需要对所有可能的异常进行处理. 而不是简单的不管, 让它再次往上异常传播.
比如对于A(),B(),C()三个函数
现在有A调用B, B调用C
C可以抛出一个ExcepC类型的异常,
B可以抛出一个ExcepB类型的异常
那么A只需要关心处理一下B抛出的ExcepB类型的异常, 不需要关心ExcepC的异常, 这个异常应该在B()函数体的内部处理掉, 即使不好处理, 也需要转化成为B()函数的异常抛出. 如果说A()能捕获C()抛出的异常, 那你完全有理由怀疑写B()函数的程序员的能力
  • 打赏
  • 举报
回复
C++ 的异常处理使它离系统级编程语言越来越远,让我觉得它已经偏离了设计C++的初衷。哦总觉得异常应该是那些应用型语言才应该具备的特性。
MoFanYingXiong(模范英雄) ,“我在编译这套链接库的时候关闭了对异常的支持,而在调用它的工程中却没有关闭,那么,程序是没法运行的” 好像并非如此,这样的处理是没有问题的,除非是把一个会抛出异常的函数的指针传递到 DLL 中调用的情况下才会出问题,甚至在同一个工程里把一部分文件打开异常处理,一部分关闭异常处理,只要不把可能抛出异常的函数夹在关闭了异常处理的代码中调用也一样可以正常的运行。
至于你提到的智能指针问题我觉得应该不能把问题算在异常身上,我觉得任何包含了需要清理的资源,使用类似于智能指针的方式是最优美的,反正我不觉得像这样的代码很有趣:

Resource r1 , r2 , r3 , r4;
if( ! r1=getR1() ) return;
if( ! r2=getR2() ) { ReleaseR1( r1 ); return; }
if( ! r3=getR3() ) { ReleaseR1( r1 ); ReleaseR2( r2 ); return; }
if( ! r4=getR4() ) { ReleaseR1( r1 ); ReleaseR2( r2 ); ReleaseR3( r3 ); return; }

DoSomething();

ReleaseR1( r1 ); ReleaseR2( r2 ); ReleaseR3( r3 ); ReleaseR4( r4 );

或者像这样的代码:
Resource r1 , r2 , r3 , r4;
if( !r1 = getR1() ) goto failed;
if( !r2 = getR2() ) goto failed;
if( !r3 = getR3() ) goto failed;
if( !r4 = getR4() ) goto failed;

DoSomething();

failed:
if( r1 ) ReleaseR1( r1 );
if( r2 ) ReleaseR2( r2 );
if( r3 ) ReleaseR3( r3 );
if( r4 ) ReleaseR4( r4 );

加载更多回复(6)

3,881

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 其它技术问题
社区管理员
  • 其它技术问题社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧