博文

第10集 C++的异常对象按传值的方式被复制和传递(2006-01-20 12:42:00)

摘要:  上一篇文章中对C++的异常对象如何被传递做了一个概要性的介绍,其中得知C++的异常对象的传递方式有指针方式、传值方式和引用方式三种。现在开始讨论最简单的一种传递的方式:按值传递。 异常对象在什么时候构造?

  1、按传值的方式传递异常对象时,被抛出的异常都是局部变量,而且是临时的局部变量。什么是临时的局部变量,这大家可能都知道,例如发生函数调用时,按值传递的参数就会被临时复制一份,这就是临时局部变量,一般临时局部变量转瞬即逝。

  主人公阿愚对这开始有点不太相信。不会吧,谁说异常对象都是临时的局部变量,应该是普通的局部变量,甚至是全局性变量,而且还可以是堆中动态分配的异常变量。是的,这上面说的好象没错,但是实际真实发生的情况是,每当在throw语句抛出一个异常时,不管你原来构造的对象是什么性质的变量,此时它都会复制一份临时局部变量,还是具体看看例程吧!如下: class MyException
{
public:
MyException (string name="none") : m_name(name)
{
cout << "构造一个MyException异常对象,名称为:"<<m_name<< endl;
} MyException (const MyException& old_e)
{
m_name = old_e.m_name; cout << "拷贝一个MyException异常对象,名称为:"<<m_name<< endl;
} operator= (const MyException& old_e)
{
m_name = old_e.m_name; cout << "赋值拷贝一个MyException异常对象,名称为:"<<m_name<< endl;
} virtual ~ MyException ()
{
cout << "销毁一个MyException异常对象,名称为:" <<m_name<< endl;
} string GetName() {r......

阅读全文(2987) | 评论:0

第9集 C++的异常对象如何被传递(2006-01-20 12:42:00)

摘要:  在相遇篇的第4集文章中,曾经讲到过在C++的异常处理模型中,是用“对象”来描述程序中出现的异常,并且在那篇文章中详细讨论了这样做所带来的诸多好处,其中之一呢就是:对象一般都很好地实现了对象的构造、对象的销毁、对象的转存复制,这为异常处理模型中异常对象的转存复制和对象销毁提供了很好的支持。是的没错,但是所谓的异常对象到底是如何被复制和传递呢?从本篇文章开始,和接下来的几篇文章中,主人公阿愚将和大家一同比较深入地探讨这个问题,并力求弄清每一个重要的细节。

概述

  呵呵!sorry,居然忘了阐述一下定义。那就是“C++的异常对象被传递”指的是什么?想当然大家也都知道,这指的就是异常出现时throw出的异常对象如何被传递到catch block块中,catch block中的异常处理模块再根据异常对象提供的异常信息做出相应的处理。程序员朋友们也许认为这很简单,其实说简单也好像不太简单,因为这种对象的传递或复制可能发生在同一个函数的不同程序块作用域间,也有可能是从当前的函数传递到上一个函数中,更有可能是把异常对象传递复制到上上(甚至更多层)的函数中。

  异常对象的传递有点类似于函数调用过程中的参数传递的过程。瞧!catch关键字的语法不就跟函数的定义有点类似吗?作为入参的异常对象也是用括号被括起来的,只不过catch只能是拥有一个参数。另外连catch(…)语法也是抄袭函数定义的方式,表示接受任意类型的数据对象。

  C++程序中函数的调用是通过“栈”来实现的,其中参数的传递也是保存到栈中,以实现两个函数间的数据共享。那么异常对象的传递呢?当然也是通过栈,其实这是很明显的一件事情,因为异常对象本身肯定是局部变量,因此它也肯定是被保存在栈中的。不过异常对象的传递毕竟还是与函数参数的传递有很大的不同,函数参数的传递是严谨的、一级一级的对象数据的压栈过程和出栈过程;但异常对象的传递却远比这要复杂些,因为它这是逆序的,属于局部变量的异常对象可能要往上层(或更上层)函数传递,它的过程是一个跳跃式的或比较混乱的过程。关于异常对象的传递具体是如何实现的,在爱的秘密篇中分析C++异常处理模型的实现时会再做详细阐述。而目前需要搞清楚的是,这个过程中所需要遵从的一些规律或标准。

  函数的参数的传递一般有指针、传值和引......

阅读全文(2960) | 评论:0

第8集 析构函数中抛出的异常(2006-01-20 12:41:00)

摘要:  前两篇文章讨论了对象在构造过程中(构造函数)和运行过程中(成员函数)出现异常时的处理情况,本文将讨论最后一种情况,当异常发生在对象的析构销毁过程中时,又会有什么不同呢?主人公阿愚在此可以非常有把握地告诉大家,这将会有大大的不同,而且处理不善还将会毫不留情地影响到软件系统的可靠性和稳定性,后果非常严重。不危言耸听了,看正文吧! 析构函数在什么时候被调用执行?

  对于C++程序员来说,这个问题比较简单,但是比较爱唠叨的阿愚还是建议应该在此再提一提,也算回顾一下C++的知识,而且这将对后面的讨论和理解由一定帮助。先看一个简单的示例吧!如下: class MyTest_Base
{
public:
virtual ~ MyTest_Base ()
{
cout << "销毁一个MyTest_Base类型的对象"<< endl;
}
};
void main()
{
try
{
// 构造一个对象,当obj对象离开这个作用域时析构将会被执行
MyTest_Base obj; }
catch(...)
{
cout << "unknow exception"<< endl;
}
}   编译运行上面的程序,从程序的运行结果将会表明对象的析构函数被执行了,但什么时候被执行的呢?按C++标准中规定,对象应该在离开它的作用域时被调用运行。实际上各个厂商的C++编译器也都满足这个要求,拿VC来做个测试验证吧!,下面列出的是刚刚上面的那个小示例程序在调试时拷贝出的相关程序片段。注意其中obj对象将会在离开try block时被编译器插入一段代码,隐式地来调用对象的析构函数。如下:
325: try
326: {
00401311 mov dword ptr [ebp-4],0
327: // 构造一个对象,当obj对象离开这个作用域时析构将会被执行
328: MyTest_Base obj;
00401318 lea ecx,[obj]
0040131B call @ILT+40(MyTest_Base::MyTest_Base) (0040102d)

阅读全文(2363) | 评论:0

第7集 构造函数中抛出的异常(2006-01-20 12:40:00)

摘要:  上一篇文章简单讨论了一下对象的成员函数抛出异常时的处理情况。本文中将继续讨论当在构造函数中抛出异常时,程序的执行情况又如何?这有点复杂呀!而且主人公阿愚还觉得这蛮有点意思! 构造函数中抛出的异常

  1、标准C++中定义构造函数是一个对象构建自己,分配所需资源的地方,一旦构造函数执行完毕,则表明这个对象已经诞生了,有自己的行为和内部的运行状态,之后还有对象的消亡过程(析构函数的执行)。可谁能保证对象的构造过程一定能成功呢?说不定系统当前的某个资源不够,导致对象不能完全构建好自己(人都有畸形儿,更何况别的呢?朋友们!是吧!),因此通过什么方法来表明对象的构造失败了呢?C++程序员朋友们知道,C++中的构造函数是没有返回值的,所以不少关于C++编程方面的书上得出结论:“因为构造函数没有返回值,所以通知对象的构造失败的唯一方法那就是在构造函数中抛出异常”。主人公阿愚非常不同意这种说法,谁说的,便不信邪!虽然C++标准规定构造函数是没有返回值,可我们知道每个函数实际上都会有一个返回值的,这个值被保存在eax寄存器中,因此实际上是有办法通过编程来实现构造函数返回一个值给上层的对象创建者。当然即便是构造函数真的不能有返回值,我们也可以通过一个指针类型或引用类型的出参来获知对象的构造过程的状态。示例如下: class MyTest_Base
{
public:
MyTest_Base (int& status)
{
//do other job // 由于资源不够,对象构建失败
// 把status置0,通知对象的构建者
status = 0;
} protected:
}; void main()
{
int status;
MyTest_Base obj1(status); // 检查对象的构建是否成功
if(status ==0) cout << “对象构建失败” << endl;
}   程序运行的结果是:
  对象构建失败
  是啊!上面我们不也得到了对象构造的成功与否的信息了吗?可大家有没有觉得这当中有点问题?主人公阿愚建议大家在此停留片刻,仔细想想它会有什么问题?OK!也许大家都知道了问题的所在,来验证一下吧......

阅读全文(2728) | 评论:0

第6集 对象的成员函数中抛出的异常(2006-01-20 12:40:00)

摘要:  C++异常处理模型除了支持面向过程的C风格程序中的异常处理外(就是没有面向对象的概念,完全是C程序,整个程序实际就是函数的集合,但却用C++编译器来编译这样的C程序,所以这样的程序中是可以a使用C++的异常处理机制的,要不怎么说C++是兼容C语言的呢?但是需要注意的是,单纯的C语言程序中是不能使用C++异常处理模型进行编程的。是不是有点说拗口了?有点糊涂了呢?其实很简单,那就是如果程序中使用了C++异常处理机制,也即代码中有try、catch和throw关键字,那么就必须使用C++编译器来编译这个程序。许多程序员朋友们在这里有一个理解上的误区,认为只有程序中使用了面向对象的概念,即使用class关键字来定义一个类结构才算得上C++程序,其实这种理解是片面的,如果程序中采用了C++异常处理机制,那么也有理由认为这是一个C++程序,哪怕程序的代码完全是C语言风格的,并且这样的程序用C编译器来编译肯定将会报错,提示未定义的try标示符等等错误信息),还支持面向对象程序中对象抛出的异常处理。

  C++异常处理模型的确和面向对象是紧密结合的,除了在相遇篇中介绍到的用对象来描述程序中出现的异常之外,C++异常处理模型也对在面向对象程序中的对象实例所抛出的异常作了最完善的支持和处理。也许大家会觉得这很容易,没什么了不起的地方。但恰恰相反,实际上这才是C++异常处理模型最成功、最不可思议和最闪光的地方。而且由于C++异常处理模型对面向对象有了很好的支持和兼容,才使得C++异常处理模型本身的实现变得特别复杂,因为它需要跟踪每一个对象的运行情况和状态(关于C++异常处理模型的实现,会在爱的秘密篇中有详细剖析)。本文和接下来的几篇文章将讲述当对象实例抛出异常时将如何处理。

  对象的生命周期一般有三种状态:构造、运行和析构销毁。因此对象抛出的异常也有这三种区别。是在对象构造时抛出的呢?还是对象运行时抛出的呢?或是析构对象时抛出的?这三种不同时候抛出的异常会将会产生不同的结果。本文首先讨论最常见的一种情况,在对象运行时抛出的异常,也即执行对象的成员函数时出现的异常。 对象的成员函数抛出的异常

  1、老方法,看例子先,如下: class MyTest_Base
{
public:
MyTest_Base (string ......

阅读全文(3372) | 评论:2

第5集 C++的异常rethrow (2006-01-19 16:57:00)

摘要:  上一篇文章已经提到了C++的异常rethrow现象,这篇文章将进一步深入讨论这个问题。当catch到一个异常后进入异常处理程序块中,此时需要从传入的异常对象中得到一些关于异常的详细信息,并判断这个异常是否能得以恢复,是否能在当前的异常处理程序块中得以处理。如果是,那么及时地处理掉这个异常,使程序恢复到正常的工作轨道上(也即从catch block后面的代码处继续执行);否则就必须重新抛出异常(Excption Rethrow),把这个异常交给上一层函数的异常处理模块去处理(反正我是处理不了了,而且我也通知了我的上层领导,所以责任吗,也就不由我担当了,哈哈 ^-^)。 语法

  很简单,有两种用法,如下:
  1、 throw ;
  2、 throw exception_obj ;
  第一种表示原来的异常对象再次被重新抛出;第二中呢,则表示原来的异常已处理或正在处理中,但此时又引发了另一个异常。示例如下: void main()
{
try
{
try
{
throw 4;
}
catch(int value)
{
// 第一种用法,原来的异常被再次抛出
// 注意它不需要带参数。
throw;
} try
{
throw 0.5;
}
catch(double value)
{
// 第二种用法,再次抛出另外的一个异常
// 它的语法和其它正常抛出异常的语法一样。
throw “another exception”;
}
}
catch(...)
{
cout << “unknow exception”<< endl;
}
}

在什么地方异常可以rethrow?

  当然,异常的rethrow只能在catch block中,或者说在catch block中抛出的异常才是异常的rethrow,因此注意下面的示例程序中存在语法错误,如下:

void main()
{
try
{
try
{
throw 4;
}
catch(int value)

阅读全文(3285) | 评论:0

第4集 C++的异常处理和面向对象的紧密关系(2006-01-19 16:56:00)

摘要:  如果有人问起C++和C到底有那些本质上的不同点?主人公阿愚当然也会有自己的一份理解,他会毫不犹豫回答出:“与C相比,C++至少引入了两项重要技术,其一就是对面向对象的全面支持;还有一项就是C++优良的异常处理模型”。是的,这两项技术对构建出一个优良的可靠复杂的软件系统都太重要了。可这两项技术之间又有何关系呢?非常客观公正的说,它们之间的关系实在是太紧密了,两者相互支持和依赖,是构建优良可靠复杂的软件系统最不可缺乏的两个东东。 用对象来描述程序中出现的异常

  虽然前几篇文章的内容中列举的一些小例子程序大多都是throw一些如int、double类型的异常,但程序员朋友都很熟悉,实际开发环境中所抛出的异常都是一个个代表抽象数据类型的对象,如C++标准库中的std::exception(),MFC开发库中Cexception等。用对象来描述的我们程序中的出现异常的类型和异常信息是C++异常处理模型中最闪光之处,而且这一特点一直沿用到java语言的异常处理模型中。

  为什么要用对象来描述程序中出现的异常呢?这样做的优势何在?主人公阿愚不喜欢穷摆出什么大道理,还是老办法,从具体的实例入手。由于异常有许许多多种类型,如有致命的错误、一般的错误、警告或其它事件通知等,而且不同类型的异常有不同的处理方法,有的异常是不可恢复的,而有的异常是可以恢复的(专业术语叫做“重入”吧!哈哈,主人公阿愚有时也会来点文绉绉的东西),所以程序员在开发系统时就必须考虑把各种可能出现的异常进行分类,以便能够分别处理。下面为一个应用系统设计出一个对异常进行分类的简单例子,如下:


  从上面的异常分类来看,它有明显的层次性和继承性,这恰恰和面向对象的继承思想如出一辙,因此用对象来描述程序中出现的异常是再恰当不过的了。而且可以利用面向对象的特性很好的对异常进行分类处理,例如有这样一个例子:

void OpenFile(string f)
{
try
{
// 打开文件的操作,可能抛出FileOpenException
}
catch(FileOpenException& fe)
{
// 处理这个异常,如果这个异常可以很好的得以恢复,那么处理完毕后函数
// 正常返回;否则......

阅读全文(2922) | 评论:0

第3集 C++中catch(…)如何使用(2006-01-19 16:56:00)

摘要:  上一篇文章中详细讲了讲C++异常处理模型的trycatch使用语法,其中catch关键字是用来定义catch block的,它后面带一个参数,用来与异常对象的数据类型进行匹配。注意catch关键字只能定义一个参数,因此每个catch block只能是一种数据类型的异常对象的错误处理模块。如果要想使一个catch block能抓获多种数据类型的异常对象的话,怎么办?C++标准中定义了一种特殊的catch用法,那就是” catch(…)”。 感性认识

  1、catch(…)到底是一个什么样的东东,先来个感性认识吧!看例子先: int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是int,值为1)
throw 1;
}
//catch( int& value )
//注意这里catch语句
catch( …)
{
cout << "在 catch(…) block 中, 抛出的int类型的异常对象被处理" << endl;
}
}   2、哈哈!int类型的异常被catch(…)抓获了,再来另一个例子: int main()
{
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl;
//这里抛出一个异常(其中异常对象的数据类型是double,值为0.5)
throw 0.5;
}
//catch( double& value )
//注意这里catch语句
catch( …)
{
cout << "在 catch(…) block 中, double类型的异常对象也被处理" << endl;
}
}   3、同样,double类型的异常对象也被catch(…)块抓获了。是的,catch(..)能匹配成功所有的数据类型的异常对象,包括C++语言提供所有的原生数据类型的异常对象,如int、double,还有c......

阅读全文(2783) | 评论:0

第2集 C++中异常处理的游戏规则(2006-01-19 16:55:00)

摘要:  如果您喜欢玩一款游戏,您必须先要很好理解这款游戏的规则。同样主人公阿愚喜欢上C++中异常处理后,当然也首先关注它的游戏规则,这就是C++中异常处理的语法。 关键字

  1、 try
  2、 catch
  3、 throw
  其中关键字try表示定义一个受到监控、受到保护的程序代码块;关键字catch与try遥相呼应,定义当try block(受监控的程序块)出现异常时,错误处理的程序模块,并且每个catch block都带一个参数(类似于函数定义时的数那样),这个参数的数据类型用于异常对象的数据类型进行匹配;而throw则是检测到一个异常错误发生后向外抛出一个异常事件,通知对应的catch程序块执行对应的错误处理。
语法

  1、还是给一个例子吧!如下:

int main()
{
cout << "In main." << endl; //定义一个try block,它是用一对花括号{}所括起来的块作用域的代码块
try
{
cout << "在 try block 中, 准备抛出一个异常." << endl; //这里抛出一个异常(其中异常对象的数据类型是int,值为1)
//由于在try block中的代码是受到监控保护的,所以抛出异常后,程序的
//控制流便转到随后的catch block中
throw 1; cout << "在 try block 中, 由于前面抛出了一个异常,因此这里的代码是不会得以执行到的" << endl;
}
//这里必须相对应地,至少定义一个catch block,同样它也是用花括号括起来的
catch( int& value )
{
cout << "在 catch block 中, 处理异常错误。异常对象value的值为:"<< value << endl;
} cout << "Back in main. Execution resumes here." << endl;
return 0; }   2、......

阅读全文(2689) | 评论:0

第1集 初次与异常处理编程相邂逅(2006-01-19 16:52:00)

摘要:  和其它很多程序员一样,本书的主人公阿愚也是在初学C++时,在C++的sample代码中与异常处理的编程方法初次邂逅的,如下: // Normal program statements
... try
{
// Execute some code that might throw an exception.
}
catch( CException* e )
{
// Handle the exception here.
// "e" contains information about the exception.
e->Delete();
} // Other normal program statements   瞧瞧,代码看上去显得那么整齐、干净,try block和catch block遥相呼应,多有对称美呀!因此主人公初次见面后就一见钟情了。   为什么要选用异常处理的编程方法?
  当然更为重要的是,C++中引入的异常处理的编程机制提供给程序员一种全新的、更好的编程方法和思想。在C++中明确提出trycatch异常处理编程方法的框架之前的年代,程序员是怎样编写程序的,如下: void main(int argc, char* argv[])
{
if (Call_Func1(in, param out)
{
// 函数调用成功,我们正常的处理
if (Call_Func2(in, param out)
{
// 函数调用成功,我们正常的处理
while(condition)
{
//do other job
if (has error)
{
// 函数调用失败,表明程序执行过程中出现一些错误,
// 因此必须处理错误
process_error();
exit();
}
//do other job
}
}
else
{
// 函数调用失败,表明程序执行过程中出现一些错误,
// 因此必须处理错误
process_error();
exit();
}

}
else
{
//......

阅读全文(2901) | 评论:0