异常的抛出和捕获
(1)异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个处理代码。
( 2). 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
#include<iostream>
using namespace std;
void FunTest()
{
FILE* fp = fopen("1.txt", "rb");
if(NULL == fp)
{
throw 1;
//throw '1';
}
int *p = (int*)malloc(0x7fffffff/4);
if(p == NULL)
{
fclose(fp);//此时p为NULL,fp不为NULL,退出前关闭文件
throw 2;
//throw '2';
}
fclose(fp);//此时fp, p都不为NULL
free(p); //malloc与free同时使用
}
int main()
{
try
{
FunTest();
}
catch(int error)
{
switch(error)
{
case 1:
cout<<error<<" "<<"打开文件错误"<<endl;
break;
case 2:
cout<<error<<" "<<"开辟空间出错"<<endl;
break;
default:
break;
}
}
catch(char error)
{
switch(error)
{
case '1':
cout<<error<<" "<<"打开文件错误"<<endl;
break;
case '2':
cout<<error<<" "<<"开辟空间出错"<<endl;
break;
default:
break;
}
}
system("pause");
return 0;
}
(3). 抛出异常后会释放局部存储对象,所以被抛出的对象也就还给系统了,throw表达式会初始化一个抛出特殊的异常对象副本(匿名对象),异常对象由编译管理,异常对象在传给对应的catch处理之后撤销。
#include <iostream>
using namespace std;
void FunTest()
{
FILE* fp = fopen("1.txt","rb");
if(NULL == fp)
{
char a = '1';
cout<<(int*)&a<<endl; //看到a的地址
throw a;
}
fclose(fp);
}
int main()
{
try
{
FunTest();
}
catch(char error)
{
cout<<(int*)error<<endl; //看出error的地址
cout<<"文件打开错误"<<endl;
}
system("pause");// 两者打印的地址不同
return 0;
}
栈展开
抛出异常的时候,将暂停当前函数的执行,开始查找对应的匹配catch子句。 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。 如果有匹配的,则处理。没有则退出当前函数栈,继续在调用函数的栈中进行查找。 不断重复上述过程。若到达main函数的栈,依旧没有匹配的,则终止程序。 上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。
#include<iostream>
using namespace std;
void FunTest1()
{
FILE* fp = fopen("xy.txt","rb");
if(fp == NULL)
{
throw 1;
}
fclose(fp);
}
void FunTest2()
{
try
{
FunTest1();
}
catch(...) //捕获未知异常
{
throw;
}
}
void FunTest3()
{
try
{
FunTest2();
}
catch(int error)
{
cout<<"文件打开错误"<<endl;
}
}
int main()
{
FunTest3();
system("pause");
return 0;
}
异常捕获的匹配规则
异常对象的类型与catch说明符的类型必须完全匹配。只有以下几种情况例外
(1)允许从非const对象到const的转换。
#include <iostream>
using namespace std;
void FunTest1()
{
int a = 1;
throw a;
}
int main()
{
try
{
FunTest1();
}
catch(const int& error) //const类型引用
{
cout<<error<<endl;
}
system("pause");
return 0;
}
(2). 允许从派生类型到基类类型的转换。
#include <iostream>
using namespace std;
class A
{};
class B:public A
{};
void FunTest2()
{
throw B();
}
int main()
{
try
{
FunTest2();
}
catch(const A& a)
{
cout<<type id(a).name()<<endl;
}
system("pause");
return 0;
}
(3). 将数组转换为指向数组类型的指针,将函数转换为指向函数类型的指针。
//数组
#include <iostream>
using namespace std;
void FunTest3()
{
int a[10];
throw a;
}
int main()
{
try
{
FunTest3();
}
catch(int* p)
{
cout<<typeid(p).name()<<endl;
}
system("pause");
return 0;
}
//函数
#include <iostream>
using namespace std;
void FunTest()
{
cout<<"FunTest()"<<endl;
}
void FunTest4()
{
throw FunTest;
}
int main()
{
try
{
FunTest4();
}
catch(void (*p)())
{
cout<<typeid(p).name()<<endl;
}
system("pause");
return 0;
}
异常的重新抛出
有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链 函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理
#include <iostream>
using namespace std;
void FunTest1()
{
throw 1;
}
void FunTest2()
{
int *p = new int[10]; //new抛出bad_alloc异常
FunTest1();
delete[] p;//因为堆上的内容未清理,造成内存的泄露,
//p虽然清除了,但是p指向的内容却没有清除,引起内存泄露
}
int main()
{
try
{
FunTest2();
}
catch(int error)
{
cout<<error<<endl;
}
system("pause");
return 0;
}
#include <iostream>
using namespace std;
void FunTest1()
{
throw 1;
}
void FunTest2()
{
int *p = new int[10];
try
{
FunTest1();
}
catch(...)
{
delete[] p;
throw; //重新抛出异常
}
}
int main()
{
try
{
FunTest2();
}
catch(int error)
{
cout<<error<<endl;
}
system("pause");
return 0;
}
异常规范
在函数声明之后,列出该函数可能抛出异常类型,并保证该函数不会抛出其他类型的异常。
(1)、成员函数在类内声明和类外定义两处必须有相同的异常规范。
(2)、函数抛出一个没有被列在它异常规范中的异常时(且函数中抛出异常没有在函数内部进行处理), 系统调用C+ +标准库中定义的函数unexpected( ).
(调用默认的unexpected()函数,然而这个默认的unexpected()调用了set_terminate()中设定的终止函数。可以用set_unexpected()来设置unexpected,就像set_terminate()一样的用法,但是在设定了新的“unexpected()”之后,就不会再调用set_terminater中设定的终止函数了。)
(3)、如果异常规范为throw(),则表示不得抛出任何异常,该函数不用放在try块中。
(4)、派生类的虚函数的异常规范必须与基类虚函数的异常规范一样或更严格(是基类虚函数的异常的子集)。因为:派生类的虚函数被指向基类类型的指针调用时,保证不会违背基类成员函数的异常 规范。
深入throw:
(i)、程序接受到throw语句后就会自动调用析构器,把该域(try后的括号内)对象clean up,然后再进入catch语句(如果在循环体中就退出循环)。
这种机制会引起一些致命的错误,比如,当“类”有指针成员变量时(又是指针!),在 “类的构建器”中的throw语句引起的退出,会导致这个指针所指向的对象没有被析构。这里很基础,就不深入了,提示一下,把指针改为类就行了,比如模板类来代替指针,在模板类的内部设置一个析构函数。
(ii)、语句“throw;”抛出一个无法被捕获的异常,即使是catch(…)也不能捕捉到,这时进入终止函数
问题a:抛出异常,但是catch不到异常怎么办?(注意没有java类似的finally语句)
在catch没有捕获到匹配的异常的时候,会调用默认的终止函数。可以调用set_terminate()来设置终止函数,参数是一个函数指针,类型是:void (*terminate)()。
异常与构造函数&析构函数
(1)构造函数完成对象的构造和初始化,需要保证不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化。 (2). 析构函数主要完成资源的清理,需要保证不要在析构函数内抛出异常,否则可能导致资源泄漏 (内存泄漏、句柄未关闭等)
C++标准库定义的exception类&自定义异常类
exception类是C++定义的一个标准异常的类,通常我们通过继承exception类定义合适的异常类。
include<iostream>
using namespace std;
#include<string>
class Exception
{
public:
Exception(int errCode = 0, string strMsg = "error")
:_errCode(errCode)
,_strMsg(strMsg)
{}
virtual void What()=0;
protected:
int _errCode;
string _strMsg;
};
class DBException:public Exception
{
public:
virtual void What()
{
cout<<_errCode<<":"<<_strMsg<<endl;
cout<<"数据库异常"<<endl;
}
DBException(int errcode, string strMsg)
:Exception(errcode, strMsg)
{}
};
void OperatorDB()
{
throw DBException(501, "数据库异常");
}
class NetException:public Exception
{
public:
virtual void What()
{
cout<<_errCode<<":"<<_strMsg<<endl;
cout<<"网络异常"<<endl;
}
NetException(int errcode, string strMsg)
:Exception(errcode, strMsg)
{}
};
void OperatorNet()
{
throw NetException(502, "网络异常");
}
int main()
{
try
{
OperatorDB();
OperatorNet();
}
catch (Exception& e)
{
e.What();
}
catch (...)
{
cout<<"未知异常"<<endl;
}
system("pause");
return 0;
}