AutoCAD 3DMAX C语言 Pro/E UG JAVA编程 PHP编程 Maya动画 Matlab应用 Android
Photoshop Word Excel flash VB编程 VC编程 Coreldraw SolidWorks A Designer Unity3D
 首页 > C++

C++和Object Pascal对象模型

51自学网 http://www.51zixue.net

9.1.3 在基类构造函数调用虚拟方法
虚拟方法在VCL基类构造函数的函数体内被调用,也就是说,类的实现以ObjectPascal模式,分派则以C++模式,取决于对象的运行时类型。因为C++Builder综合了ObjectPascal模式的立即设置运行时对象类型,以及C++模式的在派生类构造前构造基类,从VCL风格类的基类构造函数调用虚拟方法会有一些副作用。这些影响在下面描述,并且以至少有一个父类的实例化类为例说明。这里,这个实例化类被作为一个派生类。

1.ObjectPascal模型
在ObjectPascal中,程序员可使用inherited关键字,它提供了一种灵活的方式在一个派生类的构造函数体内任意位置调用基类构造函数。因而,若派生类根据建立的对象重载任何虚拟方法或初始化数据成员,可在基类构造函数和虚拟方法被调用前发生。
2.C++模型
C++语法没有inherited关键字可在派生类的构造函数体内任意位置调用基类构造函数。对于C++模型,使用inherited关键字是不必要的,因为对象的运行时类型是当前被构造类的类型,而不是派生类。因此,虚拟方法的调用是当前的类而不是派生类。因而,在这些方法被调用以前初始化数据成员或建立派生类的对象也是不必要的。

3.C++Builder模型
在C++Builder中,VCL风格对象的运行时类型,为派生类的类型,并在调用基类构造函数期间不变。因此,如果基类构造函数调用一个虚拟的方法,当派生类重载它时,派生类的方法被调用。如果这个虚拟方法依赖于派生类构造函数体或初始化列表中的任何东西,方法在这发生以前被调用。例如CreateParams是一个虚拟的成员函数,它在TWinControl的构造函数中间接地被调用。如果从TWinControl派生一个类并重载CreateParams,以便它依赖于构造函数中的任何东西,在CreateParams被调用以后,这些代码才被处理。这种状况适用于一个基类的任何派生类。考虑一个从B派生的类C,B从A派生。创建C的一个实例,若B重载方法但C没有,A也将调用B重载的方法。

注意 要记住像CreateParams一样的虚拟方法不是被构造函数显式调用的,而是间接被调用。

4.例子:调用虚拟方法
下例比较重载了虚拟方法的C++和VCL风格类。这个例子说明来自基类构造函数的那些虚拟的方法的调用怎么以两种情况被解决。MyBase和MyDerived是标准的C++类。MyVCLBase和MyVCLDerived是从Tobject派生而来的VCL风格类。虚拟方法what_am_I()在派生类中被重载,但
仅在基类构造函数中被调用,派生类构造函数中不调用。


这个例子的输出是:

Iamabase
Iamaderived
这是因为在调用它们各自的基类构造函数期间运行时类型MyDerived和MyVCLDerived的差别。

5.虚拟函数数据成员的构造函数初始化
因为数据成员可以在虚拟的函数被使用,理解它们如何以及何时被初始化是很重要的。在Object Pascal中,所有未初始化的数据被零初始化。这适用于,例如其构造函数没有调用inherited的基类。在标准的C++中,未初始化的数据成员的值不确定。下列类型的类数据成员必须在类的构造函数的初始化列表中初始化:
· 引用。
· 没有缺省构造函数的数据成员。

但是,这些数据成员的值,或那些在构造函数体中被初始化了的数据成员的值,当基类构造函数被调用时,是未定义的。在C++Builder中,VCL风格类的内存是零初始化的。注意技术上,VCL类的内存为零,是按位为零,其值实际上是未定义的。例如,一个引用为零。
一个虚拟函数,若依赖于在构造函数体或在初始化列表中初始化的成员变量,可能会表现为好像变量被初始化到零。这是因为基类构造函数在初始化列表被处理或进入构造函数体前被调用。下例说明这种情况:



