异常
程序结束的方式:
1、正常结束 ----- main最后一条语句 -------return 0;
2、异常结束:
1.自杀:自己把自己结束
int Div(int left,int right)
{
if(0 == right)
exit(0);
return left/right; }
2.他杀:delay将程序杀死
C语言中传统处理异常的方式:
- 终止程序,如if、assert,缺陷:用户难以接受。如发生内存错误,除0错误时就会终止程序。
- 返回错误码:
缺陷:需要程序员自己去查找对应的错误。如系统的很多库的接口函数都是通过把错误码放到errno中,表示错误 - C 标准库中setjmp和longjmp组合。这个不是很常用,实际中C语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误。
注:虽然goto语言看似是处理异常事件的更可行方案,不幸的是,goto是本地的:它只能跳到所在函数内部的标号上,而不能将控制权转移到所在程序的任意地点(当然,除非你的所有代码都在main体中)。
jmp_buf buff; //定义结构体类型的变量
void TestFunc1()
{
FILE* file = fopen("1.txt", "r");
if (nullptr == file)
{
//可以用goto跳转
longjmp(buff,1);
}
fclose(file);
}
void TestFunc2()
{
int* p = (int*)malloc(sizeof(int)* 0xffffffff);
if (nullptr == p)
{
longjmp(buff, 2);
}
free(p);
}
int main()
{
//设置跳转点
int state = setjmp(buff); //调用longjmp之前必须使用setjmp设置跳转点
if (0 == state)
{
TestFunc1();
TestFunc2();
}
else
{
switch (state)
{
case 1 :
std::cout << "文件打开失败" << std::endl;
break;
case 2 :
std::cout << "malloc申请空间失败" << std::endl;
default:
std::cout << "未知错误" << std::endl;
break;
}
}
system("pause");
return 0;
}
C++中的异常处理
C++中的异常概念:异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接调用处理这个错误。
- throw:当问题出现时,程序就会抛出一个异常。这是通过使用throw关键字来完成的
- catch:在你想要处理问题的,通过异常处理程序捕获异常,catch关键字用于异常捕获,并且可以多个catch进行捕获
- try:和catch连在一起,讲有可能发生异常的代码块放入try,后面连接一个或多个catch。try块中的代码为保护代码
//try&catch使用方式。
try {
// 保护的标识代码
}
catch(ExceptionName e1 ){
// catch 块
}
catch( ExceptionName e2 ){
// catch 块
}
catch( ExceptionName eN ) {
// catch 块
}
异常的抛出和匹配原则
- 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
- 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
- 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象(副本),所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)
- catch(…)可以捕获任意类型的异常,问题是不知道异常错误是什么。
- 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕 获,这个在实际中非常实用.
如果抛出异常的类型,是数组类型,则捕获时捕获数组首地址
异常安全
- 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化
- 析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句 柄未关闭等)
- C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和 unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题。
异常规范
- 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的后面接 >throw(类型),列出这个函数可能抛掷的所有异常类型。
eg:void TestFunc()throw(int,double) //只接受int和double类型的异常- 函数的后面接throw(),表示函数不抛异常。
但是在VS中并不支持异常规范,Linux系统支持- 若无异常接口声明,则此函数可以抛掷任何类型的异常。
异常的优缺点
- 优点:
1.可以更准确清晰的体现程序错误类型,相比与错误码,可以更好的定位程序中的bug
2.由于许多第三方库(boost、gtest、gmock)都包含异常,所以我们在使用的时候也需要用到异常
3.很多测试框架都要使用异常,这样能更好的使用测试单元进行白盒的测试
4.部分函数使用异常更好处理,例如构造函数没有返回值,不方便使用错误码方式处理。比如T& operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误
5.返回错误码的传统方式存在很大的弊端,在函数调用链中,函数深层出现错误,但是要通过层层返回,在最外层返回错误码 - 缺点:
1.异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会导致我们跟 踪调试时以及分析程序时,比较困难
2. 异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计
3. C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。 这个需要使用RAII来处理资源的管理问题。学习成本较高
4. C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。
5. 异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言.所以异常规范有两 点:一、抛出异常类型都继承自一个基类。二、函数是否抛异常、抛什么异常,都使用 func() throw();的方式规范化。