今天我们来说说C++中有关异常的处理方法,异常的常见用法及特性。
一,有关错误的处理方法
- 返回错误码;
- 终止程序;
- 返回合法值,让程序处于某种非法的状态;
- 调用一个预先设置好出现错误时调用的函数——回调函数;
- 异常处理;
当一个函数发现自己无法解决的错误时抛出异常,让函数的调用者直接或间接的解决这个问题。
二,异常的抛出和捕获
- 异常是通过抛出对象来引发的,该对象的类型决定应该激活哪个处理代码。
- 被选中的处理代码是调用链中与该对象类型匹配且距离抛出异常位置最近的那一个。
- 抛出对象后会释放局部存储对象,所以被抛出的对象也就还给系统了。throw表达式会初始化一个抛出特殊的异常对象副本(匿名对象),异常对象由编译管理,异常对象在传给catch处理之后撤销。
三,栈展开
抛出异常的时候,将暂停当前函数的执行,开始查找对应的匹配catch字句。
首先,检查throw是否在try块内部,如果是在查找匹配的catch语句;
如果有匹配的就进行处理,没有则退出当前函数栈,继续在调用函数的栈中进行查找;
不断重复上述过程,直到main函数的栈,如果没有找到,则终止程序。
上述这个沿着调用链查找匹配的catch字句的过程称为栈展开。如下图所示:
注:找到匹配的catch字句后,会继续沿着catch字句后面继续执行。
四,异常捕获的匹配规则
异常对象的类型与catch说明符的类型必须完全相同。
但是以下情况例外:
- 允许非const对象向const对象的转换;(大范围->小范围)
- 允许派生类型向基类类型的转换;(切割)
- 将数组转换为指向数组的指针,将函数转换为指向函数的指针。
#include<iostream>
#include<string>
using namespace std;
class Exception
{
public:
Exception(int errID,const char* errMsg)
:_errID(errID),_errMsg(errMsg)
{}
void What() const
{
cout << "errID:" << _errID << endl;
cout << "errMsg:" << _errMsg << endl;
}
private:
int _errID; //错误码
string _errMsg; //错误信息
};
void Fun1(bool isThrow)
{
if (isThrow)
{
throw Exception(1, "抛出异常Exception");
printf("Fun1(%d)\n",isThrow);
}
}
void Fun2(bool isThrowString, bool isThrowInt)
{
if (isThrowString)
{
throw string("抛出string对象");
}
if (isThrowInt)
{
throw 7;
}
printf("Fun2(%d,%d)\n", isThrowString, isThrowInt);
}
void Func()
{
try
{
Fun1(true);
Fun2(true,true);
}
catch (const string& errMsg)
{
cout << "Catch string Object:" << errMsg << endl;
}
catch (int errID)
{
cout << "Catch int Object:" << errID << endl;
}
catch (const Exception& e)
{
e.What();
}
catch (...)
{
cout << "未知异常" << endl;
}
printf("Func()\n");
}
int main()
{
Func();
system("pause");
return 0;
}
五,异常的重新抛出
有可能单个catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数来处理。
class Exception
{
public:
Exception(int errID=0,const char* errMsg="")
:_errID(errID),_errMsg(errMsg)
{}
void What()
{
cout << _errID << endl;
cout << _errMsg << endl;
}
protected:
int _errID;
string _errMsg;
};
void Fun1()
{
throw string("Throw Fun1 string");
}
void Fun2()
{
try
{
Fun1();
}
catch (string & errMsg)
{
cout << errMsg << endl;
}
}
void Fun3()
{
try
{
Fun2();
}
catch (Exception &e)
{
e.What();
}
}
int main()
{
Fun3();
system("pause");
return 0;
}
六,异常与(构造函数与析构函数)
1,构造函数完成对象的构造和初始化,需要保证的是,不要在构造函数中抛出异常,否则可能导致对象的不完整或没有完全初始化。
2,析构函数主要是完成资源的清理,需要保证的是,不要在析构函数中抛出异常,否则可能导致资源泄漏。
七,异常相比于返回错误码的优缺点
优点:
- 能准确清楚地表达错误原因。而错误码只是整型,不能描述错误的原因;
- 很多第三方库在使用异常,关闭异常将导致难以与之结合;
- 再测试框架中,异常好用;
缺点:
1,异常打乱执行流,影响调试分析代码;
2,异常有异常安全的问题,需配合RAII使用;