这个例子在Base的构造函数中引发一个异常。因为Base在Derived前被构造,not_zero还没被初始化为传递到构造函数的值42。要记住不能在其基类的构造函数被调用前初始化VCL风格类的数据成员。

9.1.4 对象析构
有两种对象析构的机制在ObjectPascal和C++中是不同的。它们是:
· 构造函数中引发异常,析构函数被调用。
· 从析构函数调用虚拟方法。
VCL风格类综合了这两种语言的方法。下面讨论这个问题。
1.从构造函数中发送异常
异常在对象构造期间被发送后调用析构函数的方式在C++和ObjectPascal中是不同的。例如,类C从类B派生,类B从类A派生:



考虑当构造C的一个实例时,异常在类B的构造函数中被引发,在C++、ObjectPascal和VCL风格类中分别会有什么结果,描述如下:

· 在C++中,首先,B的所有已被完全构造了的对象数据成员的析构函数被调用,然后A的析构函数被调用,然后A的所有已被完全构造了的对象数据成员的析构函数被调用。但是,B和C的析构函数不被调用。
· 在ObjectPascal中,仅有实例化的类的析构函数自动被调用。这里是C的析构函数。与构造函数一样,程序员的全部责任就是在析构函数中调用inherited。在这个例子中,如果假定所有的析构函数都调用inherited,那么会按C、B、A的顺序调用它们的析构函数。而且无论inherited在异常发生前是否已经在B的构造函数中被调用,A的析构函数都被调用,因为inherited在B的析构函数中被调用。调用A的析构函数独立于它的构造函数是否被实际调用。更重要的,因为通常inherited被立刻调用,所以无论C的构造函数体是否完全被执行,它的析构函数都被调用。

· 对于VCL风格类,真正的VCL基类(用ObjectPascal实现)遵循ObjectPascal调用析构函数的方法。C++VCL风格类(用C++实现)不严格遵循哪一种语言。在这里,所有的析构函数都被调用;但是那些不是已经完成调用的函数体,根据C++语言的规定,不会被进入。从而为以ObjectPascal实现的类提供了一个处理在析构函数体编写的任何清除代码的机会。包括为那些在构造函数异常发生以前被构造的子对象(本身为对象的数据成员)释放内存的代码。需记住,对VCL风格类,清除代码不能被实例化的类或那些C++实现的类处理,甚至析构函数被调用。在C++Builder中处理异常的更多信息,参考8.3节。

2.析构函数调用虚拟方法
析构函数分派虚拟方法与在构造函数中模式相同。这意味着对于VCL风格类,派生类首先被销毁,但是在随后所有的基类析构函数的调用中运行时对象类型仍保持为派生类的类型。因此,若虚拟方法在VCL基类析构函数中被调用,可能会分派到已经被销毁的一个类。

9.1.5 AfterConstruction和BeforeDestruction
TObject提供了两个虚拟方法,BeforeDestruction和AfterConstruction,分别允许程序员编写在对象创建以前和销毁以后被处理的代码。AfterConstruction在最后一个构造函数被调用以后调用。BeforeDestruction在第一个析构函数被调用以前调用。这些方法是公共的,并且自动被调用。

9.1.6 类虚拟函数
在ObjectPascal中有类虚拟函数的概念。如果可能,在C++中与之相似的可能是静态虚拟函数,但是C++中没有与这种类型的函数严格对应的函数。这种函数在VCL内部可安全地被调用,不过,在C++Builder中不能调用这种类型的函数。在头文件中可以通过下面的注释确认这种函数。
/* virtual class method */

 
 

上一篇:C++ Builder 运行时包  下一篇:Win32下的结构异常