C语言传统处理错误的方式:
1.终止程序。这就很恶心。直接导致程序崩溃。
2.返回错误码:在出现一些错误时,会返回错误码,程序员需要到错误码表中查找对应错误信息。
C++异常
概念:当一个函数发现自己无法处理一个错误时,它就通过抛异常的方式将异常给另一个函数来处理。这里引出了throw,catch,try。
throw:将会出现异常的变量用throw关键字来修饰。
catch:将想要处理异常交给catch来处理,一个throw可以对应多个catch对应。
try:try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块,try块中放着可能抛出异常的代码。格式如下
try{
//可能会出现异常的代码
}
catch(ExceptionName e1){
}
catch(ExceptionName e2){
}
catch(ExceptionName eN){
}
异常的抛出和匹配原则
1.异常的捕获处理是根据抛出异常对象的类型来决定的。
2.选中被处理的代码是离抛出对象最近且匹配的。
3.抛出异常对象后,会生成一个异常对象的拷贝,因为有可能抛出的对象是一个临时对象,在出了作用域会被析构,当catch捕捉到后处理完后销毁。
4.抛出对象的捕获有个例外,并不是所有对象类型都匹配,如果异常对象为派生类,捕获对象类型可以使用基类类型。
5.catch(…)表示可以捕获任何类型的异常,但不知道异常错误是什么
在函数调用链中,异常栈匹配原则
1.首先看throw本身是否在try内部,如果是查找匹配的catch语句,如果有匹配的,则调到匹配的catch地方处理。
2.没有匹配的catch则退出当前函数调用栈,到其他函数调用栈找catch
3.如果到达了main函数栈还没有找到匹配的,则终止程序。沿着函数调用栈找catch的过程叫做栈展开,所以在所有catch的最后一个都要写catch(…),b不然程序就会被终止。
4.如果匹配到了catch,catch执行完后,就会执行catch后面的语句。
int Division(int a,int b)
{
if(b == 0)
throw "Division by zero condition!";
else{
return a/b;
}
}
void Func()
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
int main()
{
try{
Func();
}
catch(const char *message)
{
cout<<message<<endl;
}
catch(...)
{
cout<<"unkown exception"<<endl;
}
return 0;
}
异常的重新抛出
可能单个catch不能完全处理一个异常,在进行一个校正处理后,希望再交给更外层的调用链来处理,catch则可以通过重新抛出异常交给更上层的函数处理。
double Division(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
{
throw "Division by zero condition!";
}
return (double)a / (double)b;
}
void Func()
{
int *array = new int[10];
try {
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
catch(...)
{
delete[] array;
throw;
}
delete[] array;
}
int main()
{
try {
Func();
}
catch(char* message)
{
cout<<message<<endl;
}
}
在上面的代码中,Func函数内为array申请了内存,如果在进行Division函数时出现了b==0的情况,那么此时就会抛异常,找到了最近的catch(…)且匹配,此时释放掉了array空间,如果不进行再次抛异常,会接着执行catch(…)后的代码,后面是没有抛异常的情况,也对array空间进行释放,此时,array就被释放两次,就会发生错误。所以需要再次抛异常,然后到main函数栈内的 catch(char* message)内处理。
异常安全
1.构造函数内最好不要抛异常,可能会导致成员没有初始化完全。
2.析构函数同样也不要出现异常,可能会导致内存泄露。
3.在new和delete中抛出异常,可能会导致资源泄露的问题,在lock和unlock中出现异常,可能会导致死锁。所以C++出现了RALL思想,用只能指针能很好解决异常安全问题。
异常规范
1.在函数后面通常接throw(类型)表明函数只抛的异常类型。
2.在函数后面接throw(),表示函数不抛任何类型的异常。
3.若函数后面没有任何东西,则表明可以抛任何类型的异常。
// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator new (std::size_t size, void* ptr) throw();
异常优缺点
异常优点
1.异常定义好了,相比错误码的方式,异常更能直接的看到错误信息。
2.若使用错误码,有时候深层次的函数,需要层层返回才能得到错误码再得到错误信息,异常处理则会直接被catch得到错误信息。
3.部分没有返回值的函数使用异常更友好,比如T&operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。
异常缺点
1.异常会打断程序的执行流,在运行时就很恶心,也不方便调试。
2.异常通常会导致内存泄露,死锁等问题。
3.C++对异常体系库提供的不是很好,就需要自己去定,但是很混乱。
4.异常使用得尽量规范,否则会随意抛异常,很恶心。
5.异常还会有些性能上消耗,不过对于现在CPU可以忽略不计。