传统的错误处理方式
1.错误码;
2.终止程序;
3.调用预先设置的回掉函数。
传统错误处理方式的缺点
1.如果返回错误码,用户必须要通过查看错误码对应的文字描述才能清楚错误,并且这种错误码表示的意义必须明确且唯一。
2.在某个大工程里面,如果由于某个错误而导致终止程序,本来这个错误可以容忍,但是程序终止就会导致正确的程序也无法正常的执行。
异常
1.异常:异常是程序在执行期间产生的问题。
2.C++异常处理涉及到3个关键字:throw,try,catch
3.throw用来抛出异常;catch:在想要处理问题的地方,通过异常处理捕获异常
try:try块中的代码标识将被激活的特定异常,try块中存放可能抛出异常的代码
异常的抛出
1.异常的抛出是通过抛出对象引发的。
2.例如:
(1)可以定义一个整型变量i,然后抛出这个整型变量
void fun1()
{
int i = 10;
throw i;
}
(2)也可以抛出一个匿名对象
void fun2()
{
throw string("string error");
}
异常的捕获
1.try块里面存放抛出异常的代码,catch块指定想要捕获的异常类型
例如:fun2里面抛出string类型的异常,在main函数里面进行捕获,并且catch的指定捕获的类型为string类型。
void fun2()
{
throw string("string error");
}
int main()
{
try
{
fun2();
}
catch (string& e)
{
cout << e << endl;
cout << "捕获" << endl;
}
system("pause");
return 0;
}
2.异常的抛出是通过抛出一个对象引发的,该对象的类型决定了应该激活哪个处理代码。
例如:上面fun2里面抛出了一个string对象,则会激活catch里面对string类型的处理代码。
3.被选中的处理代码是调用链中与该对象类型匹配且离抛出异常最近的哪一个。
例如:main函数中调用fun3,fun3里面调用fun2,fun2里面抛了一个string类型的异常,则函数调用链变为:
程序在执行过程中,发现fun2里面抛了一个异常,而fun2里面没有处理,就会在fun3里面找,发现fun3里面处理了,就会顺序执行,如果发现fun3里面没有处理,就会在main函数里面去找,如果main函数里面没有类型匹配的,就会终止掉程序。
void fun2()
{
throw string("string error");
}
void fun3()
{
try
{
fun2();
}
catch (int e)
{
cout << "int类型" << endl;
}
catch (string& e)
{
cout << "fun3捕获" << endl;
}
}
int main()
{
try
{
fun3();
}
catch (string& e)
{
cout << "main函数捕获" << endl;
}
system("pause");
return 0;
}
4.当采用throw抛出一个对象时,并不是直接将该对象直接传给catch参数的那个对象,因为throw抛出的是一个临时对象,当出了作用域临时对象就不会存在了,所以编译器会生成一个临时对象传给catch,该临时对象的声明周期直到有catch捕获该类型的对象为止。即抛出异常后会释放局部对象,所以抛出的对象也就还给了操作系统。
5.当抛出一个异常的时候,将暂停当前函数的执行,开始查找对应的匹配的catch子句。没有捕获之前,不会按照顺序执行相应的代码。当找到匹配的catch子句并处理之后,会继续沿着catch子句后面继续执行。
例如:fun2里面抛出一个string类型的异常,如果按照顺序执行会输出”fun2()”,fun3里面虽然try了,但是没有类型匹配的,在main函数里面有类型匹配的。
void fun2()
{
throw string("string error");
cout << "fun2()" << endl;
}
void fun3()
{
try
{
fun2();
}
catch (int& e)
{
cout << "fun3捕获" << endl;
}
cout << "fun3()" << endl;
}
int main()
{
try
{
fun3();
}
catch (string& e)
{
cout << "main函数捕获" << endl;
}
cout << "main函数" << endl;
system("pause");
return 0;
}
运行结果:(可以发现,并没有输出”fun2()”和”fun3()”,但是当main函数里面捕获异常之后输出了”main函数”.
异常的匹配规则
1.异常对象的类型必须和catch说明符的类型完全匹配,但是有下面几种情况例外:
(1)允许非const向const类型的对象转换。
(2)允许从派生类到基类类型的转换。(切片)
例如:定义两个类A和B,B公有继承了A;B里面有一个fun1,fun1里面抛了一个异常;但是main函数catch里面只捕获了A类型的对象,程序没有终止,说明将B类里面的异常捕获了,又根据输出结果,说明异常对象可以从派生类到基类进行转换。
class A
{
public:
A(int a)
:_a(a)
{}
public:
int _a;
};
class B :public A
{
public:
B(int a = 0, int b = 0)
:A(a)
, _b(b)
{}
void fun1()
{
cout << "A::fun1()" << endl;
throw B(1,10);
}
public:
int _b;
};
int main()
{
B b;
try
{
b.fun1();
}
catch (A& e)
{
cout << "catch (A& e)" << endl;
}
system("pause");
return 0;
}
(3)允许将数组转换为指向数组的指针,将函数转换为指向函数的指针。
异常的重新抛出
1.有的时候,有可能单个的catch不能完全处理一个异常,在进行一些校正之后,希望交给更外层的调用链函数来处理。
例如:fun2在new和delete之间调用了fun1,而fun1里面抛了一个异常,但是fun2里面没有捕获,就会导致fun2里面new了但没有delete。
void fun1()
{
throw string("throw string exception");
}
void fun2()
{
int* p = new int(2);
fun1();
delete p;
}
所以,我们可以采用异常的重新抛出,让fun2里面先捕获fun1里面的异常,并且在catch里面进行空间的释放,再让更上一层main函数来处理这个异常。
注:catch(…)表示捕获任意类型的异常。
void fun1()
{
throw string("throw string exception");
}
void fun2()
{
int* p = new int(2);
try
{
fun1();
}
catch (...)
{
delete p;
throw; //重新抛出,并且后面可以不用带对象
}
delete p;
}
异常与构造函数&析构函数
1.尽量不要在构造函数里面抛异常,因为构造函数是 为了完成对象的初始化,如果在构造函数里里面抛异常就会导致对象不完整。
2.也尽量不要在析构函数里卖弄抛异常,因为析构函数是为了完成资源的清理,如果在析构函数里里面抛异常,就有可能导致资源没有清理完成。