您的位置:首页 > 编程语言 > C语言/C++

C++异常机制

2015-09-10 17:44 288 查看
看C++Primer的异常部分时,有这么两个现象:

1. throw时抛出的对象即使是全局变量,在catch中对捕获到的异常变量的修改也不能修改到原始的全局变量(无论catch中异常变量的声明是否是引用)

enum EHstate { noErr, zeroOp, negativeOp, severeError };
enum EHstate state = noErr;
int mathFunc( int i ) {
if ( i == 0 ) {
state = zeroOp;
throw state; // 创建异常对象
}
// 否则, 正常处理流程继续
}

void calculate( int op ) {
  try {
    mathFunc( op );
  }
  catch ( EHstate &eObj ) {
    // 修正异常情况
    eObj = noErr; // 全局变量 state 没有被修改
  }
}

2. 非引用声明方式下, catch中修改了异常变量再次抛出时(throw后不再有新变量),被抛出的异常并没有因此修改

enum EHstate { noErr, zeroOp, negativeOp, severeError };
void calculate( int op ) {
try {
// 被 mathFunc() 抛出的异常的值为 zeroOp
mathFunc( op );
}
catch ( EHstate eObj ) {
// 做某些修正
// 试图修改异常对象
eObj = severeErr;
// 希望重新抛出值为 severeErr 的异常对象
throw;
}
}

感觉很难理解上面两个现象,就到网上找了下C++中异常的实现机制,下面这个网页介绍的非常好:
http://baiy.cn/doc/cpp/inside_exception.htm
大体的机制总结如下:

1. 为了处理异常和异常链(我自己这么叫的,因为异常有可能会被层层往上抛,出现了一条链),C++编译器在函数调用栈中埋入了异常链的信息

2. 为了在异常发生时能正确的进行栈展开(原先函数调用完成时栈的回退等处理由于异常的出现,并不遵循函数原来的弹栈流程了,但栈中的信息还得需要正确的弹出),C++编译器在代码中埋入了相应的处理(主要是一些位置信息等)。

3. 异常被抛出后,异常处理器(throw语句被编译器展开成调用异常处理器以及异常变量的创建等操作)顺着调用栈中的链来寻找异常的处理块,找到后对异常进行处理。

有了上面的机制,再来说说最开始的疑问:

1. 全局对象不能被修改是因为,异常对象是新创建出来,作为原来全局对象的一个拷贝,因此,即使改变了异常对象,全局对象也不会改变,而且“异常对象总是在抛出点被创建即使throw 表达式不是一个构造函数调用或者它没有表现出要创建一个异常对象情况也是如此”--摘自C++Primer。

2. 如果不是按引用声明catch中的异常对象,那么,新的异常对象也是原始异常对象的一个拷贝,而throw后面不跟对象时,抛出的还是原来的异常对象,因此,改变catch块中的异常对象对原始异常对象无影响(而如果是按引用,它两个就指向了同一个地方)。

疑问解决了,这个网页中(http://emuch.net/fanwen/446/61002.html)还回答了“异常对象究竟身在何处”的疑问---在栈中。而且,还Debug调试来跟踪栈的信息来说明“为什么栈都展开了为甚么该异常变量依然能用”的问题,感叹编译器的聪明。

其实,这里编译器也没有什么聪明不聪明的。结合上面的异常机制,异常处理器在寻找catch处理块的同时,会将栈中各个函数帧中的局部变量进行析构处理,虽然如此它依然会使用这个栈,还在上面实例化了一个异常变量来保存异常信息,只是最后在它异常处理完之后,它使用的信息也能正确的从栈中处理掉罢了,毕竟异常处理器也是一个函数调用,只是它的作用中还包含了清除掉它前辈的功能。这就好像如果你的任务是清理掉别人在雪上留下的脚印,而这个过程中你自己肯定也会留下脚印,只要你最后将自己的脚印也一并清除掉就行了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++ 异常