一、异常机制的构成:
①C++有通过try-throw-catch组成的 异常抛出-捕获-继续运行 的机制:
main()
{
try{
...;
funct(); //try块中包含可能引发异常的部分
}
chatch(Type&) //捕获Type类型的异常
{
...; //异常处理语句
}
}
void funct() //函数定义
{
if(...)
throw (Type&)obj; //抛出异常Type&类型obj
...;
}
//异常机制是错误处理机制,而非错误预测机制。所有可以被捕获的异常一定都是已经被人为预测到的异常。
②异常规范:C++98中,在函数原型后使用throw(Type&)或throw()来指出函数可能抛出或不可能抛出异常:
double funct(double) throw(Type&); //指出函数可能抛出异常,编译器会检查该块(及块内所有调用)
double funct(double) throw(); //指出函数不可能抛出异常,编译器不检查该块(及块内所有调用)
//C++11中异常规范写法被废弃,而使用noexcept声明:
double funct(double) noexcept; //指出函数不可能抛出异常,编译器不检查该块
二、异常机制的原理:
①通过try块标记异常的抛出方向(返回到哪里):
main()
{
try{
funct1();
//程序断点1
funct2();
}
catch(Type&) //异常捕获2
{
...; //异常处理语句
//跳转回程序断点1处继续执行
}
}
funct1()
{
...;
throw (Type&)obj; //跳转至catch异常捕获2处
}
抛出异常(throw)的方向一定是向着最近一层的try块,此时如果try块后有相应类型的异常捕获(catch),则异常被捕获。
//使用异常类时,可以在throw处直接调用异常类构造函数以创建并抛出异常类对象:
funct1()
{
...;
throw obj(...); //调用异常类构造函数,创建并抛出异常类对象
}
②通过栈的解退释放异常引发点(throw)直至最近一层的try块间所有的栈内容:
main()
{
try{
funct1();
//程序断点1
funct2();
}
catch(Type&) //异常捕获2
{
...; //异常处理语句
//跳转回程序断点1处继续执行
}
}
funct1()
{
funct_f();
...;
throw (Type&)obj; //跳转至catch异常捕获2处,同时解退funct1()所有栈内容
}
funct_f()
{
funct_ff();
...;
throw (Type&)obj; //跳转至catch异常捕获2处,同时解退funct_f()、funct1()期间所有的栈内容
}
funct_ff()
{
...;
throw (Type&)obj; //跳转至catch异常捕获2处,同时解退funct_ff()、funct_f()、funct1()期间所有的栈内容
}
栈的解退使得程序“倒退执行”;
栈解退期间所有在stack区的指向heap区的指针,也一一被执行delete,相应的heap区内容也被释放。
//与函数返回机制不同,由于抛出的原错误对象已经在throw时被解退,所以catch语句总是创建错误对象的拷贝,即使是使用引用(catch(Type&))。
三、异常类与继承:
①异常类对象的捕获顺序:由于引用(&)的特性,异常基类的引用(&)可以指向所有派生异常类的引用。因此catch时将异常类按派生顺序反向放置,异常基类置于最后:
catch(D_D&)
{
...;
}
catch(D&)
{
...;
}
catch(B&)
{
...;
}
//catch(...)省略号(...)可以指代所有种类的异常,可以捕获任何种类的异常:
catch(D_D&)
{
...;
}
catch(D&)
{
...;
}
catch(B&)
{
...;
}
catch(...) //捕获所有种类的异常
{
...;
}
②库中已经定义的异常基类:
1>头文件<exception>中定义了类exception,常用作派生出用户需要的异常类:
#include<exception>
...
class bad_funct: public std::exception //从exception派生出用户的异常基类
{
...;
}
//exception的名称位于namespace std中。
2>exception类包含virtual成员方法.what(),该方法返回一个(通常用于说明异常类型的)const字符串:
#include<iostream>
#include<exception>
...
class bad_funct: public std::exception //从exception派生出用户的异常基类
{
public:
...;
const char* what(){return (char*)...;} //重写继承而来的what()方法
}
main()
{
try{
...;
funct();
}
catch(std::exception& e) //使用基类引用可以捕获所有派生自基类的派生类
{
std::cout << e.what() << std::endl; //调用what成员方法显示错误类型
...;
}
}
funct()
{
if(...)
throw bad_funct(...); //调用异常类对象构造函数创建并抛出异常
}
③库中继承自exception类的异常类:
头文件<stdexcept>中定义了一些常规程序可能出现的异常,都以异常类的方式被定义,并全部公有继承(public)自基异常类exception:
1>logic_error(逻辑错误):(1)domain_error //定义域错误(数学)
(2)invalid_argument //无效参数(函数)
(3)length_error //长度错误(字符串等)
(4)out_of_bounds //超界(下标索引)
2>runtiom_error(运行期错误):(1)range_error //值域错误(数学)
(2)overflow_error //上溢错误(浮点超大)
(3)underflow_error //下溢错误(浮点超小)
头文件<new>中定义了使用new分配内存错误时,可能引起的异常bad_alloc,该类同样公有继承(public)自基异常类exception。
//bad_alloc类异常无需被手动抛出,在new失败时会自动抛出bad_alloc异常,直接catch即可。
//也可以在new时使用(std::nothrow)改变new为不抛出异常:
type* p = new (std::nothrow)type; //指出new不抛出异常
此时new失败时返回空指针(NULL)。处理异常时不再使用catch而是使用if判断是否为空指针:
if(NULL == p)
四、两种未被手动处理的异常:
①未捕获异常:没有被catch捕获的异常,可能是由于没有对应的catch。
默认情况下,未捕获异常将使程序调用terminate(),terminate()默认调用abort()或exit(),程序终止:
未捕获异常→terminate()→abort()或exit()→终止。
可以通过set_terminate()函数改变terminate()函数的内容,使其调用用户定义的函数:
void uncaught();
main()
{
...;
set_terminate(uncaught);
...;
}
void uncaught()
{
std::cout << "uncaught exception" << std::endl;
exit(5);
}
其中set_terminate()原型为:
terminate_handler set_terminate(terminate_handler f)noexcept;
terminate_handler是typedef void(*terminate_handler)()而来,为无参数、无返回值函数指针的typedef。
所以用户定义的可以被terminate()调用的函数,只能是无参数、无返回值的函数。
//未捕获异常无法被抛出,因此无论使用set_terminate调用什么,最终的结果都是程序终止。
②意外异常:没有被异常规范指出的异常。
默认情况下,未捕获异常将使程序调用unexpected(),unexpected()默认调用terminate(),terminate()默认调用abort()或exit(),程序终止:
意外异常→unexpected()→terminate()→abort()或exit()→终止。
可以通过set_terminate()函数改变terminate()函数的内容,使其调用用户定义的函数(同未捕获异常)。
也可以通过set_unexpected()函数改变unexpected()函数的内容,使其调用用户定义的函数:
void uncaught();
main()
{
...;
set_unexpected(uncaught);
...;
}
void uncaught()
{
std::cout << "uncaught exception" << std::endl;
exit(5);
}
其中set_unexpected()原型为:
unexpected_handler set_unexpected(unexpected_handler f)noexcept;
unexpected_handler是typedef void(*unexpected_handler)()而来,为无参数、无返回值函数指针的typedef。
所以用户定义的可以被unexpected()调用的函数,只能是无参数、无返回值的函数。
但可以被unexpected()调用的函数可以抛出bad_exception异常,可以捕获并处理该异常:
void unexpect() throw(bad_exception);
main()
{
try{
...;
set_unexpected(unexpect);
...;
}
catch(std::bad_exception& e)
{
std::cout << e.what() << std::endl;
...;
}
}
void unexpect() thorw(bad_exception)
{
std::cout << "uncaught exception" << std::endl;
throw bad_exception();
}
unexpected()调用的函数抛出bad_exception异常时需要注意两点:
1>调用的函数规范列表中必须包含bad_exception类异常;
2>bad_exception类也是从基异常类exception类派生而来,因此也有what()成员方法。
//意外异常可以被捕获,最终的结果不一定为程序终止。