目录前言异常的使用异常的抛出和捕获栈的展开查找匹配的处理代码异常重新抛出异常安全问题异常规范标准库的异常前言异常是C处理错误的一种方式也是众多面向对象语言处理错误的方式对比于C语言处理错误的方式是错误码返回错误码(对错误信息的编号)异常抛出的和解决问题的地方是分开的可能离得比较远C语言是挨在一起的只是返回一个错误的编号异常的使用异常的抛出和捕获程序出现问题时我们通过抛出(throw)一个对象来引发一个异常double Divide(int a, int b) { try { //当b0时抛出异常 if (b 0) { string s(Divide by zero condition!); throw s; } else { return ((double)a / (double)b); } } catch(int errid) { cout errid endl; } return 0; }这里的string是遇到catch就捕获吗不一定捕获这里有个要求就是得类型匹配被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个如果有多个匹配的就选离它最近的一个也就是说会不断往上一层寻找与自己类型匹配的如果到main函数还没有找到的话就会终止程序这里还有两个重要的含义1、沿着调用链的函数可能提早退出(就是抛了异常经过其他函数但是其他函数没有捕获就会直接跳过剩下的程序)。2、一旦程序开始执行异常处理程序沿着调用链创建的对象都将销毁抛出异常对象后会生成⼀个异常对象的拷贝因为抛出的异常对象可能是一个局部对象所以会生成⼀个拷贝对象这个拷贝的对象会在catch子句后销毁。这里的处理类似于函数的传值返回下图代码为源代码在string处抛出异常后到main函数处找到类型匹配的string后被catchdouble Divide(int a, int b) { try { //当b0时抛出异常 if (b 0) { string s(Divide by zero condition!); throw s; } else { return ((double)a / (double)b); } } catch(int errid) { cout errid endl; } return 0; } void Func() { int len, time; cin len time; try { cout Divide(len, time) endl; } catch (const char* errmsg) { cout errmsg endl; } cout __FUNCTION__ : __LINE__ ⾏执⾏ endl; } int main() { while (1) { try { Func(); } catch (const string errmsg) { cout errmsg endl; } } return 0; }在这里使用了宏宏在预处理的时候就替换在预处理阶段编译器就知道我们编译哪个文件在编译文件的哪一行栈的展开这里跟我们前面讲的一样就是栈帧的销毁然后不断寻找匹配函数的过程就是异常是一定要被捕获的最晚是在main函数被捕获栈展开的过程是在编译时查找哪个try-catch对应匹配的(跟多态类似多态是在运行时决定调用哪个函数实际在编译时转换成对应的指令我指向哪个对象就去哪个对象的虚表中寻找运行就是执行这个指令)如果在mian函数还没有被捕获就会终止程序查找匹配的处理代码允许从派生类向基类类型的转换也就是说在这个过程中允许切片和切割也就是说这里抛异常的时候可以抛一个派生类然后用基类来捕获上面的场景如果一个项目很大有很多不同类型的异常抛出需要写很多catch代码就会很乱虽然语法上允许可以抛任意类型但是现实中抛任意类型肯定是不好的如下代码实际中我们的异常要包含两种消息1.错误编号2.错误信息这里在抛异常的时候不是抛exception就是exception的派生类遇到未知异常就是没有按约定抛exception或者exception派生类对象这里就解决了外层1.就不需要别人新抛个异常在这里需要单独捕获也解决了第二个问题2.抛基类或者派生类可以用基类捕获不想抛基类你可以抛派生类需要什么信息可以自己加// ⼀般⼤型项⽬程序才会使⽤异常下⾯我们模拟设计⼀个服务的⼏个模块 // 每个模块的继承都是Exception的派⽣类每个模块可以添加⾃⼰的数据 // 最后捕获时我们捕获基类就可以 class Exception { public: Exception(const string errmsg, int id) :_errmsg(errmsg) , _id(id) {} virtual string what() const { return _errmsg; } int getid() const { return _id; } protected: string _errmsg; int _id; };防止有乱抛异常的然后又没有类型匹配程序就被终止掉了就可以使用catch(...)能够保证抛了一些异常以后没有被捕获但是最终被捕获了不足之处就是捕获了但是不知道发生了什么但是拿到这个消息能够知道1.这个程序不会因为异常没有被捕获而终止2.我们找到了有乱抛异常的现象异常重新抛出也就是说在没有满足条件的情况下异常会被继续抛出// 下⾯程序模拟展⽰了聊天时发送消息发送失败补货异常但是可能在 // 电梯地下室等场景⼿机信号不好则需要多次尝试如果多次尝试都发 // 送不出去则就需要捕获异常再重新抛出其次如果不是⽹络差导致的 // 错误捕获后也要重新抛出如下图中的代码思路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; } } } }遇到抛异常就没有delete重新抛出的核心逻辑就是有时候我们要拦截下来做一些内存的释放异常安全问题以前new了以后就可以delete可以挨着执行有了抛异常不行抛异常这里会使得我们释放内存边困难就需要使用智能指针因为异常为跳转执行就会导致有些资源没能得到释放异常规范如果加了noexcept不会抛异常但是实际你又抛异常那么这个异常不会被捕获会报警告不会抛异常也可不加标准库的异常exception的派生类日常捕获派生类
C++异常处理机制详解
目录前言异常的使用异常的抛出和捕获栈的展开查找匹配的处理代码异常重新抛出异常安全问题异常规范标准库的异常前言异常是C处理错误的一种方式也是众多面向对象语言处理错误的方式对比于C语言处理错误的方式是错误码返回错误码(对错误信息的编号)异常抛出的和解决问题的地方是分开的可能离得比较远C语言是挨在一起的只是返回一个错误的编号异常的使用异常的抛出和捕获程序出现问题时我们通过抛出(throw)一个对象来引发一个异常double Divide(int a, int b) { try { //当b0时抛出异常 if (b 0) { string s(Divide by zero condition!); throw s; } else { return ((double)a / (double)b); } } catch(int errid) { cout errid endl; } return 0; }这里的string是遇到catch就捕获吗不一定捕获这里有个要求就是得类型匹配被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个如果有多个匹配的就选离它最近的一个也就是说会不断往上一层寻找与自己类型匹配的如果到main函数还没有找到的话就会终止程序这里还有两个重要的含义1、沿着调用链的函数可能提早退出(就是抛了异常经过其他函数但是其他函数没有捕获就会直接跳过剩下的程序)。2、一旦程序开始执行异常处理程序沿着调用链创建的对象都将销毁抛出异常对象后会生成⼀个异常对象的拷贝因为抛出的异常对象可能是一个局部对象所以会生成⼀个拷贝对象这个拷贝的对象会在catch子句后销毁。这里的处理类似于函数的传值返回下图代码为源代码在string处抛出异常后到main函数处找到类型匹配的string后被catchdouble Divide(int a, int b) { try { //当b0时抛出异常 if (b 0) { string s(Divide by zero condition!); throw s; } else { return ((double)a / (double)b); } } catch(int errid) { cout errid endl; } return 0; } void Func() { int len, time; cin len time; try { cout Divide(len, time) endl; } catch (const char* errmsg) { cout errmsg endl; } cout __FUNCTION__ : __LINE__ ⾏执⾏ endl; } int main() { while (1) { try { Func(); } catch (const string errmsg) { cout errmsg endl; } } return 0; }在这里使用了宏宏在预处理的时候就替换在预处理阶段编译器就知道我们编译哪个文件在编译文件的哪一行栈的展开这里跟我们前面讲的一样就是栈帧的销毁然后不断寻找匹配函数的过程就是异常是一定要被捕获的最晚是在main函数被捕获栈展开的过程是在编译时查找哪个try-catch对应匹配的(跟多态类似多态是在运行时决定调用哪个函数实际在编译时转换成对应的指令我指向哪个对象就去哪个对象的虚表中寻找运行就是执行这个指令)如果在mian函数还没有被捕获就会终止程序查找匹配的处理代码允许从派生类向基类类型的转换也就是说在这个过程中允许切片和切割也就是说这里抛异常的时候可以抛一个派生类然后用基类来捕获上面的场景如果一个项目很大有很多不同类型的异常抛出需要写很多catch代码就会很乱虽然语法上允许可以抛任意类型但是现实中抛任意类型肯定是不好的如下代码实际中我们的异常要包含两种消息1.错误编号2.错误信息这里在抛异常的时候不是抛exception就是exception的派生类遇到未知异常就是没有按约定抛exception或者exception派生类对象这里就解决了外层1.就不需要别人新抛个异常在这里需要单独捕获也解决了第二个问题2.抛基类或者派生类可以用基类捕获不想抛基类你可以抛派生类需要什么信息可以自己加// ⼀般⼤型项⽬程序才会使⽤异常下⾯我们模拟设计⼀个服务的⼏个模块 // 每个模块的继承都是Exception的派⽣类每个模块可以添加⾃⼰的数据 // 最后捕获时我们捕获基类就可以 class Exception { public: Exception(const string errmsg, int id) :_errmsg(errmsg) , _id(id) {} virtual string what() const { return _errmsg; } int getid() const { return _id; } protected: string _errmsg; int _id; };防止有乱抛异常的然后又没有类型匹配程序就被终止掉了就可以使用catch(...)能够保证抛了一些异常以后没有被捕获但是最终被捕获了不足之处就是捕获了但是不知道发生了什么但是拿到这个消息能够知道1.这个程序不会因为异常没有被捕获而终止2.我们找到了有乱抛异常的现象异常重新抛出也就是说在没有满足条件的情况下异常会被继续抛出// 下⾯程序模拟展⽰了聊天时发送消息发送失败补货异常但是可能在 // 电梯地下室等场景⼿机信号不好则需要多次尝试如果多次尝试都发 // 送不出去则就需要捕获异常再重新抛出其次如果不是⽹络差导致的 // 错误捕获后也要重新抛出如下图中的代码思路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; } } } }遇到抛异常就没有delete重新抛出的核心逻辑就是有时候我们要拦截下来做一些内存的释放异常安全问题以前new了以后就可以delete可以挨着执行有了抛异常不行抛异常这里会使得我们释放内存边困难就需要使用智能指针因为异常为跳转执行就会导致有些资源没能得到释放异常规范如果加了noexcept不会抛异常但是实际你又抛异常那么这个异常不会被捕获会报警告不会抛异常也可不加标准库的异常exception的派生类日常捕获派生类