« 编译器实现有感 | 返回首页 | 网易求职被骗记? »

C++ exception 的问题

今天在 C++ 会议上认识的鲍同学发了封 email 过来,顺着他的 email 我找到了<a href="http://spaces.msn.com/members/wesleybao/">他的 blog</a>。发现最上面谈的是关于 C++ 异常的问题。
<a href="http://spaces.msn.com/members/wesleybao/Blog/cns!1p0i3yoUKRgnWt0UyAV1FMog!935.entry">Exception handling in depth</a>

的确在大会的第2天我去听了这个演讲,老实说,其内容我觉得不符合我的预期,所以半途我跑到隔壁去听荣耀讲模板元编程了。

当时讲师前半段一直在讲 SEH ,和 C++ 关系不大。我本以为会讲 C++ 异常的实现的,我个人以前研究过一些,很有兴趣听听人家的理解,结果没有听到。据说后来那个会场最终吵了起来,很遗憾没有领略那个盛况 :)

鲍同学提到 VC6 的实现问题,我可以证明,VC6 的异常处理,在 happy path 上是没有什么内核 API 调用的。
VC6 的异常处理也不比 icc gcc 什么的差。是不是好点我就说不准了。

unhappy path 的性能我个人不是很关心。我想大家也不会太关心吧。

不过,VC6 上关掉异常是可以提高性能的。因为毕竟有些环境需要设置,VC6 会利用 fs 保存一些环境信息。而且无论是什么编译器实现,因为需要处理 unhappy path 一般需要编译自动生成大量的代码。这比你只用返回值一定会生成更多更长的字节数。只是因为大家现在都不大关心代码体积,这个问题被掩盖了。我个人在某些情况下是在乎这个的。而且代码体积增加也意味着略微的时间效率损失,ICC 或许会优化的好一些,让其在 happy path 上,对性能影响最小,比如调远 unhappy path 的 code 的物理距离等。

除零的问题是这样,一般非数学计算的程序,我们很少用除法,即使用到,大多数情况,程序逻辑可以保证其不为0。而数学计算的程序一般又用的浮点数,除零得到的是无穷大并不抛出异常。所以我并不认为用除0异常来体现异常的优越性有特别大的说服力。

我个人因为特定的项目环境的原因,是不使用异常机制的。大部分情况下,处理 unhappy path 用 goto 足亦。这里就扯到 goto 的问题。goto 在很多人看来很不耻。不过我认为在函数设置一些出口点,然后 goto 是一种很优美的方法。

其实不用异常的好处也很多,首先程序的复杂性降低了,否则程序的流程会比较复杂,对程序员的要求更高。其次,接口的设计也会比较简单,最后性能会有那么一点的提高。而且,我的程序曾经大量使用 coroutine,C++ 异常对此很难支持。呵呵,言多必失,我对异常几乎没怎么用,都是理论知识,不敢多评论了。

其实偶还是支持鲍同学大部分观点的 :)

Comments

异常有时候没办法而不得不用,像c++ new 操作符,默认的bad_alloc, 另外stl中的各种诸如下标越界,参数错误等异常也不少啊...
至于性能,我觉得一个程序应该是主要考虑正常情况下的效率,出错之后首要考虑的应该是如何处理,毕竟一个程序不是完全为处理错误的效率而开发的,不然就有点本末倒置了...
如果一个程序需要高稳定性,毕竟用一大堆if来做输入参数和返回结果的判断带来的性能开销以及可读性降低也会很可观的 但是c++不应该像java/.net中常见的那样将异常作为一种常规传递状态信息,返回结果等用途...

这么早的一篇文章,我现在才来留言~说实话,我现在也会在特定的场合使用goto,发现把函数变成单一的出口非常有利于程序设计。

但是我使用了一些C++的库,而对于一些对象只在里使用地方最近处才定义,然而goto是不能越过这些定义的,除非将那些代码放入一个block({})中,相对来说有时候不太方便。

exception是有用的。但不能在程序中大量使用。更不能把exception作为error handling的一种方式。问为什么的人是还没有了解error和exception的区别。

就拿经典的除零来讲。大多数人都说除零是一个exception,但是忘了这是对cpu来说的。把零当成除数是一个常识性的错误(error),但对cpu来说只是一个异常,因为它并不了解一段机器码是一个除法,它没有除法单元。

但除零对大多数软件来说是一个错误,而非异常。因为一个变量没有判断是否为零就被当成了除数,这是一个明显的错误;除非并不了解它会被作为除数,那产生的可以说是一个异常。

还有一个例子,就是分配内存。使用异常来代替指针的判断,还说是提高可读性。这是对异常处理的极大误解。

程序中大量使用异常的,绝对不是为了提高程序的稳定性,或是可笑的提高可读性,那都是忽悠外行的。使用异常原因只有一个:偷懒!

老程序员使用异常,是不想写那么多的if判断,他们的catch中只有几行;新程序员使用异常,是不知道该在哪个地方进行判断,他们的代码中,判断会很少。

偷懒之外还有一种,是出于对异常的崇拜,他们的catch写得中规中矩,不过他们很快走出这个阶段。

好笑

>不过我认为在函数设置一些出口点,然后 goto 是一种很优美的方法。

cool,这个东西在Functional Programming里叫continuation,是非常优美的一种解决方案。

有一点,云风长得比鲍同学帅太多了,哈哈...

能不能把网页右上角的“云风流言”改为“云风留言”,这是一个异常还是一个错误?

和云风的观点相反,我认为使用异常才有利于简化程序流程,降低程序复杂度。

我以前也都是不用异常的。最近在用JavaScript和C#的时候才开始用,发现很好用。
DHTML如果用C++来访问,判断HRESULT非常繁琐,很累,用JavaScript的时候,包装到异常里面了,就很舒服了,相信用过的都有感受。
异常配合IDE,调试错误也很方便,相反,如果用返回值,打断点,加ASSERT就很麻烦了。

使用返回值,错误处理和程序功能互相夹杂,然后再加上提前返回的return ,真是一塌糊涂,这种情况下,goto反而会使程序好一点。

虽然不同的语言的异常的具体实现不同,高级语言的异常效率当然会低一些,但是抛开效率不谈,使用异常的程序的确比返回值更好看

小弟今年大四,云风前辈,是我崇拜的偶像,和偶像谈话,真还是有点战战兢兢啊,:),

不知道,我说的对不对,贻笑大方了,
而且我说的观点也没有经过我编写大项目的程序设计的实践经验,仅仅是一些小程序的测试

我对异常的感觉,是c++一贯的风格,
1:零开销,效率可以与c媲美
2:方便大规模程序组织

好处是
1:如果不使用异常,就会必须定义很多错误值,而且这些错误值,不利于程序的逻辑组织,
2:如果使用异常的话,可以把错误按照程序的逻辑组织起来,,
可以给用户非常清晰的逻辑,而且从代码上看,清晰易懂。
3:而且使用异常可以最大发挥出c++的在大规模的程序组织这方面的能力,比如,用异常+“资源申请即初始化”技术,可以高雅有效的解决资源申请资源分配和释放的的问题,
如果不用这个技术,很难找到比这个更加高雅的方案了(c++之父语),
4:可以更加发挥出面向对象的优势,提高抽象层次,而且还不损失效率。比如,编写有一个类的时候,可以使用“类的不变式 + 扩展库”的方式来编写每一个类,避免肥大类,
如果该类是一个申请资源类,如何“优雅高效”维护类的不变式呢,这需要用到“异常”(c++之父语)

不过使用异常的缺点也显而易见:
1:云风前辈说的
2:不要小看目前异常带来的性能开销(以后编译器高度符合标准后除外)
3:在团队合作的时候,找不到足够高明的c++程序员后,使用异常带来的合作困难
4:异常在大规模程序中,必须使用,如果只是使用c++的butter c子集的话,不要使用异常

不知道云风前辈的游戏代码有多少行,小弟曾经在游戏公司实习过,游戏代码是绝对不可能上百万行代码,,

如果代码量上百万千万行代码的话,
不用异常,用c的错误处理方式,
应该很难吧,

我说的也是纯理论的,没有经过规模比较大的程序的实践和经验,所以还请云风前辈见谅

我的感觉是:
c++就像一个工具多多的工具箱,
里面有很多的东东是我们用不到,应该说是我们工作的环境用不到的,
如果勉强去用,会使程序拙劣,

但是,每一个工具都有它存在的理由和最恰当的使用的地方,

当在某一个场景中如果都这些特性,我们都需要,而用上的时候,我们的由衷的赞叹:我们的设计真优美!

我倒是觉得异常在happy path的是时候要比错误返回值性能要好,用了异常只是增加两条汇编指令,而用错误返回值则要增加条件判断,哪个效率高云风应该比较清楚吧?
其实异常VS错误返回值哪个好已经不言自明,看看C++之后的编程语言几乎全部都是支持异常的。只不过C++的异常设计的不好,而且异常神秘的实现机制也不是那么容易被人理解,所以才会引发那么多争议。

噢,我blog里说除0只是打个比方,事实上在C++里四则运算符都可能被重载。比如+号用作string的连接,a+b+c+d+e完全可能异常,因为string连接时可能需要分配内存会失败。这个例子其实说的是functional programming的paradigm,也就是像LISP那样,函数层层嵌套,如func1 ( func2 ( func3 ( a, b ) c ) d ) ,因为每个函数都可能会出错的。用error return value就麻烦了。我只不过用add divide multiply作为了func1 func2 func3的例子,又用+ - * / 替换了add divide这样的函数名 :)

Post a comment

非这个主题相关的留言请到:留言本