C++异常处理
异常处理(exception handling)是C++的一项语言机制,用于在程序能处理异常事件。 异常事件在C++中表示为异常对象(exception object)。异常事件发生时,由操作系统为程序设置当前异常对象,然后执行程序的当前异常处理代码块,在包含了异常出现点的最内层的 throw
执行 因此,
由于 抛出一个表达式时,被抛出对象的静态编译时类型将决定异常对象的类型。 catch
在catch块中可以使用不带表达式的throw语句将捕获的异常重新抛出: throw ;
被重新抛出的异常对象就是当前catch语句捕获时所匹配的,原本由 可以用catch(...){ }来捕获所有的异常。通常在catch(...){ }中,先执行可做的处理,然后重新抛出异常。 catch语句内部产生的新异常,或者“重新抛出异常”,均不能被同级的 栈展开栈展开(unwinding)是指当前的 catch语句如果匹配异常对象成功,在完成了对catch语句的参数的初始化(对传值参数完成了参数对象的copy构造)之后,对同层级的try块执行栈展开。 由于线程执行时,被调用的函数的参数、返回地址、局部变量等都是依函数调用次序保存在函数调用栈(即线程运行时栈)上。当前被调用函数的参数、局部变量名字可以覆盖掉早前调用函数的同名变量,看起来就是只有当前函数内的名字可以访问,早前调用的函数内部的名字都不可访问,就像磁带被“卷起”。异常处理时按照函数调用顺序的逆序析构,依次析构各个被调函数的局部变量,就类似把已经卷起的“磁带”再展开,抹去上面记录的数据,故此“栈展开”得名。unwinding在物理学、电工学上也翻译做“退绕”、“退卷”。 C++标准程序库中定义的异常类标准异常类定义在C++标准程序库的四个头文件中:
所有的异常类都是exception类的子类。 runtime_error类(表示运行时才能检测到的异常)包含了overflow_error、underflow_error、range_error几个子类; logic_error类(一般的逻辑异常)包含了domain_error、invalid_argument、out_of_range、length_error几个子类; 各种标准异常类都定义了一个接受字符串的构造函数,字符串初始化式用于为所发生的异常提供更多的信息。所有异常类都有一个what()虚函数,它返回一个指向C风格字符串的指针。 应用程序可以从各种标准异常类派生自己的异常类。 函数的异常规格异常规格(exception specification)列出函数可能会抛出的所有异常的类型。异常规格写在函数的形参表之后的关键字throw之后跟着一对圆括号括住的异常类型列表。如: void foo(int) throw(bad_alloc, invalid_argument)
{
/*函数体*/
}
异常列表还可以为空: void foo(int) throw();
表示该函数不抛出任何异常。 如果函数内抛出的异常的类型不在该函数的异常规格中,则系统函数unexpected()被调用。如果在unexpected()中抛出的异常出现在该函数的异常规格中,则在该函数被调用处恢复对异常的catch处理。如果在unexpected()中抛出的异常不在该函数的异常规格中,则调用系统函数terminate()以终止程序。 标准异常类中的构造函数、析构函数和what()虚函数都承诺不抛出异常。如what的完整声明为:virtual const char* what() const throw(); 派生类中的虚函数不能抛出基类虚函数中没有声明的新异常。 使用函数的异常规格的好处:
Microsoft Visual C++接受但暂不支持C++标准中的函数的异常规格。即使使用了编译器选项/D1ESrt,函数抛出不在其异常规格中的其他类型异常时,不会自动调用unexpected(),而是在该函数调用点处的 noexcept关键字事实上,异常规格这一特性在程序中很少被使用,因此在C++11中被弃用[2]。C++11定义了新的noexcept关键字。如果在函数声明后直接加上noexcept关键字,表示函数不会抛出异常。另外一种形式是noexcept关键字后跟常量表达式,其值转为布尔值,如果为真表示函数不会抛出异常,反之,则有可能抛出异常。 returnType funcDeclaration (args) noexcept(常量表达式) ; 如果保证不抛出异常的函数却实际上抛出异常,则会直接调用std::terminate中断程序的执行。 noexcept关键字还可以用作运算符,其后的操作数表达式如果有可能抛出异常,则运算符返回为false;如果操作数表达式保证不抛出异常,则运算符返回为true。这一运算符用于在定义模板函数时可以根据模板参数类型来确定是否传出异常。 对类析构函数,使用noexcept关键字也可以显式指明不剖出异常。类析构函数默认不抛出异常。如果声明为(或默认)不抛出异常的类析构函数在运行时抛出了异常,将导致调用std::terminate中断程序的执行。 构造函数、析构函数与异常构造函数没有返回值,所以应该用异常来报告发生的问题。构造函数抛出异常就意味着该构造函数没有执行完,所以其对应的析构函数不会被自动调用,因此构造函数应该先析构所有已初始化的基对象、成员对象,再抛出异常。 析构函数被期望不向函数外抛出异常。析构函数中向函数外抛出异常,将直接调用terminator()系统函数终止程序。如果一个析构函数内部抛出了异常,就应该在该析构函数内部捕获、处理了该异常,不能让异常被抛出析构函数之外。 构造函数初始化列表的异常机制C++类构造函数初始化列表的异常机制,称为function-try block。一般形式为: myClass::myClass(type1 pa1)
try: _myClass_val (初始化值)
{
/*构造函数的函数体 */
}
catch ( exception& err )
{
/* 构造函数的异常处理部分 */
};
资源获取即初始化资源获取即初始化(Resource acquisition is initialization,RAII)是指:为了更为方便、鲁棒地释放已获取的资源,避免资源死锁,一个办法是把资源数据用对象封装起来。程序发生异常,执行栈展开时,封装了资源的对象会被自动调用其析构函数以释放资源。 例子#include <iostream>
#include <exception>
using namespace std;
class AA{
private:
public:
int s;
AA(class AA& rhs)
{ cout<<"copy ctor;"<<endl; }
AA(int ihs)
try: s ( ihs )
{ cout<<"defautl ctor;"<<endl; }
catch(...)
{ }
~AA()
{ cout<<"dtor:"<<s<<endl; }
void foo() throw ( exception )
{
throw 3.14; //浮点型异常对象不在该函数的异常规格中
}
};
int main()
{
AA a(101);
try
{
AA temp(102);
temp.foo(); //对于Visual C++,即使用编译器选项/D1ESrt
//仍然会无视AA::foo函数的异常规格
throw a;
}
catch (AA& e) //引用型参数,可改为传值型参数
{
e.s=102; //修改throw抛出的异常对象
cout<<"catch AA exception!"<<endl;
}
catch(...)
{
cout<<"catch all others exception!"<<endl;
}
}
C++异常与Windows操作系统异常处理机制在Windows操作系统上,编译器实现的C++异常一般是基于操作系统的异常处理机制。 参见参考文献
|
Portal di Ensiklopedia Dunia