C++笔记---异常

1. 异常的概念

1.1 异常和错误

异常通常是指在程序运行中动态出现的非正常情况,这些情况往往是可以预见并可以在不停止程序的情况下动态地进行处理的。

错误通常是指那些会导致程序终止的,无法动态处理的非正常情况。例如,越界访问、栈溢出、语法错误等等。错误往往无法预见,需要程序员进行调试来发现出错的原因。

1.2 异常处理机制

C++提供了一套异常处理机制,用于管理和控制程序中可能出现的异常。这个机制基于三个关键的关键字:throw、try和catch。

  • throw关键字用于抛出一个异常。异常可以是任何类型的对象,但通常是从std::exception派生的类的实例。
  • try块包围可能会抛出异常的代码。如果在try块中发生异常,程序会立即停止执行当前的函数,并开始在包含try块的函数上下文中搜索匹配的catch块。
  • catch块定义了异常处理代码。每个catch块都有一个异常声明,用于指定它能够捕获的异常类型。当try块中抛出一个异常时,程序会尝试匹配catch块中的异常声明,并执行匹配的catch块中的代码。
try
{// 可能抛出异常的代码// ...
}
catch(Exception e) 
{// 处理异常或显示错误信息的代码// ...
}
// 如果需要可继续增加catch块

其中,Exception为可接收异常对象的类型(与异常对象的类型相同,异常对象的父类,异常对象可以发生隐式类型转换的类型)。

与传参的规则相似,能传参给e就能捕获,其中用父类来捕获子类异常在异常继承体系中非常实用,例如:除零异常类继承自算术异常类,那么就可以使用算术异常类来捕获除零异常。 


 2. 异常的抛出与捕获

程序出现问题时,我们通过抛出(throw)一个对象来引发一个异常,该对象的类型以及当前的调用链决定了应该由哪个catch的处理代码来处理该异常。

当异常被抛出后,程序会立即跳转到能捕获该异常的最近的catch块处,也就是说:

(1)当前try块中的代码会立即停止执行,并沿着调用链往回匹配能够捕获该异常的catch块。

(2)在匹配catch块的过程中,当前函数栈帧未能处理掉异常,则函数栈帧会被立即销毁(该函数栈帧中已定义的对象全部进入析构流程)并返回上一层函数调用,继续匹配catch块。

若在返回到main函数之后都未能处理掉异常,那么该异常就成为了一个错误,程序会立即终止并报错。

