异常处理
C++的异常机制为程序员提供了一种可以更加自然处理异常的方法。
好处:使用异常可以使错误和处理分开来,由库函数抛出异常,由调用者捕获并处理异常,是否终止异常就由调用者决定。
异常的处理结构
try{
//可能引发异常的代码
}
catch(ErrorType error){
//异常处理
}
catch(...){
//省略号代表可以接受任何类型的异常
}
抛出异常
使用throw 异常对象
语句
两个含义:
沿着调用链的函数将提早退出,类似于return语句。
一旦程序开始执行异常处理代码,沿着调用链创建的对象(局部对象,**对于动态分配的变量将不会销毁)将被销毁。
栈展开:栈展开过程沿着潜逃函数的调用链不断查找,知道找到与异常相匹配的catch字句为止或未找到catch子句,则退出主函数。
重新抛出异常
当捕获了一个异常,当当前无法处理时,我们可以先处理此时可以完成的处理,然后再次调用throw;
抛出异常。
当catch异常声明语句是引用,在catch子句中对异常对象修改的内容才能被保留并继续传播
catch(error &err){
throw;
}
异常对象
throw语句中的表达对象必须是完全类型的。
如果语句是类类型的,则必须包含一个可访问的析构函数和一个可访问的拷贝构造函数或移动构造函数。
如果语句时数组类型或函数类型,则表达式将被转换成与之对应的指针类型,注意:抛出一个局部对象的指针是错误的
当抛出了一条表达式时,该表达式的静态编译类型决定了异常对象的类型。
捕获异常
catch语句的异常声明类似于函数的声明
可以忽略捕获形参的名字
声明的类型必须是完全类型,可以是引用,指针,还可以加上
const
异常的匹配规则比正常的转换规则相比,受到了更多限制
必须正确处理捕获异常的顺序,派生类的catch子句应该位于基类的catch子句之前。
如果未捕获异常,将会调用函数
std::terminate()
,默认情况时调用abort,可以调用set_terminate()来设置终止函数,参数是一个函数指针,类型是:void (*terminate)()。
try{
throw A();
}catch(B b){
//什么时候可以被捕获呢
}
A和B时相同的类型。
B是A的直接或间接基类,此时B会丢失A的部分信息。
A,B是指针或引用,与普通类型的引用和指针规则一致。
函数抛出异常的描述(C++11已经取消该方案)
可以将可能抛出的异常集合作为函数声明和定义的一部分,如果函数违反了这个规定,将会转换为一个std::unexpected()
调用,默认是调用std::terminate()
,通常 是调用abort()
。
int a();//表示可以抛出任何异常
int b() throw();//表示不会抛出任何异常
int c() throw(x1,x2);//表示会抛出x1,x2类型以及其派生类类型的异常
示例
#include <iostream>
#include <string>
#include <exception>
using namespace std;
class MyError{
public:
MyError(string err):error(err){}
string what(){return error;}
~MyError() throw(){}
private:
string error;
};
void funCall(){
throw MyError("zhainankl");
}
int main(){
try{
funCall();
}catch(MyError e){
cout<<"MyError e:";
cout<<e.what()<<endl;
}catch(...){
cout<<"..."<<endl;
}
return 0;
}
其他
throw和throw new的区别
throw exception()
表示编译器会自动建立一个一场对象,并且由编译器负责清理对象所占内存throw new exception()
表示抛出一个由用户建立的异常对象的指针,并且在catch子句中声明为exception *
,需要由用户自己处理异常对象的内存。
建议使用throw exception()
析构函数中不能抛出异常
- 如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
- 通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。
- 析构函数的异常不能被抛出析构函数之外,需要在析构函数内部进行处理。
throw抛出的对象
如果throw抛出一个对象,那么无论catch中用什么类型接收(引用或其他),在传递到catch之前都会构造一个临时对象,也就是说至少经历了一次对象的复制,因此要求异常类型必须可复制的,并且该临时对象的类型与异常对象的静态类型是一致的,也就是说,如果throw抛出的是一个指向子类对象的父类引用,那么会发生分割现象,即只有子类对象中的父类部分会被抛出,抛出对象的类型也是父类类型。