• MFC异常抛出机制的一处设计缺陷


    作者:朱金灿
    来源:http://blog.csdn.net/clever101/


         昨天看了《More effective C++》的规则十二,今天看了《More effective C++》的规则十三,再到论坛问了问,自认发现了MFC的异常抛出机制的一处设计缺陷。当然我也非常希望大家就MFC的这处设计发表自己的观点。


    条款十二:理解"抛出一个异常"与"传递一个参数"或"调用一个虚函数"间的差异


    下面是我的一些理解:




         作者专门把这一动作和函数的参数传递作比较,事实上也比较类似,当然如作者所言,二者还是有根本区别的:比如抛出异常不可能回到调用者那儿,而函数参数传递是必然要回到调用者那儿的。

    为此我设想了三种抛出异常的情形:


    第一种拷贝对象,再传递对象:



    这里把这段代码细化一下,我感觉画一个流程图大家看得更清楚些:


    para%20transfer1



          这里实际上类的拷贝构造函数调用了两次,所以作者在文中说:当抛出一个异常时,系统构造的(以后会析构掉)被抛出对象的拷贝数比以相同对象做为参数传递给函数时构造的拷贝数要多一个。


    第二种拷贝对象,传递引用



        其对应的流程图如下:


    para%20transfer2


    第三种办法,在堆上创建对象,复制指针,传递指针



           其对应的流程图如下:


    para%20transfer3


         
          这里要详细说说为什么要new一个对象出来?因为文中作者说:我们还没有讨论通过指针抛出异常的情况,不过通过指针抛出异常与通过指针传递参数是相同的。不论哪种方法都是一个指针的拷贝被传递。你不能认为抛出的指针是一个指向局部对象的 指针,因为当异常离开局部变量的生存空间时,该局部变量已经被释放。Catch子句将获得一个指向已经不存在的对象的指针。这种行为在设计时应该予以避免。

    上面说的是看了规则十二领悟到的。今天看了规则十三,有了新的领悟。事实通过传递指针还有传递全局与静态对象的指针,具体如下:



           与之对应的流程图如下:


    para%20transfer4


        
           现在我们比较这三种做法,。第一种做法在效率毫无疑问是最低的,首先定义一个异常对象就调用了构造函数,随后的throw语句和catch语句就两次调用了拷贝构造函数。如《More effective C++》所言,这种做法还会产生slicing problem,即派生类的异常对象被做为基类异常对象捕获时,那它的派生类行为就被切掉了(sliced off)。


           我们再认真比较第二种做法和第三种做法:第二种做法,定义一个局部的异常对象就调用了构造函数,随后的throw语句调用了一次拷贝构造函数。第三种做法无论是采用从堆申请内存还是定义全局或静态对象的做法,都只是调用了一次构造函数。从效率而言,似乎第三种做法比第二种做法更优。但第三种做法有着一些严重弊端:第三种做法占用的是全局内存,这里需要考虑每次都为不经常发生的内存花掉一个异常对象的全局内存是否值得,还有如同《More effective C++》指出的:采用指针传递的做法很容易陷入一个哈姆雷特式的难题--对进入catch子句的异常对象指针是删除还是不删除?如果堆申请的,自然应该删除,但是如果是静态或全局对象,自然不应删除。而这是不可能知道的。所以综合而言,规则十三明确指出:通过引用(reference)捕获异常(是相对而言最好的做法)。


           但我看了微软的MSDN上的一个例子(MSDN Library for Visual Studio 2008 SP1 简体中文):


         我又感到疑惑了,看代码微软的做法是通过在堆上构造异常对象,然后将指针传递给catch子句的。其中theException->Delete();这一句实际上是调用CException::Delete函数的。MSDN对CException::Delete函数的解释是:
    This function checks to see if the CException object was created on the heap, and if so, it calls the delete operator on the object.


          开始我不明白为何微软这样设计呢?后来一问才知道这是历史遗留问题。MFC在C++标准支持异常处理之前就开始使用异常机制。至于说采用堆申请内存还是使用静态或全局对象,微软可能觉得静态、全局对象更难管理,而且为不经常发生的异常都定义一个静态或全局变量显然是铺张浪费的。可能你要问:VC 6.0的MFC在C++标准支持异常处理之前就开始使用异常机制,那么为什么在C++标准支持异常处理之后MFC还不改过来呢?我想可能是为了向前兼容吧,如果改过来的话,那意味着之前的VC 6.0的代码都得改过来,显然用户无法承担这一成本。


          另外对于MFC使用指针,MFC提供了一套TRY, CATCH, ENDCATCH的宏可以自动释放,具体见:

    http://msdn.microsoft.com/it-it/library/sas5wzs9(VS.80).aspx


           在此特别感谢CSDN的akirya大侠和微软中文技术论坛的SplendourG大侠。








  • 相关阅读:
    实习第一天
    使用epublib解析epub文件(章节内容、书籍菜单)
    jdk1.8以前不建议使用其自带的Base64来加解密
    java学习-AES加解密之AES-128-CBC算法
    java学习-sha1散列算法
    日、周、月活跃用户数,用户流失率
    java学习-java.lang.Math随机数生成
    AndroidStudio报错Software caused connection abort: recv failed
    java学习-java.lang一Number类
    jdk内置类javax.imageio.ImageIO支持的图片处理格式
  • 原文地址:https://www.cnblogs.com/lanzhi/p/6471036.html
Copyright © 2020-2023  润新知