#include<iostream>
#include<string>
using namespace std;double Divide(int a, int b)
{try{// 当b == 0时抛出异常if (b == 0){string s("Divide by zero condition!");throw s;} else{return ((double)a / (double)b);}} catch(int errid){cout << "Divide:" << errid << endl;} return 0;
} void Func()
{int len, time;cin >> len >> time;try{cout << Divide(len, time) << endl;} catch(const char* errmsg){cout << "Func:" << errmsg << endl;}// 除数为0时,此行不会被执行cout << __FUNCTION__ << ":" << __LINE__ << "行执行" << endl;
} int main()
{while (1){try{Func();} catch(const string& errmsg){cout << "main:" << errmsg << endl;}} return 0;
}

抛出的异常实质上是异常对象的拷贝,因为被抛出的异常对象可能是一个局部对象,函数栈帧被销毁之后该对象也会被销毁(这里的处理类似于函数传值返回)。

但是这里有一个例外,就是在使用左值引用捕获异常时,异常也能被捕获(一般来说异常对象的拷贝应该是右值,无法使用左值引用接收),且此时引用的是原始异常对象,原始异常对象的生命周期也被延长至catch块末尾。

异常对象的拷贝在catch块运行结束之后销毁。


 3. 异常重新抛出

在catch块中再次使用throw语句会将当前catch块捕获到的异常原样抛出

// 下面程序模拟展⽰了聊天时发送消息,发送失败补货异常,但是可能在
// 电梯地下室等场景⼿机信号不好,则需要多次尝试,如果多次尝试都发
// 送不出去,则就需要捕获异常再重新抛出,其次如果不是网络差导致的
// 错误,捕获后也要重新抛出。
void _SeedMsg(const string& s)
{if (rand() % 2 == 0){throw HttpException("网络不稳定,发送失败", 102, "put");} else if (rand() % 7 == 0){throw HttpException("你已经不是对象的好友,发送失败", 103, "put");} else{cout << "发送成功" << endl;}
}void SendMsg(const string& s)
{// 发送消息失败,则再重试3次for (size_t i = 0; i < 4; i++){try{_SeedMsg(s);break;} catch(const Exception & e){// 捕获异常,if中是102号错误,网络不稳定,则重新发送// 捕获异常,else中不是102号错误,则将异常重新抛出if (e.getid() == 102){// 重试三次以后失败了,则说明网络太差了,重新抛出异常if (i == 3)throw;cout << "网络较差,开始第" << i + 1 << "重试" << endl;} else{throw;}}}
} int main()
{srand(time(0));string str;while (cin >> str){try{SendMsg(str);} catch(const Exception & e){cout << e.what() << endl << endl;} catch(...){cout << "Unkown Exception" << endl;}}return 0;
}

4. 异常安全问题

4.1 捕获意外的异常和未知异常

前面说过,异常如果未被捕获就会成为错误,所以我们在main函数中一般会这样来写以避免异常未被捕获的情况:

int main()
{try{Func();} catch(const Exception & e){cout << e.what() << endl << endl;} catch(...){cout << "Unkown Exception" << endl;}return 0;
}

其中,这里的Exception代表异常继承体系中所有异常的父类(自定义了异常继承体系或使用了标准库中的异常继承体系),也就是说其可以接收继承体系中任意类型的异常,从而保证意外抛出的异常也能被捕获。

"..."代表任意类型的被抛出的异常,假如被该块捕获,说明该异常不在异常继承体系中,是未知的异常。

4.2 异常处理导致的内存泄露

异常抛出后,后面的代码就不再执行,前面申请了资源(内存、锁等),后面进行释放,但是中间可能会抛异常就会导致资源没有释放,这里由于异常就引发了资源泄漏,产生安全性的问题。

我们可以采取先捕获异常并将资源释放之后重新抛出的方式处理这种情况,但这样做代码的可维护性较差,后面智能指针章节讲的RAII方式解决这种问题是更好的。

double Divide(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw "Division by zero condition!";} return(double)a / (double)b;
} void Func()
{// 这里可以看到如果发⽣除0错误抛出异常,另外下面的array没有得到释放。// 所以这里捕获异常后并不处理异常,异常还是交给外层处理,这里捕获了再// 重新抛出去。int* array = new int[10];try{int len, time;cin >> len >> time;cout << Divide(len, time) << endl;} catch(...){// 捕获异常释放内存cout << "delete []" << array << endl;delete[] array;throw; // 异常重新抛出,捕获到什么抛出什么} cout << "delete []" << array << endl;delete[] array;
} int main()
{try{Func();} catch(const char* errmsg){cout << errmsg << endl;} catch(const exception & e){cout << e.what() << endl;} catch(...){cout << "Unkown Exception" << endl;} return 0;
}

其次析构函数中,如果抛出异常也要谨慎处理,比如析构函数要释放10个资源,释放到第5个时抛出异常,则也需要捕获处理,否则后面的5个资源就没释放,也资源泄漏了。《Effctive C++》第8个条款也专门讲了这个问题,别让异常逃离析构函数


5. 异常规范

5.1 异常处理的最佳实践

在使用C++异常处理时,应当遵循一些最佳实践,包括:

  • 只在真正无法通过常规错误处理机制恢复的情况下抛出异常。
  • 尽可能地捕获和处理异常,以提供清晰的错误报告和恢复策略。
  • 不要使用裸的throw语句,总是在try块中使用。
  • 使用noexcept关键字来标记那些不应该抛出异常的函数,这有助于编译器优化性能。

5.2 noexcept关键字

对于用户和编译器而言,预先知道某个程序会不会抛出异常大有裨益,知道某个函数是否会抛出异
常有助于简化调用函数的代码。

C++98中函数参数列表的后面接throw(),表示函数不抛异常,函数参数列表的后面接throw(类型1,类型2...)表示可能会抛出多种类型的异常,可能会抛出的类型用逗号分割。

C++98的方式这种方式过于复杂,实践中并不好用,C++11中进行了简化,函数参数列表后面加noexcept表示不会抛出异常,啥都不加表示可能会抛出异常。

