C++异常处理

throw

throw是一个C++关键字，与其后的操作数构成了throw语句，语法上类似于return语句。throw语句必须被包含在try块之中；可以是被包含在调用栈的外层函数的try中。
执行throw语句时，其操作数的结果作为对象被复制构造为一个新的对象，放在内存的特殊位置（既不是堆也不是栈，Windows上是放在“线程信息块TIB”中）。这个新的对象由本级的try所对应的catch语句逐个做类型匹配；如果匹配不成功，则与本函数的外层catch语句依次做类型匹配；如果在本函数内不能与catch语句匹配成功，则递归回退到调用栈的上一层函数内从函数调用点开始继续与catch语句匹配。重复这一过程直到与某个catch语句匹配成功或者直到主函数main()都不能处理该异常。
因此，throw语句抛出的异常对象不同于一般的局部对象。一般的局部对象会在其作用域结束时被析构。而throw语句抛出的异常对象驻留在所有可能被激活的catch语句都能访问到的内存空间中。
throw语句抛出的异常对象在匹配成功的catch语句的结束处被析构（即使该catch语句使用的是非“引用”的传值参数类型）。
由于throw语句都进行了一次副本拷贝，因此异常对象应该是可以copy构造的。但对于Microsoft Visual C++编译器，异常对象的复制构造函数即使私有的情形，异常对象仍然可以被throw语句正常抛出；但在catch语句的参数是传值时，在catch语句处编译报错：“ cannot be caught as the destructor and/or copy constructor are inaccessible”。
抛出一个表达式时，被抛出对象的静态编译时类型将决定异常对象的类型。

catch

catch语句匹配被抛出的异常对象时，如果catch语句的参数是引用型，则该参数直接引用到throw语句抛出的异常对象上；如果catch参数是传值的，则拷贝构造一个新的对象作为catch语句的参数的值。在该catch语句结束时，先析构catch的传值的参数对象，然后析构throw语句抛出的异常对象。
catch语句匹配异常对象时，不会做任何隐式类型转换（implicit type conversion），包括类型提升（promotion）。 异常对象与catch语句进行匹配的规则很严格，一般除了以下几种情况外，异常对象的类型必须与catch语句的声明类型完全匹配：允许非const到const的转换；允许派生类到基类的转换；将数组和函数类型转换为对应的指针。
在catch块中可以使用不带表达式的throw语句将捕获的异常重新抛出：
throw ;
被重新抛出的异常对象就是当前catch语句捕获时所匹配的，原本由throw语句抛出的那个异常对象。重新抛出的异常对象与catch语句的形参无关。如原来抛出的是派生类Derived，catch语句形参是基类Based，则重新抛出后的异常类型是Derived。如果catch语句形参是引用型，重新抛出的原来的异常对象的内容可能已在catch语句内部被修改了。
可以用catch(...){ }来捕获所有的异常。通常在catch(...){ }中，先执行可做的处理，然后重新抛出异常。
catch语句内部产生的新异常，或者“重新抛出异常”，均不能被同级的try...catch...中其他的catch语句捕获、处理。只能由更外层的catch语句去捕获该异常。

栈展开

栈展开（unwinding）是指当前的try...catch...块匹配成功或者匹配不成功异常对象后，从try块内异常对象的抛出位置，到try块的开始处的所有已经执行了各自构造函数的局部变量，按照构造生成顺序的逆序，依次被析构。如果当前函数内对抛出的异常对象匹配不成功，则从最外层的try语句到当前函数体的起始位置处的局部变量也依次被逆序析构，实现栈展开，然后再回退到调用栈的上一层函数内从函数调用点开始继续处理该异常。
catch语句如果匹配异常对象成功，在完成了对catch语句的参数的初始化（对传值参数完成了参数对象的copy构造）之后，对同层级的try块执行栈展开。
由于线程执行时，被调用的函数的参数、返回地址、局部变量等都是依函数调用次序保存在函数调用栈（即线程运行时栈）上。当前被调用函数的参数、局部变量名字可以覆盖掉早前调用函数的同名变量，看起来就是只有当前函数内的名字可以访问，早前调用的函数内部的名字都不可访问，就像磁带被“卷起”。异常处理时按照函数调用顺序的逆序析构，依次析构各个被调函数的局部变量，就类似把已经卷起的“磁带”再展开，抹去上面记录的数据，故此“栈展开”得名。unwinding在物理学、电工学上也翻译做“退绕”、“退卷”。

noexcept关键字

事实上，异常规格这一特性在程序中很少被使用，因此在C++11中被弃用。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++异常一般是基于操作系统的异常处理机制。