在介紹C++異常處理之前先回顧一下在C語言中我們是如何來處理異常的。
1、終止程序(除數爲零)
2、返回錯誤值(errno,GetLastError()獲取系統出現的最近的錯誤碼)
3、返回合法值,讓程序處於某種非法狀態(atoi函數)
4、調用程序預先準備好的在出現錯誤時用的函數(回調函數)
5、直接退出,暴力解決(abort(),exit()函數)
6、使用goto語句
7、setjmp()與longjmp()結合
6、7可以看一下如下代碼:
#include<iostream>
using namespace std;
int Div(int left,int right)
{
int ret = 0;
if(right == 0)
goto R;
http://www.baidu.com; //此處http也爲標籤,所以不會出現編譯錯誤
ret = left /right;
R: return ret;
}
int main()
{
cout<<Div(10,0)<<endl;//現在除數爲0會返回ret的初值0
system("pause");
return 0;
}
#include<iostream>
using namespace std;
#include<setjmp.h>
jmp_buf buf;
void FunTest1()
{
longjmp(buf, 1);
}
void FunTest2()
{
longjmp(buf, 2);
}
int main()
{
int iState = setjmp(buf);//setjmp初值爲0,應注意 setjmp應先調用,並且在調用setjmp的函數返回之前調用longjmp
if(iState == 0)
{
FunTest1();
FunTest2();
}
else
{
switch(iState)
{
case 1:
cout<<"FunTest1()"<<endl;
break;
case 2:
cout<<"FunTest2()"<<endl;
break;
}
}
system("pause");
return 0;
}
接下來我們看看C++是如何處理異常的
異常的拋出和捕獲
1、異常是通過拋出對象引發的,該對象類型決定了該激活哪個處理代碼
2、被選中的處理代碼是調用鏈中與該對象類型匹配,且離拋出異常最近的那個
3、拋出異常後會釋放局部存儲對象,所以被拋出的對象也就還給系統了,throw表達式會初始化一個拋出特殊的異常對象副本,異常對象由編譯管理,異常對象再傳給對應的catch處理之後撤銷
void FunTest()
{
FILE *fp = fopen("1.txt", "rb");
if(NULL == fp)//打開文件失敗
{
char err = 2;
cout<<(int*)&err<<endl;//err的地址004AFA27
throw err;//拋出特殊的異常對象副本
}//若FunTest()中有catch塊就在FunTest中直接捕獲
}
int main()
{
try
{
FunTest();
}
catch(char& err1)//按類型捕獲,但並不是FunTest中throw的err
{
cout<<(int*)&err1<<endl;//err1的地址004AF95B,與err不同
}
system("pause");
return 0;
}
棧展開
我們先看下面一段代碼
void FunTest1()
{
FILE *fp;
try
{
fp = fopen("1.txt","rb");
if(NULL == fp)
{
int err = 1;
cout<<(int*)&err<<endl;
throw err;
}
}
catch(char err)
{
return;
}
fclose(fp);
}
void FunTest2()
{
int* p =(int*)malloc(0x7fffffff);
if(NULL == p)
{
throw 2;
}
free(p);
}
void FunTest3()
{
int* p = new int[10];
try
{
FunTest1();
}
catch(int err)
{
delete[] p;
throw;
}
}
int main()
{
try
{
FunTest1();
FunTest2();
FunTest3();
}
catch(int& err1)
{
cout<<(int*)&err1<<endl;
switch(err1)
{
case 1:
cout<<"打開文件失敗"<<endl;
case 2:
cout<<"申請空間失敗"<<endl;
}
}
catch(char err1)
{}
catch(double err1)
{}
catch(...)
{
cout<<"未知錯誤"<<endl;
}
system("pause");
return 0;
}
在拋出異常的時候,會暫停當前函數的執行,開始查找對應的匹配catch子句。
首先檢查throw本身是否在try塊內,如果是再查找匹配的catch語句。若有匹配的則處理。
像上面示例的程序中throw在try塊內,但當前函數中沒有匹配的catch。程序會退出當前函數棧,在調用函數(FunTest3)的棧中進行查找,在FunTest3中雖然捕獲到了FunTest的錯誤,但捕獲後它又重新拋出,最後只能在主函數中尋找,找到對應catch。若到達main函數的棧依然沒有匹配的,就會終止程序。
所以把沿着調用鏈查找匹配的catch子句的過程叫做棧展開。如下圖。
異常捕獲一些規則
1、允許非const到const轉化
2、允許派生類到基類轉化(派生類是一個基類 is-a)
3、允許數組到指針轉化
4、允許函數到函數指針的轉化
void FunTest1()
{
int err = 1;
throw err;
}
class B
{};
class C:public B
{};
void FunTest2()
{
throw C();
}
void FunTest3()
{
int array[10];
throw array;
}
void FunTest5()
{
cout<<"FunTest5()"<<endl;
}
void FunTest4()
{
throw FunTest5;
}
int main()
{
try
{
FunTest4();
FunTest3();
FunTest2();
FunTest1();
}
catch(const int& err)
{
cout<<err<<endl;
}
catch(B& err)
{
cout<<"B()"<<endl;
}
catch(int* err)
{
cout<<"int*"<<endl;
}
catch(void(*p)())
{
p();
}
return 0;
}
注:
構造函數不能拋出異常,因爲構造函數完成對象的構造和初始化,若拋出異常可能導致對象不完整或沒有完全初始化
析構函數也不能拋出異常,因爲析構函數完成資源的清理,若在此拋出異常可能導致內存泄露。
自定義異常類:
#include<iostream>
#include<string>
using namespace std;
////////////////////////////////////////////頂層類 Exception///////////////////////
class Exception
{
public:
Exception(int errID, string strErrCode)
: _errID(errID)
, _strErrCode(strErrCode)
{}
virtual void What()const = 0;
protected:
int _errID;
string _strErrCode;
};
class DBException:public Exception
{
//
public:
DBException(int errID, string strErrCode)
: Exception(errID, strErrCode)
{}
virtual void What()const
{
cout<<"錯誤碼:"<<_errID<<endl;
cout<<"描述符: "<<_strErrCode<<endl;
}
};
class NetException:public Exception
{
//
public:
NetException(int errID, string strErrCode)
: Exception(errID, strErrCode)
{}
virtual void What()const
{
cout<<"錯誤碼:"<<_errID<<endl;
cout<<"描述符: "<<_strErrCode<<endl;
}
};
// 數據庫
//
void FunTest1()
{
DBException db(1, "插入數據失敗");
throw db;
}
void FunTest2()
{
NetException ne(2, "網絡異常");
throw ne;
}
int main()
{
try
{
FunTest1();
FunTest2();
}
catch(const Exception& e)
{
cout<<typeid(e).name()<<endl;
e.What();
}
catch(const exception& e)
{
cout<<typeid(e).name()<<endl;
e.what();
}
catch(...)
{
cout<<"未知異常!"<<endl;
}
system("pause");
return 0;
}