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;
}









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