C++异常详解
1.对异常的几种处理方式
1)调用abort()
abort()函数的原型位于头文件cstlib中,其实现是向标准错误流发送abnormal program termination(程序异常终止),然后终止程序。
示例如下:
#inclide<iostream> #inclide<cstlib> double hmean(double x,double y) { if(x==-y) { std::cout<<"arguments are not valid\n"; std::abort(); } return 2*x*y/(x+y); } int main() { double x,y,z; while(std::cin>>x>>y) { z=hmean(x,y); std::cout<<"result is "<<z<<std::endl; std::cout<<"next loop "<<std::endl; } return 0; }
2)返回错误码
使用一个bool值来标记,运行结果是成功,还是失败。
示例如下:
#inclide<iostream> bool hmean(double x,double y,double &z) { if(x==-y) { return false; } z= 2*x*y/(x+y); return true; } int main() { double x,y,z; while(std::cin>>x>>y) { if(hmean(x,y,z)) { std::cout<<"result is false!"<<std::endl; } else{ std::cout<<"result is true!"<<std::endl; std::cout<<"result is "<<z<<std::endl; } std::cout<<"next loop "<<std::endl; } return 0; }
3)使用全局变量errno
出现异常时可以将全局变量errno设值,需要注意的是,要确保没有其他的函数同时在使用这个全局变量
2.异常机制
涉及try,catch,throw关键字
示例代码如下:
#inclide<iostream> double hmean(double x,double y) { if(x==-y) { throw "arguments are not valid"; } return 2*x*y/(x+y); } int main() { double x,y,z; while(std::cin>>x>>y) { try { z = hmean(x,y); } catch(const char* s) { std::cout<<s<<std::endl; std::cout<<"next loop "<<std::endl; continue; } std::cout<<"result is "<<z<<std::endl; std::cout<<"next loop "<<std::endl; } return 0; }
3.上面的示例中,我们抛出的是字符串,通常情况,我们会为每个可能出现的异常,定义一个异常类,当出现异常时,抛出该异常对象,catch块对该异常对象进行捕获。
示例代码如下:
#inclide<iostream> class bad_hmean { private: double x; double y; public: bad_hmean(int a=0,int b=0):x(a),y(b){} void mesg(); }; inline void bad_hmean::mesg() { std::cout<<"arguments are not valid "<<x<<" "<<y<<std::endl; } double hmean(double x,double y) { if(x==-y) { throw bad_mean(x,y); } return 2*x*y/(x+y); } int main() { double x,y,z; while(std::cin>>x>>y) { try { z = hmean(x,y); } catch(bad_hmean &hg) { hg.mesg(); std::cout<<"next loop "<<std::endl; continue; } std::cout<<"result is "<<z<<std::endl; std::cout<<"next loop "<<std::endl; } return 0; }
4.异常规范
我们看下面的两行代码
double hmean(int x,int y) throw(bad_thing)//可能抛出bad_thing异常
double hmean(int x,int y) throw()//不抛出异常
其中后面的throw()部分就是异常规范,指出该函数可能抛出的异常。
程序员来确定可能抛出的异常,这样并不好。
在C++11中,已经摒弃了该规范。
5.栈解退
栈解退是很重要的概念。为什么这么说呢,当抛出异常后,程序终止,或被catch块捕捉,我们必须要考虑内存的释放问题。
我们先看一下,正常函数是如何处理内存释放的。
函数调用时,调用函数的指令的地址会放到栈中,函数的参数或局部变量也将被添加到栈中或堆中。
如果在其中又调用了函数,则执行同样的操作。
当函数结束以后,程序会跳到被调用时存储的地址处,栈顶的元素被释放,同时释放其自动变量。
如果自动变量时类对象,则类的析构函数将被调用。
当函数出现异常时,程序也将不断释放栈,直到找到一个与该异常相对应的try块的返回地址。
随后,控制权将转到块尾的catch处理程序,而不是函数调用后面的第一条语句,这个过程称为栈解退。
和正常函数调用不同的是,函数返回将处理该函数放在栈中的对象,而函数异常则处理,try块和throw之间放在栈中的对象。
有了栈解退机制,引发异常后,也会释放调用中间函数时栈中的对象。
我们看看下面的两个例子:
void test1() { string mesg("hello"); if(false) throw exception(); return; } void test2() { double *ar = new double[n]; if(false) throw exception(); delete [] ar; return; }
对于test1,函数异常后,会进行栈解退,string类析构函数会被调用,占用的内存将释放。
对于test2,栈解退时,将删除栈中变量ar,但ar指向的内存块未释放,并且不可访问,会造成内存泄漏的问题。此时要如何处理呢?
可以在引发异常的代码块中,捕获该异常,释放该内存块,然后重新引发异常。此时,内存块就被释放了。
void test2() { double *ar = new double[n]; try { if(false) throw exception(); } catch(exception &ex) { delete[] ar; throw; } delete [] ar; return; }
6.关于catch块
try {} catch(exception &ex) {}
注意catch块中参数为引用类型。当throw异常向上抛出时,该异常对象会被释放,此时catch块接收的异常对象为原始异常对象的一个副本。
使用引用的目的是,基类引用可以执行派生类对象。若不使用引用,则只能调用基类的特性。
7.C++标准异常库
标准异常类exception在头文件exception中定义,类含有一个名为what()的虚拟成员函数。从exception派生的类可以重新定义它。
C++库定义了很多基于exception的异常类型,此处不再详细介绍。
参考资料:《C++ Primer.Plus》 pp.616-642