| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> C++异常处理 -> 正文阅读 |
|
[C++知识库]C++异常处理 |
我好像认识很多个用C的方式写C++的人。难道是因为我圈子里大多是嵌入式工程师嘛? 上周我跟北京研发的一个哥们合作,因为他提供的动态库总是段错误,我说你提供的这个接口没有做入参的检查吧,你发现入参不合法之后给我抛个异常出来吧,别让它再段错误了。 他说我抛了啊,我怎么可能连异常都不知道呢? 于是我说你让我看一眼你是怎么抛异常的。
我tm想把他和追梦格林扔到八角笼中决斗一番。 1 一知半解有时候甚至还不如完全不懂,后者由于自知害怕出错,一般会采取比较稳妥的方式,前者经常不懂装懂贪功冒进。 让我们拨动秒针,穿梭时空,回到自己刚刚学C语言的时候。 老师说,函数虽然可以写多个入参,但只能有一个返回值(某Go语言开发者:what?),我们需要在写函数的时候指明返回值的类型。 当然,大家的技术水平都是比较高的,多数情况下,函数都会正常的运行。但老虎也有打盹的时候,偶尔也会因为一些疏忽导致函数出错。此时我们应该返回什么呢? 我总结了三种情况,错误码,NULL,空。 但这三种情况的用法某种意义上是一样的:都需要对返回值做判断。即如果返回值正常,则继续往下走;如果返回值出错/为NULL/为空则不能继续,因为会引发各种错误。 此时C++迈着大步跑了过来,“加上我,我还有一种情况,叫做异常!” 2 错误码的机制有点像健康码,园区保安需要核对每个人的健康码是绿色还是红色,决定是否让你进入园区工作;当然会很有效,但缺点是太过繁琐,效率低下,影响美观。 异常的机制则像有个人站在30层的楼顶要往下跳,如果没人拿防护网接住他(try-catch),那么他将崩溃殒命。但你也可以选择在1楼到29楼的任何一层拿防护网接住他,询问他崩溃的原因,那么悲剧将有可能被挽回。 当然,你也可以选择先接住他,问明原因后再把他扔出去(throw),告诉他人间的悲欢并不相通,我只是有点八卦。 至于扔出去之后他是挂掉还是又被人接住,取决于你这层楼的下面还有没有防护网。 这30层楼就是函数层层调用搭起来的高楼大厦,所以异常跳楼的时候也是层层传递。即在20层楼处搭建的防护网只能捕捉到20楼以上跳楼的靓仔,却捕获不到从19楼跳楼的住户。所以出于安全起见,我们一般会在一楼支起一个防护网捕捉所有跳楼的靓仔,然后安稳的睡去。 当然如果你选择在30层楼的每一层都搭一个防护网,那我建议你别干消防了,去园区门口当保安查健康码吧,那个适合你。 3 错误码的返回一般有两种形式,一种是占用函数返回值,形如
一种是返回值另有他用,所以需要占用一个入参或者使用修改全局变量的方式。函数将错误码填入传进来的入参中,一般为指针形式,形如
详细错误信息的获取也有两种方式,一种是直接返回一个错误码和错误信息的结构体,形如
一种是只返回错误码,错误信息定义为全局变量。接收者通过strerror(errno)的方式获取详细的错误信息。 无论采取什么样的形式来设计,错误码的机制都决定了接收者需要对其进行判断;而异常则不用层层处理,可以避免“必须检查返回值,不能遗漏一个的情况”。 以读文件里面的read函数举例,
read函数的返回值,在正常时返回读取到的字节数,在文件末尾调read时返回0,出错时返回-1并设置errno。 假如A函数调用了read,用于将读取到的数据做分割、提取、处理等工作; B函数调用了A函数,将处理后的数据用plot控件绘制成图形; C函数调用了B函数,将图形显示到UI界面上。 那么ABC函数得这么写:
由此可见,我们需要在每一层都得判断一下函数返回值是否正确,NULL和空也是一样的情景。这样写一方面写的人觉得繁琐,另一方面看的人也觉得啰嗦。 作为对比,异常则是只需在read函数里throw,在函数C里catch即可。当然前提是A,B函数里的代码要保证异常安全。
上述例子比较简单,工作的时候为了保证异常安全,一般建议大家使用RAII的思想,退出作用域,资源随即释放。即使发生异常,现场会恢复到调用前的状态,资源也不会有任何泄漏。也就是所谓的异常安全了。 4 错误码还有一个致命的问题是,使用者可以不接收,不判断。一旦他选择忽略,程序拿着一个错误的结果继续往下走,鬼知道会出什么事情。就好像园区保安有点累,不检查健康码就放人进去,有可能会引起全上海封城3个月的严重后果。 作为对比,异常是不能忽略的,有人跳楼你不处理,那这个程序肯定就挂了。 C++的标准库就是用异常来作为错误处理方式的。如果你使用过std::vector和std::map这类常见容器,一定对out of range,bad alloc,map::at这类异常出错记忆深刻。 使用异常的理由千千万,这儿还有一条最重要的理由:
构造函数和析构函数连返回值都没有,我怎么返回错误码? 5 C++标准库中定义了异常类,并且有继承派生关系。我们只能以默认初始化的方式取初始化bad_alloc,bad_cast这些对象,不允许为其提供初始值;而logic_error和runtime_error这些类型的对象,则必须提供字符串初始值来初始化。 这两种常用异常类型的区别是: logic_error:理论上无需程序运行,读代码就能看出来的异常; runtime_error:理论上只有程序运行起来才能检测出的异常。 开头北京同事如果这样抛异常,我这边就不会出错啦。
6 当然了,异常处理也是有一些成本的。为了在运行时处理异常,程序内部要记录大量的信息,标记的对象需要跟踪,抛出和处理都需要编译器对代码进行相应的优化。 所以如果某些函数是绝对不会抛出异常的时候,我们可以在其后标记noexcept,来显式的告知编译器:这个人是个老实人,不会跳楼,不用对他做什么优化工作。
一般而言,类内的移动构造函数、移动赋值运算符和 swap 函数都需要保证不抛异常并标为 noexcept。 不过这个noexcept只是一种承诺和保证,虽然对外承诺说我保证不抛异常,但并不意味着真的不抛异常。出异常了只能说这里发生了一些不符合预期的事情,此时系统会直接调用std::terminate中断程序的执行。 所以如果被标记为不会跳楼的老实人真的跳楼了,那他会死的很干脆。虽然有点出乎意料,但好像确实符合老实人的实际处境。 7 当然了,完全不用异常和完全不用错误码都是比较极端的做法,异常和错误码配合使用味道更佳。 在一些允许频繁出错的地方,比如网络波动引起的错误,硬件设备操作的故障等等,还是老老实实使用错误码比较好。毕竟检查健康码的成本,总比防护跳楼的成本要低一些。 说到跳楼,神探夏洛克 S2E3,被莫里亚蒂用自杀逼入死局的夏洛克,由于事先已经写好了异常处理机制,他自信的站在圣巴塞洛缪医院四层的楼顶,看了看左手边的圣保罗教堂,望着基友华生的侧脸, 纵身一跃。 |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 16:43:59- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |