程序设计基石与实践之C++异常处理

来源:互联网 发布:java linkedlist sort 编辑:程序博客网 时间:2024/06/06 01:02

异常处理

异常处理是一种允许两个独立开发的程序组件在程序执行期间遇到程序不正常的情况时, 相互通信的机制.

抛出异常

异常Exception是程序可能检测到的, 运行时刻不正常的情况. Ex. 被0整除, 数组越界访问, 空闲存储内存耗尽等;throw表达式后面跟的不能只是一个类型,  如: class NoData {}; throw NoData; //Wrong; 异常是个对象, throw抛出的是一个class类型的对象, 如: throw NoData(); //constructor creates a object;异常一般是class类型对象, 但throw表达式也可以抛出任何类型的对象,如: 枚举类型, enum State{ Error, NoError, Normal, };//throw Error;

try块

try块(try block)必须包围能够抛出异常的语句; try块之后是一组处理代码, 称为catch子句;

当某条语句抛出异常时, 在该语句后面的语句将被跳过; 程序执行权被转交给处理异常的catch子句; 如果没有catch子句能处理该异常, 程序执行权会被转交给C++标准库中定义的函数terminate( );try块可以包含任何C++语句-表达式和声明; 一个try块引入一个局部域, 在try块内声明的变量不能在try块外别引用, 包括catch子句中; 可以声明整个包含在try块中的函数; 这种情况下, 不是吧try块放在函数定义的内部, 而是把函数体整个包含在一个函数try块(function try block)中.

int main(int argc, char** argv) try { //Do sth. return 0; } catch(ExceptionA){     //Do sth.      return errorA;} catch(ExceptionB){     //Do sth.      return errorB; }
Note: 关键字try在函数体的开始花括号之前, catch子句在函数体的结束花括号之后; 函数try块对类构造函数尤其有用; 

捕获异常  

C++异常处理代码是catch子句(catch clause); 三个部分: 1)关键字catch; 2)括号中的单个类型或单个对象声明(异常声明exception declaration); 3)复合语句中的一组语句;类型匹配, 基类的处理代码可以处理从异常声明类型派生出来的class类型的异常; 如果catch子句没有return语句, 在catch子句执行完后, 程序的执行将在catch子句列表的最后子句之后继续进行;C++的异常处理机制被称为不可恢复的nonresumptive: 一旦异常被处理, 程序的执行就不能够在异常被抛出的地方继续;

异常对象

catch子句的异常声明可以是一个类型声明或一个对象声明. catch子句的异常声明应该声明一个对象的情况: 当需要获得throw表达式的值(Ex.读取错误信息), 或者要操作throw表达式所创建的异常对象时;如: throw ExceptionA(value); catch(ExceptionA eAObj) { cout<<eAObj.getValue()<<endl };

异常对象总是在抛出点被创建, 即使throw表达式不是一个构造函数调用, 或者没有表现出要创建一个异常对象;

enum EHState {noErr, zeroOp, negativeOp}; EHState state = noErr;                                                                                                                                                                                                                                                                                                       int matchFunc(int i) {     if (i == 0)     {         state = zeroOp;         throw state;     }     else        return i; }                                                                                                                                                                                                                                                                                                       void caculate(int op) {     try{         matchFunc(op);     }         catch(EHState eObj){         eObj = noErr;        return;     } }

全局对象state没有被用作异常对象, throw表达式创建了一个类型为EHState的异常对象, 用全局state的值来初始化这个对象;进入catch子句时, 如果异常声明声明了一个对象, 则用异常对象的拷贝初始化这个对象; 如: eObj由throw表达式创建的异常对象state的拷贝进行初始化;按值传递: catch(CLASS obj); 引用传递 catch(CLASS& obj);(不创建局部拷贝), 为了防止不必要的拷贝大类型对象, class类型的参数应该被声明为引用;使用引用类型的异常声明, catch子句能修改异常对象; 但是由throw表达式指定的任何变量(原始变量)仍不受影响; 如: 上例中的全局变量state不变(zeroOp);

栈展开 

处理异常抛出的过程: 如果throw表达式位于try块中, 则检查与try块关联的catch子句; 如果没有找到合适的catch子句, 则在主函数中继续查找; 如果一个函数调用在退出时带着一个被抛出的异常, 并且这个调用位于try块中, 则检查与try块相关联的catch子句; 如果没有找到合适的catch子句, 则查找过程在主调函数中继续, 这个过程沿着嵌套函数调用链向上继续;

栈展开stack unwinding: 查找用来处理被抛出异常的catch子句, 因为异常而退出复合语句和函数定义的过程; 随着栈的展开, 局部类对象的生命期结束, 析构函数会被调用;没有找到catch子句的时候, 程序就调用C++标准库中定义的函数terminate(); terminate()的缺省行为是调用abort(), 程序非正常退出;C++异常处理要求运行时刻的支持, 编译时刻无法获得所有信息;

重新抛出

catch子句可以决定该异常由上级函数来处理, 可以通过重新抛出rethrow该异常, 把异常传递给函数调用链中更上级的catch子句; Ex. throw;被重新抛出的异常就是原来的异常, 如果catch子句在抛出前对它做了修改(引用的情况下), 修改的部分也包含在重新抛出的异常对象中;如果在throw后面有表达式, 则抛出新的异常对象;

catch-all 处理代码 

catch-all: catch(...) {//对任何异常都会进入;}

在函数随着异常退出前, 使用catch(...)来捕获异常, 释放该函数使用的资源, 通过rethrow表达式把异常传递给上级函数;catch(...)可以单独使用, 也可以和其他catch子句联合使用;catch子句的检查顺序和它们在try块后出现的顺序相同; 一旦找到一个匹配, 后续的catch子句将不再检查; 所以catch(...)总是被放在异常处理代码的最后;

异常规范

异常规范exception specification提供了一个方案, 随着函数声明列出可能抛出的异常, 保证该函数不会抛出任何其他类型的异常;异常规范跟在函数参数表后, 用关键字throw来指定, 用括号把异常类型表括起来; 异常声明是函数接口的一部分, 必须在头文件中的函数声明上指定;(声明和定义都要指定相同规范)

class Stack{public:     Stack(int capacity) : mvStack(capacity), miTop(0) {}    void DisplayVector(int index) throw (StackException, int);//...};

如果函数声明指定了一个异常规范, 同一函数的重复声明必须指定同一类型的异常规范; 函数抛出一个没有被列在异常规范中的异常的情况; 异常规范违例只能在运行时刻被检测出来(编译时期无法确定该异常是否会被抛出), 如果函数抛出一个没有被列在异常规范中的异常, 系统会调用C++标准库中定义的函数unexpected(), 缺省行为是调用terminate();
Note: 如果函数内部处理了不在异常规范列表中的异常, 系统就不会调用unexpected();空的异常规范保证函数不会抛出任何异常;  extern void no_err() throw();如果函数声明没有异常规范, 则该函数可以抛出任何异常;被抛出的异常类型与异常规范中指定的类型间不允许类型转换;

void Stack::DisplayVector(int index) throw(string){    if (index < 0)        throw "err";    cout<<getVector().at(index)<<endl;}
throw表达式创建的异常对象类型是const* char; 异常规范不允许从被抛出的异常类型到异常规范指定的类型之间的转换, 会调用unexpected(); FIX: throw string("err");

异常规范与函数指针

函数指针的声明处加上异常规范, 如:void(*pf)(int) throw(string); 和函数声明一样, 同一指针不能有不同异常规范;当带有异常规范的函数指针被初始化(赋值)时, 对于用作初始值的指针类型有限制: 用作初始值的指针异常规范必须与被初始化的指针异常规范一样或者更加严格;

void test1(int) throw(EHState, string) {};void test2(int) throw(string) {};void noError(int) throw() {};                                                                                          void (*pf1) (int) throw(string) = &test1; // Wrong, test1's ES not as strict as pf1        void (*pf2) (int) throw(string) = &test2; // Correct, test2's ES same as pf2        void (*pf3) (int) throw(string)= &noError; // Correct noError's ES stricter than pf3
[PROBLEM]:我的g++编译器没有报错, 版本不支持规范?

 异常与设计事项 

虽然异常处理是被内置语言支持的, 但不是每个C++程序都应当使用异常处理; 异常处理适合用在独立开发的不同程序部分之间, 用于不正常情况的通信;(Ex. 库函数中不能局部处理的意外情况, 抛出异常通知调用者);异常处理是容错系统实现中的主要辅助手段. 程序中的异常处理通常是分层的. 在一个层或组件中, 不是每个函数都能够处理异常. 通常, try块和catch子句被一个程序组件的入口点函数使用; catch子句处理当前组件中不适合传递给高层程序的异常;

OTHER

new操作符失败会抛出bad_alloc类型的异常, 并且不会调用构造函数;一个引用的dynamic_cast失败, 会抛出一个bad_cast类型的异常;

"std::uncaught_exception()" 事先判断当前是否存在已被抛出但尚未捕获的异常;如: if(!std:;uncaught_execption()) //没有未捕获的异常;

异常对象不是创建在函数栈上, 而是在专用的异常栈上, 因此可以传递到上层函数;异常处理机制保证, 所有从try到throw语句之间构造起来的局部对象的析构函数会被自动调用, 清退堆栈; 如果没有找到匹配的catch子句, 系统调用terminate()终止整个程序, 这种情况则不能保证所有局部对象都正确销毁;catch块参数采用传引用的好处: 提高效率, 利用对象的多态性; 派生类的catch要放在基类的catch之前, catch(void*)要放在catch(...)前;编写异常说明时, 确保派生类成员函数的异常说明和基类成员函数的一致, 派生类改写的虚函数的异常说明要和对应的基类序函数异常说明相同或者更加严格;


关于程序设计基石与实践更多讨论与交流,敬请关注本博客和新浪微博songzi_tea.