编译器并不会在编译时检查noexcept,也就是说如果一个函数用noexcept修饰了,但是同时又包含了throw语句或者调用的函数可能会抛出异常,编译器还是会顺利编译通过的(有些编译器可能会报个警告)。但是一个声明了noexcept的函数抛出了异常,程序会调用 terminate函数 终止程序。

noexcept(expression)还可以作为一个运算符去检测一个表达式是否有可能会抛出异常,可能会则返回false,不会就返回true。 

// C++98
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();// C++11
size_type size() const noexcept;
iterator begin() noexcept;
const_iterator begin() const noexcept;double Divide(int a, int b) noexcept
{// 当b == 0时抛出异常if (b == 0){// 假如抛出则会报错throw "Division by zero condition!";} return(double)a / (double)b;
} int main()
{try{int len, time;cin >> len >> time;cout << Divide(len, time) << endl;} catch(const char* errmsg){cout << errmsg << endl;} catch(...){cout << "Unkown Exception" << endl;} int i = 0;cout << noexcept(Divide(1, 2)) << endl;cout << noexcept(Divide(1, 0)) << endl;cout << noexcept(++i) << endl;return 0;
}

6. C++标准库中的异常继承体系

其中std::exception为所有异常类的父类,其包含一个虚函数what,该函数在被调用后返回异常信息。该继承体系中所有的子异常类都重写了该函数,以表示不同的异常信息。

 具体信息参考:exception - C++ Reference

一般公司中都会写一套自己的异常体系,标准库中的异常体系其实用的不多。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/469831.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【RabbitMQ】08-延迟消息

