C++關於異常

在介紹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;
}









發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章