C++异常处理
1.C++异常抛出与捕获
#include <iostream>
using namespace std;
//自定义异常类,其实跟普通类没什么区别,任何类型都可以作为异常被抛出
class MyError
{
const char * const m_pData;
public:
MyError(const char * const p=NULL):m_pData(p){}
const char * what()
{
return m_pData;
}
};
class MyErrorChild1:public MyError
{
public:
MyErrorChild1(const char * const p=NULL):MyError(p){}
};
class MyErrorChild2:public MyError
{
public:
MyErrorChild2(const char * const p=NULL):MyError(p){}
};
//函数头中throw是异常规格说明,用于告诉函数调用者该函数可能抛出的异常类型,去掉不影响执行结果
void func(int i) throw(int,MyError,MyErrorChild1,MyErrorChild2,char)
{
cout<<"begin func "<<i<<endl;
switch(i)
{
case 1:throw int(1);break; //抛出一个int类型异常,任何类型都可以作为异常抛出
case 2:throw MyError("MyError");break;
case 3:throw MyErrorChild1("MyErrorChild1");break;
case 4:throw MyErrorChild2("MyErrorChild2");break;
case 5:throw 'a';break;
default:cout<<"no throw"<<endl;
}
//异常在该函数中没有被捕获,会被抛到上层函数,下面语句会被跳过
cout<<"end func"<<endl;
}
int main()
{
while(1)
{
try
{
cout<<"please input a int(0 exit):";
int i;
cin>>i;
if (i==0) break;
func(i);
}
catch (int i) //通过catch捕获异常
{
cout<<"int catch "<<i<<endl;
}
//异常为对象时最好用引用,防止对象拷贝和对象切割
catch (MyErrorChild1 &e) //多条catch捕获不同类型的异常,异常被捕获后后续catch不再执行
{
cout<<"MyErrorChild1 catch "<<e.what()<<endl;
}
catch (MyError &e) //可以捕获MyError类型及其子类型的异常
{
cout<<"MyError catch "<<e.what()<<endl;
}
catch (...) //捕获剩余所有类型的异常
{
cout<<"catch all leave"<<endl;
//throw; //此处还可以再通过throw将异常抛出
}
//异常被捕获后,继续执行后面的语句,所以下面语句会被执行
cout<<"while end"<<endl;
}
cout<<"main exit"<<endl;
return 0;
}
执行结果:
please input a int(0 exit):1
begin func 1
int catch 1
while end
please input a int(0 exit):2
begin func 2
MyError catch MyError
while end
please input a int(0 exit):3
begin func 3
MyErrorChild1 catch MyErrorChild1
while end
please input a int(0 exit):4
begin func 4
MyError catch MyErrorChild2
while end
please input a int(0 exit):5
begin func 5
catch all leave
while end
please input a int(0 exit):0
main exit
2.标准异常--C++标准库定义的异常
#include <iostream>
using namespace std;
void func1()
{
throw bad_cast("bad_cast");
}
void func2()
{
throw bad_alloc("bad_alloc");
}
void func3()
{
throw overflow_error("overflow_error");
}
void func4()
{
throw underflow_error("underflow_error");
}
void func5()
{
throw range_error("range_error");
}
void func6()
{
throw runtime_error("runtime_error");
}
void func7()
{
throw domain_error("domain_error");
}
void func8()
{
throw invalid_argument("invalid_argument");
}
void func9()
{
throw out_of_range("out_of_range");
}
void func10()
{
throw length_error("length_error");
}
void func11()
{
throw logic_error("logic_error");
}
void func(int i)
{
cout<<"begin func "<<i<<endl;
switch(i)
{
case 1:func1();break;
case 2:func2();break;
case 3:func3();break;
case 4:func4();break;
case 5:func5();break;
case 6:func6();break;
case 7:func7();break;
case 8:func8();break;
case 9:func9();break;
case 10:func10();break;
case 11:func11();break;
default:cout<<"no throw"<<endl;
}
}
int main()
{
while(1)
{
try
{
cout<<"please input a int(0 exit):";
int i;
cin>>i;
if (i==0) break;
func(i);
}
catch (bad_cast &e)
{
cout<<"catch bad_cast "<<e.what()<<endl;
}
catch (bad_alloc &e)
{
cout<<"catch bad_alloc "<<e.what()<<endl;
}
catch (overflow_error &e)
{
cout<<"catch overflow_error "<<e.what()<<endl;
}
catch (underflow_error &e)
{
cout<<"catch underflow_error "<<e.what()<<endl;
}
catch (range_error &e)
{
cout<<"catch range_error "<<e.what()<<endl;
}
catch (runtime_error &e)
{
cout<<"catch runtime_error "<<e.what()<<endl;
}
catch (domain_error &e)
{
cout<<"catch domain_error "<<e.what()<<endl;
}
catch (invalid_argument &e)
{
cout<<"catch invalid_argument "<<e.what()<<endl;
}
catch (out_of_range &e)
{
cout<<"catch out_of_range "<<e.what()<<endl;
}
catch (length_error &e)
{
cout<<"catch length_error "<<e.what()<<endl;
}
catch (logic_error &e)
{
cout<<"catch logic_error "<<e.what()<<endl;
}
catch (exception &e)
{
cout<<"catch exception "<<e.what()<<endl;
}
}
cout<<"main exit"<<endl;
return 0;
}
3. set_terminate() 和 set_unexpected()。
(1)set_terminate(),程序中任何未被catch捕获的异常都会触发库函数terminate(),一旦terminate()被调用代表程序已经无法挽回。 库函数terminate()默认操作是调用abort异常退出程序。 可以通过set_terminate设置自定义的terminate()函数。以下情况会触发terminate()函数:
1》catch列表未捕获到异常;
2》局部对象的析构函数调用时抛出异常可以被上层函数捕获,但如果异常栈反解过程中抛出异常将无法被捕获;
3》全局对象或静态对象构造函数或析构函数调用时抛出异常;
4》库函数unexpected()被调用时会触发terminate()的调用。
(2)set_unexpected(),抛出未在异常规格说明中说明的异常时会触发库函数unexpected(),可以通过set_unexpected设置自定义unexpected函数(vc6.0不支持set_unexpected)。unexpected()中可以通过throw再将异常抛回到原点。
#include <iostream>
using namespace std;
class MyError1{};
class MyError2{};
class MyError3{};
/*
函数头中throw是异常规格说明,用于告诉函数调用者该函数只可能抛出的异常类型,throw(MyError1,MyError2) 告诉编译器func只可能抛出MyError1、MyError2两种类型的异常,如果抛出MyError3则会触发unexpected()函数。
void func(int i);告诉编译器func不确定会抛出什么类型的异常,即可能抛出任何类型的异常。
void func(int i) throw();告诉编译器func不会抛出任何类型的异常。
*/
void func(int i) throw(MyError1,MyError2)
{
cout<<"begin func "<<i<<endl;
switch(i)
{
case 1:throw MyError1();break; //MyError1会被捕获
case 2:throw MyError2();break; //MyError2不会被捕获,会触发terminate
case 3:throw MyError3();break; //MyError3不包含在异常规格列表中,会触发unexpected,my_unexpected又把异常MyError1抛出到这里,又被捕获到,程序继续运行
default:cout<<"no throw"<<endl;
}
}
//用户自定义unexpected()函数
void my_unexpected()
{
cout<<"unexpected and throw MyError1"<<endl;
throw MyError1(); //可以通过throw将异常MyError1抛回到原点,这样可能让程序继续运行;也可以调用exit(1)退出程序。
}
//用户自定义terminate()函数
void my_terminate()
{
cout<<"terminate and exit"<<endl;
exit(1); //terminate中唯一能做的就是退出程序。
}
int main()
{
void (*p)() = set_unexpected(my_unexpected);//设置自定义unexpected()
void (*q)() = set_terminate(my_terminate);//设置自定义terminate()
while(1)
{
try
{
cout<<"please input a int(0 exit):";
int i;
cin>>i;
if (i==0) break;
func(i);
}
catch (MyError1 &)
{
cout<<"catch MyError1"<<endl;
}
}
cout<<"main exit"<<endl;
return 0;
}
执行结果:
please input a int(0 exit):1
begin func 1
catch MyError1
please input a int(0 exit):3
begin func 3
unexpected and throw MyError1
catch MyError1
please input a int(0 exit):2
begin func 2
terminate and exit
4.栈反解技术
C++异常处理必须保证程序执行流程离开一个作用域的时候,对于属于这个作用域的所有由构造函数创建的对象,它们的析构函数一定会被调用。这种技术称为栈反解技术,由C++编译器实现。该技术作用于:
1》函数调用时,在栈中申请的局部对象(不能是对象指针,new创建的对象在堆上),如果构造函数调用成功后抛出异常,离开当前函数作用域时其析构函数会被调用。
2》构造函数调用过程中如果抛出异常,对象的成员对象(不能是对象指针)如果已经调用自己构造函数初始化成功的,离开当前构造函数作用域时其析构函数会被调用。
3》析构函数调用过程中如果抛出异常,对象所有成员对象都会调用其析构函数。(析构函数抛出异常程序往往认为是垃圾的程序,是不能容忍的,栈反解过程中调用析构函数如果抛出异常是无法捕获的,只能触发terminate()导致程序终止)
栈反解技术作用:所有的对象资源都变成原子资源,哪怕在异常情况下,只要对象资源被成功创建,就肯定会被释放。
#include <iostream>
using namespace std;
class CA
{
private:
static int count;
public:
CA()
{
if(count==2) throw 2;
cout<<"CA"<<++count<<endl;
}
~CA()
{
cout<<"~CA"<<count--<<endl;
}
};
int CA::count =0;
class CB
{
CA b[4];
};
class CC
{
CA b[2];
public:
~CC()
{
throw 2;
}
};
void func(int i)
{
if(i==1)
{
CA b[4]; //b[2]创建时抛出异常,b[0]、b[1]被正常释放
}
else if(i==2)
{
CB b; //b.b[2]创建时抛出异常,b.b[0]、b.b[1]被正常释放
}
else if(i==3)
{
CC c; //c析构函数抛出异常,c.b被正常释放
}
}
int main ()
{
while(1)
{
try
{
cout<<"please input a int(0 exit):";
int i;
cin>>i;
if (i==0) break;
func(i);
}
catch (...)
{
cout<<"catch exception"<<endl;
}
}
return 0;
}
以上实例程序验证了栈反解的功能,但要利用栈反解过程必须定义对象的实例(不能定义成指针形式),C++一般的解决方案是
RAII(
Resource Acquisition Is Initialization),C++内置的RAII即为智能指针auto_ptr。
//vc6.0C++库智能指针实现
template<class _Ty>
class auto_ptr
{
public:
typedef _Ty element_type;
explicit auto_ptr(_Ty *_P = 0):_Owns(_P != 0), _Ptr(_P) {}
auto_ptr(const auto_ptr<_Ty>& _Y):_Owns(_Y._Owns), _Ptr(_Y.release()) {}
auto_ptr<_Ty>& operator=(const auto_ptr<_Ty>& _Y)
{
if (this != &_Y)
{
if (_Ptr != _Y.get())
{
if (_Owns)
delete _Ptr;
_Owns = _Y._Owns;
}
else if (_Y._Owns) _Owns = true;
_Ptr = _Y.release();
}
return (*this);
}
~auto_ptr()
{
if (_Owns) delete _Ptr;
}
_Ty& operator*() const
{
return (*get());
}
_Ty *operator->()
{
return (get());
}
_Ty *get() const
{
return (_Ptr);
}
_Ty *release() const
{
((auto_ptr<_Ty> *)this)->_Owns = false;
return (_Ptr);
}
private:
bool _Owns;
_Ty *_Ptr;
};