C++异常处理

C++异常处理

在我们以前的学习中,总会有很多控制代码出错的地方,类似于段错误等,在我们以前的处理办法中,一般有以下几种处理方式:

  1. 终止程序(比如段错误)
  2. 返回错误码
  3. 返回合法值
  4. 调用一个预先设置好的函数(回调函数)

可是以上几种方法皆有很多弊端,第一种直接终止程序说到底并没有解决问题,我们不会解决问题也不知道问题出在哪里,第二种方法返回错误码我们知道了错误的地方可是依旧没办法解决,第三个事实上还是让程序处于某种非法状态,第四种方法是不错可是我们C++中由于语言特性提出了一种新的方法——异常处理
异常是当一个函数发现自己无法处理的错误时抛出异常,让函数调用者直接或间接的处理这个问题
也就是《C++ primer》中说的:将问题检测和问题处理相分离。

异常的基本语法

C++中异常是先抛出,后面处理的时候进行捕捉处理,所以一共有三个关键字:throw,try,catch

  • throw:使用 throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。
  • catch:catch语句用于捕捉异常,然后进行处理
  • try:try块中的代码将被激活特定异常,后面跟着一个或多个catch

通常我们是在一个函数内抛出异常,当我们对函数进行异常声明的时候,那么这个函数就只能抛出固定类型的异常

void fun(int a) throw(x1,x2)

这就代表这个函数只能抛出x1,x2两种类型的异常,要是抛出了x4类型的异常的话会转换为一个std::unexpected()调用,默认是调用std::terminate(),通常是调用abort()。
当函数不进行异常描述的时候,他就有可能抛出所有类型的异常

void fun(int a)

这个代表函数可以抛出任何类型的异常

void fun(int a)throw()

这个代表函数不能抛出任何类型的异常

异常的抛出和捕获

栈展开:

  1. 异常是通过抛出对象引发的,该对象的类型决定了应该激活那个处理代码
  2. 被选中的处理代码是调用链中与该对象类型匹配并且离抛出异常位置最近的那一个
  3. 抛出异常后会释放局部存储对象,所以被抛出的对象也就还给了系统,throw表达式会初始化一个抛出特殊的异常对象副本,异常对象由编译管理,异常对象再传给相应的catch后销毁

当捕获异常的时候只能捕获try块内的异常

try
{
throw A();
}
catch(A a)
{
}
catch(...)
{
}

当我们在try块内抛出一个A类型的异常的时候,try底下的catch会进行匹配,当匹配到A类型的catch的时候进行捕获,当没有捕获到的时候就捕获到catch(…)代表捕获任意类型的异常,一般是捕捉未知异常。

异常的捕获匹配规则

异常对象的类型与catch说明符的类型必须完全匹配,只有以下几种情况例外

  1. 允许从非const对象到const对象的转换
  2. 允许从派生类到基类的转换
  3. 将数组转换为指向数组类型的指针,将函数转换为指向函数类型的指针

下面用一段代码来演示最常见的异常处理

class Exception
{
public:
 Exception(int errid,const string errmsg)
  :_errid(errid)
  , _errmsg(errmsg)
 {}
 virtual void what() const
 {
  cout << _errid << endl;
  cout << _errmsg << endl;
 }
private:
 int _errid;
 string _errmsg;
};
void fun1()
{
 throw Exception(1, "抛出Expection类型异常");
}
void fun2()
{
 throw string("抛出string类型异常");
}
void fun3()
{
 throw int(2);
}
int main()
{
 try
 {
  fun1();
  //fun2();
  //fun3();
 }
 catch (Exception& e)
 {
  e.what();
 }
 catch (int errid)
 {
  cout << "捕捉到了int异常" << endl;
 }
 catch (string errmsg)
 {
  cout << "捕捉到了string异常" << endl;
 }
 catch (...)
 {
  cout << "未知异常" << endl;
 }
 system("pause");
 return 0;
}

在上面的代码中我们模拟了三个函数,三个函数中分别有三个类型的异常,当函数出现异常的时候我们就可以在函数中抛出异常,然后在try块中运行三个函数,当第一个异常被抛出后执行catch的代码,这个时候执行流混乱,所以当我们在抛异常的时候一定要注意执行流混乱。
可是在我们完成一个项目的时候,很多地方都要抛出异常,要是这么简单的抛出就会有很多问题,第一个问题, 很多函数都要抛出异常的话那么我们该怎么分配类型?第二个问题,当单个的catch不能处理这个异常的时候,我们怎么进行处理,接下来让我们探讨下这两个问题
解决第一个问题有一个很巧妙的办法,刚刚在匹配规则那里有一条是允许从派生类到基类的转换,那么我们可以定义一个基类,把他的处理函数搞成虚函数,然后后面每一个要抛异常的函数就抛它的派生类,然后重写它的处理函数,这样我们也可以捕捉到它的派生类进行处理,同时由于虚函数重写可以保证不同的函数抛出的异常处理方式不同

异常的重新抛出

解决第二个问题我们可以在进行一定得矫正处理后,交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理。
下面这一段代码演示异常的重新抛出

class Exception
{
public:
 Exception(int errid,const string errmsg)
  :_errid(errid)
  , _errmsg(errmsg)
 {}
 virtual void what() const
 {
  cout << _errid << endl;
  cout << _errmsg << endl;
 }
private:
 int _errid;
 string _errmsg;
};
void fun1()
{
 throw string( "抛出string类型异常");
}
void fun2()
{
 try 
 {
  fun1();
 }
 catch (string)
 {
  throw Exception(1, "抛出Expection类型异常");
 }
 catch (...)
 {
  cout << "未知异常" << endl;
 }
}
void fun3()
{
 try
 {
  fun2();
 }
 catch (Exception& e)
 {
  e.what();
 }
 catch (...)
 {
  cout<<"未知异常"<<endl;
 }
}
int main()
{
 fun3();
 system("pause");
 return 0;
}

在fun1函数抛出的函数我们用fun2进行捕捉然后重新抛出

异常的构造函数与析构函数

构造函数要完成对象的构造与初始化,需要保证不要在构造函数中抛出异常,否则可能导致对象不完整或者没有完全初始化。
析构函数要完成资源的清理,需要保证不要在析构函数中抛出异常,否则可能导致资源泄露(内存泄漏,句柄未关闭等)

在C++标准库中我们定义了一个exception类,我们一般通过继承这个类来完成我们的异常类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值