1. 延迟消息 2. 死信交换机 正常队列不需要接受消息。 Configuration public class NormalQueueConfig {Beanpublic DirectExchange normalExchange() {return new DirectExchange("normal.direct");}Beanpublic Queue normalQueue() {return QueueBuilder.durable(…

软件测试——认识测试

在本篇文章中&#xff0c;我会给大家说明一下几个问题&#xff1a; 什么是测试软件测试和开发的区别优秀的测试人员需要具备的素质 通过这几个问题&#xff0c;带大家了解测试这个岗位。 1. 什么是测试&#xff1f; 在我们的日常生活中就有很多测试的例子&#xff0c;比如我…

LLMs之PDF:zeroX(一款PDF到Markdown 的视觉模型转换工具)的简介、安装和使用方法、案例应用之详细攻略

LLMs之PDF&#xff1a;zeroX(一款PDF到Markdown 的视觉模型转换工具)的简介、安装和使用方法、案例应用之详细攻略 目录 zeroX的简介 1、支持的文件类型 zeroX的安装和使用方法 T1、Node.js 版本&#xff1a; 安装 使用方法 使用文件 URL&#xff1a; 使用本地路径&…

5G 现网信令参数学习(3) - RrcSetup(1)

目录 1. rlc-BearerToAddModList 1.1 rlc-Config 1.1.1 ul-AM-RLC 1.1.2 dl-AM-RLC 1.2 mac-LogicalChannelConfig 2. mac-CellGroupConfig 2.1 schedulingRequestConfig 2.2 bsr-Config 2.3 tag-Config 2.4 phr-Config 2.5 skipUplinkTxDynamic 3. physicalCellG…

力扣 LeetCode 27. 移除元素(Day1:数组)

解题思路&#xff1a; 注意&#xff1a;数组只能覆盖&#xff0c;不能删除 erase方法的复杂度为O( n )而不是O( 1 )&#xff0c;因为需要把删除后后面的数组向前移动 方法一&#xff1a;双层for循环暴力 方法二&#xff1a;快慢指针 fast表示新数组的元素 slow表示新数组元…

Redis - String 字符串

一、基本认识 字符串类型是Redis最基础的数据类型&#xff0c;关于字符串需要特别注意&#xff1a; Redis中所有的键的 类型都是字符串类型&#xff0c;⽽且其他⼏种数据结构也都是在字符串类似基础上构建的&#xff0c;例如列表和集合的 元素类型是字符串类型&#xff0c;所…

树-好难-疑难_GPT

// // Created by 徐昌真 on 2024/11/10. // #include <iostream> using namespace std;template<typename T> struct ListNode{ //新建链表节点T data; //指向下一个子节点 ListNode< TreeNode<T>* > childHead; 这里的 T 是TreeNde类型的…

Mysql数据类型面试题15连问

整数类型的 UNSIGNED 属性有什么用&#xff1f; MySQL 中的整数类型可以使用可选的 UNSIGNED 属性来表示不允许负值的无符号整数。使用 UNSIGNED 属性可以将正整数的上限提高一倍&#xff0c;因为它不需要存储负数值。 例如&#xff0c; TINYINT UNSIGNED 类型的取值范围是 0 ~…

【go从零单排】Mutexes互斥锁

&#x1f308;Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 &#x1f4d7;概念 在 Go 语言中&#xff0c;互斥锁&#xff08;Mutex&#xff09;是一种用于保护共…

LLM时代下Embedding模型如何重塑检索、增强生成

文章目录 一、背景二、C-MTEB评测结果三、性能不错的向量模型腾讯Conan系列阿里GTE系列商汤Piccolo系列合合信息acge系列智源BGE系列数元灵Dmeta系列jina系列OpenAI系列 四、业务中选择向量模型有哪些考量五、洞察与总结为什么需要RAG和Embedding向量化技术&#xff1f;RAG 和 …

[SWPUCTF 2022 新生赛]Power! 反序列化详细题解

知识点: PHP反序列化(执行顺序) 构造POP链 代码审计 题目主页: 输入框可以输入内容,习惯性先查看一下页面的源代码,收集信息 发现源码中有提示参数source 先不急,再看一下其他信息 是apache服务器,php版本为7.4.30 url传参 ?sourceindex.php 回显了index.php的源码 …

【go从零单排】Rate Limiting限流

&#x1f308;Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 &#x1f4d7;概念 在 Go 中&#xff0c;速率限制&#xff08;Rate Limiting&#xff09;是一种控制…

【GPTs】MJ Prompt Creator:轻松生成创意Midjourney提示词

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | GPTs应用实例 文章目录 &#x1f4af;GPTs指令&#x1f4af;前言&#x1f4af;MJ Prompt Creator主要功能适用场景优点缺点 &#x1f4af; 小结 &#x1f4af;GPTs指令 中文翻译&#xff1a; 任务说明 您是一款为幻灯片工…

Android Profiler 内存分析

Android studio&#xff08;下面简称AS&#xff09;为App提供的性能分析工具&#xff0c;在AS3.0替换掉旧的分析工具&#xff0c;对于其使用方法&#xff0c;官方也有对应的介绍&#xff1a;Android Profiler 对于使用方法&#xff0c;我只用到比较简单的功能&#xff0c;高级的…

[ Linux 命令基础 3 ] Linux 命令详解-文件和目录管理命令

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

HTMLCSS: 实现可爱的冰墩墩

效果演示 HTML <div class"wrap"><div class"body"></div><div class"ear"></div><div class"ear rightEar"></div><div class"leftHand"></div><div class"…

【电力系统】永磁同步电机调速系统带有扰动观测器

【电力系统】永磁同步电机调速系统带有扰动观测器( DOB)的最优滑模控制、改进补偿滑模控制、传统滑模、PID控制研究 摘要 本文研究了永磁同步电机&#xff08;PMSM&#xff09;调速系统中的不同控制策略&#xff0c;包括最优滑模控制、改进补偿滑模控制、传统滑模控制以及PID控…

TVM计算图分割--分割方式

文章目录 TVM中的计算图分割方式1. Partition Pass2. dataflow_pattern3. 内置图分割接口4. Pipeline Executor5. BYOC框架6. Collage7. UMA深度学习模型通常是用计算图来表示的。计算图是一种有向无环图,其中节点代表算子,表示一个操作,节点之间的边表示算子之间的数据依赖…

如何使用IDEA创建Maven/SSM工程?

鉴于很多学校还在教授SSMJSP&#xff0c;很多同学不会使用IDEA创建Maven工程&#xff0c;这里进行说明 windows下安装jdk并配置环境 添加链接描述Windows下安装Maven并配置环境 首先你要本地安装jdk&#xff0c;Maven并配置基础环境变量&#xff0c;然后对IDEA进行jdk、Mave…

大数据新视界 -- 大数据大厂之 Impala 性能优化:优化数据加载的实战技巧(下)(16/30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…