C++ day34 异常(三)异常规范,未捕获异常,意外异常,异常导致内存泄漏 谁践踏了优雅 2023-02-17 02:43 10阅读 0赞 ### 文章目录 ### * 异常规范(C++98添加,C++11已摒弃,不建议使用) * * 异常规范的作用(正是这俩作用的鸡肋和难办使它失去了粉丝) * C++11支持的特殊的异常规范:关键字noexcept(程序员的庄严承诺) * 异常被引发后可能出现的两种问题 * * 未捕获异常 uncaught exception * * 示例 * 意外异常 unexpected exception(C++11摈弃异常规范的另一理由) * 异常处理可能导致==内存泄漏==:解决办法是智能指针模板 * 总结 # 异常规范(C++98添加,C++11已摒弃,不建议使用) # exception specification 由于大量已用代码用了异常规范,所以暂未从标准中完全剔除,但是完全去掉指日可待。 > 其实我之前看到它已经被摒弃就不打算深入了解他了,但后面好多代码都涉及到这个东西。。。。搞的我很被动。只好回头来学习。 下面代码中的throw部分就是异常规范,它后面的圆括号里面是**类型列表**,但是也可以不包含类型列表。 double harm(double a) throw(bad_thing);//表示也许harm函数会抛出bad_thing异常 double harm(double a) throw();//表示harm函数不会抛出异常 异常规范可能出现的位置: * 函数原型 * 函数定义 ## 异常规范的作用(正是这俩作用的鸡肋和难办使它失去了粉丝) ## * **告诉程序员可能需要使用try块**。 给函数指定异常规范,以让函数的用户**知道要捕获哪些异常**。 即如果函数的原型和函数定义中有异常规范,则调用该函数时最好用try块把它包起来并写catch块来处理异常。 比如: double argh(double, double) throw(out_of_bounds);//函数原型,指明了可能会引发out_of_bounds异常 则使用时就这么用: try{ x = argh(y, z); } catch{ ··· } 但是C++11并不买账,它认为可能直接在函数原型旁边**写注释**提醒程序员用try块,干嘛非要加个语法工具呢,明明注释就可以告诉大家要用try块了。 * **让编译器添加执行运行阶段检查的代码**,即检查是否违反了异常规范。 但是编译器表示我好难。。因为很难检查是不是引发了异常,比如harm函数没有引发,但是harm调用的另一个函数引发了;或者以前没引发,更新一个库以后就引发了。。。 原则上,异常规范的规范列表列表应该包含此函数调用的其他函数引发的异常。比如,a函数调用了b函数,b函数可能引发retort对象异常,那么a函数和b函数的异常规范中都应该包含retort对象异常。 可是,a函数怎么知道自己内部具体调用了多少函数,调用的这些函数又调用了哪些函数,无底洞啊简直,就算编译器不辞辛劳,真的找到了a函数会直接和间接调用的所有函数,那把他们可能引发的异常都写进大家的异常规范里??多长啊得,又不好维护。。。编译器表示头大。写编译器的人更头大。 总之,编译器和为编译器写这种检查代码的程序员发现,这个活真的不好干,于是大家达成一致意见:不要用异常规范了。C++11也觉得很有道理,就建议大家不要使用这个**看似很有前景,看似会对代码安全有贡献**的特性了。 ## C++11支持的特殊的异常规范:关键字noexcept(程序员的庄严承诺) ## 这个关键字是C++11新增的。用来表示函数不会引发异常,这其实是程序员的一份庄严承诺。程序员必须足够自信,才能信誓旦旦的使用该关键字。 使用它的目的和好处:有助于编译器优化代码,编译器知道这个函数一定不会引发异常,就无需对它多余关照了。 # 异常被引发后可能出现的两种问题 # 这两种情况,默认都会导致程序**异常终止**。 ## 未捕获异常 uncaught exception ## 如果异常不是在带有异常规范的函数中引发,即引发这个异常的函数并没有写异常规范,那么**必须要捕获他**。 如果不捕获,即**没写try块**把引发异常的代码包起来,或者**没找到匹配类型的catch块**,则这种异常就叫未捕获异常。 未捕获异常不会导致程序立刻终止。程序会首先调用**头文件exception**中的`terminate()`函数,terminate()函数默认调用`abort()`函数来终止程序。但是,我们可以用exception头文件中的`set_terminate()`函数来修改terminate函数到底调用谁,即可以让他不调用`abort()`,而是调用比如`exit()`函数。 下面是exception头文件中`set_terminate()`函数和`terminate()`函数的声明: typedef void (*terminate_handler)();//terminate_handler是一个函数指针类型,指向没有参数也没有返回值的函数 terminate_handler set_terminate(terminate_handler f) noexcept; void terminate() noexcept; > 函数指针,即函数的地址,难道matlab里的函数句柄其实就是函数的地址?记得学的时候书上就解释说是一种间接的使用函数的方式啦巴拉巴拉的,直接说是函数的地址不就好了??不就一下理解了???网上那么多人都觉得matlab的函数句柄略难理解,难道大家没有一个学C的函数指针的? > 看到一个解释通透的啦,看来句柄确实是地址。![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NjA3ODk0_size_16_color_FFFFFF_t_70] 如果多次调用set\_terminate()函数,则terminate函数对调用最后一次set\_terminate函数设置的函数。 #include <exception> using namespace std; void myQuit() { cout << "Terminating due to uncaught exception\n"; exit(5); } //在自己主程序的开头,指定终止操作调用自己写的终止函数 set_terminate(myQuit);//myQuit函数名其实就是一个函数地址,一个函数指针 ### 示例 ### 自己写了一个未捕获异常的示例,即new请求内存失败,我没用try块把new语句包起来,引发的异常就是未捕获的异常。 嘻嘻 using namespace std; void myQuit() { cout << "Terminating due to uncaught exception\n"; exit(5); } int main() { set_terminate(myQuit); double * d = new double[100000000000000];//引发异常后,程序先调用terminate,然后调用myQuit return 0; } Terminating due to uncaught exception Process returned 5 (0x5) execution time : 0.508 s Press any key to continue. ## 意外异常 unexpected exception(C++11摈弃异常规范的另一理由) ## 没有和异常规范的规范列表中的某种异常匹配,则是意料之外的异常,即函数引发了其异常规范中没有的异常。 > 异常规范里写到的异常类型是预期的异常。 由于定义中涉及到异常规范,所以这种异常**必须由带异常规范的函数来引发**。 > 异常规范:C++11不建议使用,但还没被踢出标准。 这种情况下,程序会调用`unexpected()`函数,这个函数再去调用terminate函数,然后terminate还是默认调用abort函数来终止程序。 但是unexpected函数的行为也是可以修改的,用一个叫做set\_unexpected的函数来修改。这几个函数在exception头文件的声明如下: typedef void (*unexpected_handler)(); unexpected_handler set_unexpected(unexpected_handler f) noexcept;//把函数指针作为参数传入 void unexpected() noexcept; 天哪,我不知道下面这段在说什么,怎么这么复杂,我服了,溜了 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NjA3ODk0_size_16_color_FFFFFF_t_70 1] double argh(double x, double y) throw(out_of_bounds, bad_exception); try{ x = argh(a, b); } catch(out_of_bounds * ex){ ··· } catch(bad_exception & ex){ ··· } # 异常处理可能导致内存泄漏:解决办法是智能指针模板 # 当异常处理遇上动态内存分配,就有可能会出事儿。 先看一个不会出事儿的: void test1(int n) { string mesg("I'm trapped in an endless loop"); ... if (oh_no) throw exception(); ··· return; } 如果oh\_no为true,throw语句会提前终止函数,但是动态分配内存的mesg对象由于栈解退,还是成功的被调用了析构函数,即动态分配的内存会被释放,就不会造成内存泄漏。 所以这里没出问题,主要是因为mesg是一个类对象,有析构函数,所以在析构函数里释放了动态分配的这些内存。 > string类的构造函数使用了动态内存分配哦!!! 再看看会出事的: void test2(int n) { double * d = new double[n]; ··· if (oh_no) throw exception(); ··· delete [] d; return; } 这里就不一样了,double指针d,不是对象,没有析构函数的加持,于是throw以后,函数终止,后面的delete就执行不了了,栈解退只能把变量d,即double指针变量,释放,并不能释放d指向的那一堆内存啊。于是这对内存就泄露了,因为指向他们的指针都没了。 > 所以还是来自类这种大家族比较好,有背景就是不一样,人家的析构函数在栈解退时会被自动调用就把内存释放了。而来自基本数据类型的,比如int,double等的对象,就没有析构函数这种外挂工具,一旦出现大的风雨比如异常,就玩去哪没有招架之法,只能眼睁睁看着自己指向的内存被泄露。 那上面这种情况要怎么处理呢?办法总是有的。只要智商不滑坡,办法总比困难多。 void test2(int n) { double * d = new double[n]; ··· try{ if (oh_no) throw exception(); } catch(exception & ex){ //处理方法:先捕捉异常,然后只是释放内存,再帮你重新引发一个一样的异常 delete [] d; throw; } ··· delete [] d; return; } 这种处理很聪明,我先捕获你,但其实我并不会真的帮你处理你的问题,而只是想利用你解决我的问题,我把我的内存释放完了,我再帮你引发一个同样的异常,也不耽误你的事儿,还把我的危机化解了。聪明。 但是毕竟你想不到一切可能发生的异常情况,所以这种临时补墙刷灰的操作肯定不完美,也许还会背的疏忽和错误呢?所以这种情况下,其实最好的办法是智能指针模板。 # 总结 # * 函数可以包含异常规范,以指出自己可能引发的各种异常,但是C++11不建议大家使用。 * 未捕获异常(没有匹配的catch块)默认情况下会终止程序。 * 意外异常(没有和任何异常规范匹配的异常)默认情况下也会终止程序。 * 应该在**设计程序时候就加入**异常处理的功能,不是以后再加。 * 使用异常,虽然会提升代码的安全性,但是也会**增加代码长度**,还会**降低程序运行速度**。任何工具都是双刃剑,按需使用。 对于某些重要的项目,异常处理是非常重要的,不进行异常错误处理的代价也许更高,虽然会增加代码工作量和程序时间,但是也要做。 * **异常规范不可以用于模板**,因为函数模板经过不同的具体化方式,引发的异常也不尽相同。 * 异常和动态内存分配也许会冲突。 大体意思就是:现在学了这么多,仍然只是皮毛,建立起来一个要处理异常的概念,但是怎么处理,这个实现操作却需要自己去看库文档和类源码的异常例程,自己去总结和学习异常处理的细节。这只是万里长征第一步。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NjA3ODk0_size_16_color_FFFFFF_t_70 2] [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NjA3ODk0_size_16_color_FFFFFF_t_70]: https://img-blog.csdnimg.cn/20200611140403115.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NjA3ODk0,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NjA3ODk0_size_16_color_FFFFFF_t_70 1]: https://img-blog.csdnimg.cn/2020061114473066.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NjA3ODk0,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NjA3ODk0_size_16_color_FFFFFF_t_70 2]: https://img-blog.csdnimg.cn/2020061115060663.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NjA3ODk0,size_16,color_FFFFFF,t_70
相关 Java异常处理:未捕获异常示例 在Java编程中,异常处理机制是用于控制程序流程,当出现预期之外的情况时,可以抛出一个异常并进行捕获和处理。 下面是一个未捕获异常的简单示例: ```java public - 日理万妓/ 2024年10月16日 12:09/ 0 赞/ 78 阅读
相关 异常处理失效:未捕获的异常导致程序崩溃 是的,你描述的情况非常常见。在编程中,如果一个异常没有被恰当地捕获并进行处理,它通常会终止当前的函数或者方法执行。 这可能导致程序运行不完整,甚至出现系统崩溃的情况。因此,在 Love The Way You Lie/ 2024年09月22日 12:27/ 0 赞/ 83 阅读
相关 Java异常处理:未捕获异常导致程序崩溃实例 在Java编程中,异常处理是为了避免程序因为意外或预期外的情况而直接停止运行。当一个未被捕获的异常被抛出时,程序将自动进入异常处理流程。 下面是一个例子,展示了未捕获异常会导 约定不等于承诺〃/ 2024年09月12日 01:51/ 0 赞/ 96 阅读
相关 Java异常处理:未捕获的异常 在Java中,异常处理是通过try-catch-finally语句块来实现的。当一个方法或者代码块执行时遇到了预期之外的情况,就会抛出一个异常。 如果在一个try语句块中的代 朴灿烈づ我的快乐病毒、/ 2024年09月10日 11:33/ 0 赞/ 86 阅读
相关 Java异常处理:未捕获异常案例 在Java编程中,异常处理是一种机制,用于当程序运行时遇到错误或不期望的情况时,优雅地进行恢复或者提供错误信息。 下面是一个未捕获异常的案例: ```java public Myth丶恋晨/ 2024年09月06日 07:51/ 0 赞/ 89 阅读
相关 Java异常处理:未捕获异常示例 在Java中,如果一个方法可能会抛出某种异常,但这个异常没有被任何try-catch块捕获,那么这个异常就会被称为"未被捕获的异常"。 下面是一个简单的例子: ```jav 系统管理员/ 2024年09月05日 17:06/ 0 赞/ 81 阅读
相关 【C++】异常处理 ② ( 异常捕获类型 | 异常捕获机制 - 严格匹配异常类型 | 未知异常捕获 - 不知道异常类型 ) 文章目录 一、异常捕获机制 - 严格匹配异常类型 1、异常捕获机制 - 严格匹配异常类型 2、代码示例 - 异常捕获严格匹配异常类型 痛定思痛。/ 2024年02月17日 12:26/ 0 赞/ 54 阅读
相关 C++ day34 异常(三)异常规范,未捕获异常,意外异常,异常导致内存泄漏 文章目录 异常规范(C++98添加,C++11已摒弃,不建议使用) 异常规范的作用(正是这俩作用的鸡肋和难办使它失去了粉丝) C++11支 谁践踏了优雅/ 2023年02月17日 02:43/ 0 赞/ 11 阅读
还没有评论,来说两句吧...