C++的异常处理机制
前言
- 异常是一种程序控制机制,与函数机制独立和互补:
函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它依附于栈结构,却可以同时设置多个异常类型作为网捕条件,从而以类型匹配在栈机制中跳跃回馈. - 异常设计目的:
栈机制是一种高度节律性控制机制,面向对象编程却要求对象之间有方向、有目的的控制传动,从一开始,异常就是冲着改变程序控制结构,以适应面向对象程序更有效地工作这个主题,而不是仅为了进行错误处理。
异常设计出来之后,却发现在错误处理方面获得了最大的好处。
异常基本语法
- 若有异常则通过throw操作创建一个异常对象并抛掷。
- 将可能抛出异常的程序段嵌在try块之中。控制通过正常的顺序执行到达try语句,然后执行try块内的保护段。
- 如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行。程序从try块后跟随的最后一个catch子句后面的语句继续执行下去。
- catch子句按其在try块后出现的顺序被检查。匹配的catch子句将捕获并处理异常(或继续抛掷异常)。
- 如果匹配的处理器未找到,则运行函数terminate将被自动调用,其缺省功能是调用abort终止程序。
- 处理不了的异常,可以在catch的最后一个分支,使用throw语法,向上扔。
这是一个C++中异常的抛出机制:
#include <iostream>
using namespace std;
void divde(int x, int y)
{if (y == 0){throw x; // 当y == 0时,抛出异常}cout << "y/x的结果" << x/y << endl;
}int main()
{try{divde(10, 2);divde(10, 0);}catch (int e){cout << e << "被零整数" << endl;}catch (...) //其他种类的异常处理{cout << "未知异常" << endl;}return 0;
}
异常可以不处理,继续外抛。
#include <iostream>
using namespace std;
void divde(int x, int y)
{if (y == 0){throw x; // 当y == 0时,抛出异常}cout << "y/x的结果" << x/y << endl;
}
void mydivde(int x, int y)
{try {divde(x, y);}catch (...){cout << "我不处理异常" << endl;throw;}
}
int main()
{try{mydivde(100, 0);}catch (int e){cout << e << "被零整数" << endl;}catch (...) //其他种类的异常处理{cout << "未知异常" << endl;}return 0;
}
#include <iostream>
using namespace std;void testFunction1() {if (1) throw 1;
}
void testFunction2() {try {testFunction1();}catch (char) /*异常时严格的按照类型匹配的*/{cout << "期待的异常char" << endl;}
}int main()
{testFunction2();return 0;
}
throw 1将穿透函数testFunction1,testFunction2和main,抵达系统的最后一道防线——激发terminate函数(C++中,异常不可以忽略,当异常找不到匹配的catch字句时,会调用系统的库函数terminate(),该函数调用引起运行终止的abort函数。)最后一道防线的函数可以由程序员设置.从而规定其终止前的行为.
修改系统默认行为:
- 可以通过set_terminate函数修改捕捉不住异常的默认处理器,从而使得发生捉不住异常时,被自定义函数处理:
- void myTerminate(){cout<<“HereIsMyTerminate\n”;}
- set_terminate(myTerminate);
- set_terminate函数在头文件exception中声明,参数为函数指针void(*)().
栈解旋(unwinding)
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反。这一过程称为栈的解旋(unwinding)。
案例:
#include <iostream>
using namespace std;class MyException {};class Test
{public:Test(int a = 0, int b = 0){this->a = a;this->b = b;cout << "Test 构造函数执行" << "a:" << a << " b: " << b << endl;}void printT(){cout << "a:" << a << " b: " << b << endl;}~Test(){cout << "Test 析构函数执行" << "a:" << a << " b: " << b << endl;}
private:int a;int b;
};void myFunc() throw (MyException)
{Test t1(1, 2);Test t2(2, 3);cout << "要发生异常" << endl;throw 1; }int main()
{//异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,//都会被自动析构。析构的顺序与构造的顺序相反。//这一过程称为栈的解旋(unwinding)try{myFunc();}catch (int e) {cout << "接收到int类型异常" << endl;}catch (...){cout << "未知类型异常" << endl;}return 0;
}
测试结果:这个也很好理解,throw
异常相当这个函数要返回了,把异常信息返回给捕获函数。那么就要把在这个函数压栈产生的变量给进行栈解旋。
异常接口声明
- 为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型,例如:
void func() throw (A, B, C , D);
//这个函数func()
能够且只能抛出类型A B C D及其子类型的异常。 - 如果在函数声明中没有包含异常接口声明,则次函数可以抛掷任何类型的异常,例如:
void func();
- 一个不抛掷任何类型异常的函数可以声明为:
void func() throw();
- 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,
unexpected
函数会被调用,该函数默认行为调用terminate
函数中止程序。
异常类型和异常变量的生命周期
throw
的异常是有类型的,可以使,数字、字符串、类对象。throw
的异常是有类型的,catch
严格按照类型进行匹配。- 注意 异常对象的内存模型
下面我比较传统的错误处理机制和C++的异常处理机制
传统的错误处理机制
#include <iostream>
using namespace std;
// 传统的错误处理机制
int my_strcpu(char *dest, char *soucre)
{ if (dest == nullptr){ return 1;}if (soucre == nullptr){return 2;}// 比如做一个copy的数据限定if (*soucre == 'a'){return 3;}while (*soucre != '\0'){*dest++ = *soucre++;}*dest = '\0';return 0;
}int main()
{ int ret;char destBuff[1024] = {0};char sourceBuff[] = "123456789";ret = my_strcpu(destBuff,sourceBuff);if (ret != 0){switch (ret){case 1: cout << "destBuff is error" << endl;break;case 2: cout << "sourceBuff is error" << endl;break;case 3: cout << "copy is error" << endl;break;default:cout << "未知错误" << endl;break;}}cout << destBuff << endl;return 0;
}
异常处理机制
#include <iostream>
using namespace std;
// C++的异常错误处理机制
class BadDest {};
class BadSource {};
class BadCopy {};
void my_strcpu(char* dest, char* soucre)
{if (dest == nullptr){throw BadDest();}if (soucre == nullptr){throw BadSource();}// 比如做一个copy的数据限定if (*soucre == 'a'){throw BadCopy();}while (*soucre != '\0'){*dest++ = *soucre++;}*dest = '\0';
}int main()
{int ret;char destBuff[1024] = { 0 };char sourceBuff[] = "a123456789";try{my_strcpu(destBuff, sourceBuff);}catch (BadDest e){cout << "destBuff is error" << endl;}catch (BadSource e){cout << "sourceBuff is error" << endl;}catch (BadCopy e){cout << "copy is error" << endl;}catch (...){cout << "未知错误" << endl;}cout << destBuff << endl;return 0;
}
这个异常会throw一个匿名对象,那么这个有一个问题就值得谈论?这个匿名对象是copy给e还是e就是这个匿名对象了?先说结论:这个匿名对象是拷贝给e的。
测试代码
#include <iostream>
using namespace std;
// C++的异常错误处理机制
class BadDest {};
class BadSource {};
class BadCopy
{public:BadCopy() {cout << "BadCopy 构造函数 do" << endl;}BadCopy(const BadCopy &obj){cout << "BadCopy 拷贝构造函数 do" << endl;}~BadCopy(){cout << "BadCopy 析构函数 do" << endl;}
};
void my_strcpu(char* dest, char* soucre)
{if (dest == nullptr){throw BadDest();}if (soucre == nullptr){throw BadSource();}// 比如做一个copy的数据限定if (*soucre == 'a'){ cout << "开始throw BadCopy" << endl;throw BadCopy();}while (*soucre != '\0'){*dest++ = *soucre++;}*dest = '\0';
}int main()
{int ret;char destBuff[1024] = { 0 };char sourceBuff[] = "a123456789";try{my_strcpu(destBuff, sourceBuff);}catch (BadDest e){cout << "destBuff is error" << endl;}catch (BadSource e){cout << "sourceBuff is error" << endl;}catch (BadCopy e){cout << "copy is error" << endl;}catch (...){cout << "未知错误" << endl;}cout << destBuff << endl;return 0;
}
测试结果如下:
进一步讨论如果这个是引用的话,匿名对象是copy还是直接使用throw出来的那个对象。
#include <iostream>
using namespace std;
// C++的异常错误处理机制
class BadDest {};
class BadSource {};
class BadCopy
{public:BadCopy() {cout << "BadCopy 构造函数 do" << endl;}BadCopy(const BadCopy &obj){cout << "BadCopy 拷贝构造函数 do" << endl;}~BadCopy(){cout << "BadCopy 析构函数 do" << endl;}
};
void my_strcpu(char* dest, char* soucre)
{if (dest == nullptr){throw BadDest();}if (soucre == nullptr){throw BadSource();}// 比如做一个copy的数据限定if (*soucre == 'a'){ cout << "开始throw BadCopy" << endl;throw BadCopy();}while (*soucre != '\0'){*dest++ = *soucre++;}*dest = '\0';
}int main()
{int ret;char destBuff[1024] = { 0 };char sourceBuff[] = "a123456789";try{my_strcpu(destBuff, sourceBuff);}catch (BadDest e){cout << "destBuff is error" << endl;}catch (BadSource e){cout << "sourceBuff is error" << endl;}catch (BadCopy &e){cout << "copy is error" << endl;}catch (...){cout << "未知错误" << endl;}cout << destBuff << endl;return 0;
}
测试结果:
更进一步讨论如果这个是引用的话,匿名对象是copy还是直接使用throw出来的那个对象。
大家可以试试,你使用指针的类型接收上述代码throw的异常是接受不到,还有一个小细节,catch (BadCopy &e)
和catch (BadCopy e)
不可以同时存在,但是catch (BadCopy &e)
和catch (BadCopy *e)
可以同时存在,catch (BadCopy e)
和catch (BadCopy *e)
也是可以的。那想要让catch (BadCopy *e)
可以接受的到,那么throw就必须抛出一个地址,哪只要代码这么 throw &(BadCopy());
改一下就可以了
异常生命周期总结:
结论1:如果在接受异常的时候使用一个异常变量,则copy
构造异常变量。
结论2:如果在接受异常的时候使用一个异常引用,会使用throw
的那个匿名对象,所以引用e
还是那个匿名对象
结论3:指针和引用/变量可以同时存在,但是引用和变量不能写在一块。
结论4:若想使用指针接受异常,throw
的值是一个地址,这样子写还可能导致野指针的问题!!!地址throw
出来后析构了,但是指针还没有指向nullptr
。好的方法是throw new (BadCopy());
但是记得delete e
在捕获中避免内存泄漏。
最好的方法:引用接,简单方便安全
异常的层次结构(继承在异常中的应用)
- 异常是类 – 创建自己的异常类
- 异常派生
- 异常中的数据:数据成员
- 按引用传递异常
- 在异常中使用虚函数
案例:
案例:设计一个数组类 MyArray,重载[]操作,
数组初始化时,对数组的个数进行有效检查
1) index<0 抛出异常eNegative
2) index = 0 抛出异常 eZero
3) index>1000抛出异常eTooBig
4) index<10 抛出异常eTooSmall
5) eSize类是以上类的父类,实现有参数构造、并定义virtual void printErr()输出错误。
#include <iostream>
using namespace std;class MyArrary
{public:MyArrary(int arrayLength);~MyArrary();int& operator [](int index);int getArraryLength();
//异常处理类
public:class eSize{public:eSize(int size){mySize = size;}virtual void printError() {cout << "size" << mySize << endl;}protected:int mySize;};class eNegative : public eSize{public:eNegative(int size) : eSize(size){}virtual void printError(){cout << "eNegative is error ArrayLength is " << mySize << endl;}};class eZero : public eSize{public:eZero(int size) : eSize(size){}virtual void printError(){cout << "eZero is error ArrayLength is " << mySize << endl;}};class eTooBig : public eSize{public:eTooBig(int size) : eSize(size){}virtual void printError(){cout << "eTooBig is error ArrayLength is " << mySize << endl;}};class eTooSmall : public eSize{public:eTooSmall(int size) : eSize(size){}virtual void printError(){cout << "eTooSmall is error ArrayLength is " << mySize << endl;}};protected:
private:int arrayLength;int* mySpace;
};MyArrary::MyArrary(int arrayLength)
{if (arrayLength < 0){throw eNegative(arrayLength);}else if (arrayLength == 0){throw eZero(arrayLength);}else if (arrayLength > 1000){throw eTooBig(arrayLength);}else if (arrayLength < 10){throw eTooSmall(arrayLength);}this->arrayLength = arrayLength;mySpace = new int[arrayLength];
}
MyArrary::~MyArrary()
{if (mySpace != nullptr){delete[] mySpace;mySpace = nullptr;arrayLength = 0;}arrayLength = 0;}
int& MyArrary::operator [](int index)
{return mySpace[index];
}int MyArrary::getArraryLength()
{return arrayLength;
}int main()
{//1) index < 0 抛出异常eNegative// 2) index = 0 抛出异常 eZero// 3) index>1000抛出异常eTooBig// 4) index < 10 抛出异常eTooSmall// 5) eSize类是以上类的父类,实现有参数构造、并定义virtual void printErr()输出错误。try{MyArrary a(0);for (int i = 0; i < a.getArraryLength(); i++){a[i] = i;cout << a[i] << endl;}}catch (MyArrary::eSize & e){e.printError(); // 利用多态}catch (...){cout << "未知异常" << endl;}return 0;
}
可以使用多态和异常相结合大大的提高异常处理的灵活性。
标准程序库异常
使用标准异常库的案例:
#include <iostream>
#include <stdexcept>
using namespace std;class Teacher
{public:Teacher(int age){if (age > 100){throw out_of_range("年龄太大");}this->age = age;}
private:int age;};int main()
{try{Teacher t1(101);}catch (out_of_range e){cout << e.what() << endl;}catch (...){cout << "未知异常" << endl;}return 0;
}
C++的异常处理机制相关推荐
- Go语言的错误异常处理机制及其应用
一.背景 在日常编写golang程序或阅读别人的golang代码时,我们总会看到如下的一堆代码块: xx, err = func(xx) if err != nil {//do sth. to tac ...
- recover 没有捕获异常_GO语言异常处理机制panic和recover分析
本文实例分析了GO语言异常处理机制panic和recover.分享给大家供大家参考.具体如下: Golang 有2个内置的函数 panic() 和 recover(),用以报告和捕获运行时发生的程序错 ...
- 异常处理器详解 Java多线程异常处理机制 多线程中篇(四)
在Thread中有异常处理器相关的方法 在ThreadGroup中也有相关的异常处理方法 示例 未检查异常 对于未检查异常,将会直接宕掉,主线程则继续运行,程序会继续运行 在主线程中能不能捕获呢? 我 ...
- java异常详细讲解_Java异常处理机制的详细讲解和使用技巧
一起学习 1. 异常机制 1.1 异常机制是指当程序出现错误后,程序如何处理.具体来说,异常机制提供了程序退出的安全通道.当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器. 1.2 ...
- java提供两种处理异常的机制_浅析Java异常处理机制
关于异常处理的文章已有相当的篇幅,本文简单总结了Java的异常处理机制,并结合代码分析了一些异常处理的最佳实践,对异常的性能开销进行了简单分析. 博客另一篇文章<[译]Java异常处理的最佳实践 ...
- java异常处理机制详解
java异常处理机制详解 参考文章: (1)java异常处理机制详解 (2)https://www.cnblogs.com/vaejava/articles/6668809.html 备忘一下.
- 【Java面试题】21 Java中的异常处理机制的简单原理和应用。
[Java面试题]21 Java中的异常处理机制的简单原理和应用. 参考文章: (1)[Java面试题]21 Java中的异常处理机制的简单原理和应用. (2)https://www.cnblogs. ...
- SpringMVC异常处理机制详解[附带源码分析]
SpringMVC异常处理机制详解[附带源码分析] 参考文章: (1)SpringMVC异常处理机制详解[附带源码分析] (2)https://www.cnblogs.com/fangjian0423 ...
- c语言c2182是什么错误,C语言中一种更优雅的异常处理机制
上一篇文章对C语言中的goto语句进行了较深入的阐述,实际上goto语句是面向过程与面向结构化程序语言中,进行异常处理编程的最原始的支持形式.后来为了更好地.更方便地支持异常处理编程机制,使得程序员在 ...
- Laravel 5.5 的错误异常处理机制以及应用实例
一.前言 我们在开发项目中,难免会因为逻辑上的失误而报错,这些报错的展现形式也就是框架封装好的异常处理机制.在项目上线之前,我们还可以根据框架提供的报错信息来锁定错误代码的位置.但是项目上线之后我们是 ...
最新文章
- mysql 实验论证 innodb表级锁与行级锁
- _LVM——让Linux磁盘空间的弹性管理
- 遨游3.0 RC 版公布
- python外卷(7)--glob
- va_list/va_start/va_end的使用
- 这 10 个云计算错误,会让你的业务一蹶不振!
- at24c16如何划分出多个读写区_漫话:如何给女朋友解释为什么Windows上面的软件都想把自己安装在C盘...
- python 在线培训费用-在线Python编程培训哪家机构比较好?
- 深度学习 --- 卷积神经网络CNN(LeNet-5网络学习算法详解)
- 为何MAC的JDK/JRE大小这么小?
- 网页版微博HTML解析和提取,爬虫聚焦——以新浪微博为例
- 大小端转换定义结构体的技巧
- echarts实现平面3D柱状图
- matlab求一维热传导方程数值解代码,一维热传导方程数值解法及matlab实现
- 计算机c盘如何扩容,C盘空间不足怎么办?4种方法获得更多空间!
- 潇洒老师分享的小知识:注塑模具“压模”的原因和预防措施
- 小胡的第一篇Blog
- 听见丨戴森召回逾10万台进口空气净化暖风器 沃尔沃开始在普通家庭展开自动驾驶项目
- css和html的用法,HTML与CSS之CSS的基本使用
- 实现多个文件夹名同时重命名的操作
热门文章
- AP微积分考试备考重点
- SafeNet呼吁采用KMIP,推出首款基于硬件的企业密钥管理平台
- android源码预置apk
- ☀️在爬完一周的朋友圈后,我发现了.......惊人⚠️秘密
- 如何解决安装ESXI 5.5出现紫屏或者红屏
- 商用车车队管理系统FMS
- mingw编译FFmpeg32位和64位dll
- thesis; dissertation; treatise; paper 几种论文你分得清么?别用错场合
- 什么是 Wireframe线框图
- Flash动画学习指引六:操作动作补间