C++和Object Pascal对象模型
时间:2010-09-17 来源:chulia
http://www.hackbase.com/lib/2007-03-27/14109.html
第9章 VCL的C++语言支持
C++Builder的快速应用程序开发(RAD)能力是建立在用ObjectPascal编写的可视组件库(VCL)基础之上的。本章说明ObjectPascal语言的特征、结构及概念如何在C++Builder中实现以支持VCL。
本章的第一部分比较C++和ObjectPascal的对象模型,并说明C++Builder如何组合这两种方法。本章的第二部分说明ObjectPascal语言的结构如何对应转换成C++Builder中的C++结构。包括一些被增加来支持VCL的关键字的扩展的相关细节。这些扩展,如闭合和属性,是独立的支持基于VCL的代码的有用特征。
注意 从TObject派生的C++类是指那些TObject是其最初的、然而并非直接的祖先的类。为保持编译器逻辑上的一致性,这样的类也被称为“VCL风格类”
9.1 C++和ObjectPascal对象模型
C++和ObjectPascal在创建、初始化、引用、拷贝和销毁对象的方法上有些微小的差异。在本节中描述这些细微的差别和它们对C++BuilderVCL风格类的影响。
9.1.1对象本身和实例
在C++中,类的一个实例是一个实际的对象。那个对象能直接被操作,或者通过引用或指针间接访问它。例如,给定一个构造函数中没有参数的C++类CPP_class,下列变量都是该类的有效实例变量:
相反,在ObjectPascal中,一个Object类型的变量总是间接地引用对象。所有对象的内存都动态地被分配。例如,给定ObjectPascal类OP_class:
ref是OP_class类型的对象的一个“引用”。转换到C++Builder代码,将是:
1.区分C++和ObjectPascal的引用
文档中经常会把ObjectPascal类实例变量作为引用提及,但却作为指针来描述它的行为。这是因为它兼有这两者的属性。一个ObjectPascal引用类似C++指针,但有下列的不同:
· 一个ObjectPascal引用是隐式的间接引用(在此情况下更像一个C++引用)。
· 一个ObjectPascal引用不像定义的操作那样有指针运算。
ObjectPascal引用和C++引用比较时,也有类似和不同之处。两种语言的引用都是隐式的间接引用,然而,
· 一个ObjectPascal引用可以被重指,而C++引用不能。
· 一个ObjectPascal引用可以是nil,而C++引用必须指向有效的对象。
有些VCL结构下的设计计划是建立在使用这种类型的实例变量的基础上的。指针是最接近Object Pascal引用的C++语言结构。因此,几乎所有的VCL对象的标识符在C++Builder中都被转换为C++指针。
注意ObjectPascal的var参数类型最接近C++的引用。参见9.2.3节的“Var参数”可获得更多信息。
2.拷贝对象
与C++不同,ObjectPascal没有可支持拷贝对象的内嵌编译器。本节描述这个差别对VCL风格类赋值操作符和拷贝构造函数的影响。
(1)赋值操作符
ObjectPascal赋值操作符(:=)不是一个类赋值操作符(operator=())。操作符赋值拷贝引用,而不是对象。在下列代码中,B和C都指向同一个对象:
这个例子在C++Builder中转换为下列代码:
C++Builder的VCL风格类的赋值操作符遵循ObjectPascal语言的规定。这意味着,下列代码中,在两个dereferenced指针之间的赋值是无效的,因为它们试图拷贝对象而不是指针:
注意 对于VCL风格类,使用C++语法的引用是有效的。例如,下列代码是有效的:
尽管与使用赋值操作符不同,但对于澄清和比较而言,这里提出的语法已经足够类似了。
(2)拷贝构造函数
ObjectPascal没有内嵌的拷贝构造函数。因而,在C++Builder中的VCL风格类也没有内嵌的拷贝构造函数。下例中的代码试图使用一个拷贝构造函数创建TButton指针:
对于VCL类,不能编写依赖于一个内嵌的拷贝构造函数的代码。要在C++Builder中创建VCL风格类对象的一个拷贝,可编写一个成员函数以拷贝对象。另外,VCLTPersistent类的派生类可重载Assign方法以从一个对象拷贝数据到另一个对象。例如,对于包含资源图像的图形类,诸如TBitmap和TIcon通常就会这么做。拷贝一个对象的方式最终由程序员(组件开发者)决定;但需注意一些标准C++使用的拷贝方法对VCL风格类是不适用的。
3.对象作为函数参数
正如前面所讨论的,C++和Object Pascal中的实例变量不是等同的。在将对象作为函数的参数传递时应该记住这一点。在C++中,对象可以通过值、引用或指针传递给函数。在Object Pascal中,当一个对象按值传递给函数时,应记住这个对象参数已经是一个对象的引用了。所以,实际上是引用被按值传递,而不是实际的对象。在Object Pascal中,不能像在C++中一样将实际对象按值传递。VCL风格类对象在传
递给函数时遵循ObjectPascal的规定。
9.1.2C++Builder中VCL类的对象构造
C++和Object Pascal构造对象是不同的。本节概述这一问题并说明C++Builder如何综合这两种方式。
1.C++对象构造
在标准的C++中,构造的顺序先是虚基类,然后是基类,最后是派生类。C++语法使用构造函数初始化列表调用基类构造函数。对象的运行时类型为当前构造函数被调用的类的类型。虚拟方法的分派遵循对象的运行时类型并因此在构造期间变化。
2.Object Pascal对象构造
在Object Pascal中,仅有实例化的类的构造函数确保被调用,然而,仍为基类分配内存。直接基类的构造通过在相应的派生类的构造函数中调用inherited来进行。习惯上,VCL类使用inherited调用(非空)基类构造函数。不过,这并不是语言的要求。对象的运行时类型立即被建立为实例化的类的类型,并且不在基类构造函数被调用时变化。虚拟方法的分派遵循对象的运行时类型并且不在构造期间变化。
3.C++Builder对象构造
VCL风格对象的构造与ObjectPascal对象类似,但适用C++语法。这意味着调用基类构造函数时遵循C++语法,对于所有的非VCL的基类和最直接的VCL父类使用初始化列表。这个VCL基类最先被构造。它使用inherited构造自己的基类,遵循ObjectPascal的方法。因此,VCL基类构造的顺序与C++的相反。然后从最远的祖先到派生类,所有的C++基类被构造。对象的运行时类型和虚拟方法分派也基于ObjectPascal。
图9-1说明了一个VCL风格类实例的构造,MyDerived从MyBase派生,MyBase是TWinControl的一个直接的派生类。MyDerived和MyBase以C++实现。TWinControl是以ObjectPascal实现的VCL类。
注意 到对一个C++程序员来说,构造的顺序似乎是反的,因为对于实际的VCL类,它从最边缘的父类开始一直到TObject,然后构造MyBase,最后构造派生类。
注意 TComponent不调用inherited,因为TPersistent没有构造函数。TObject有一个空的构造函数,它也不被调用。若这些类构造函数被调用,其顺序将遵循图9-1中的顺序(这些类为图中带灰影的部分)。
表9-1总结了C++、ObjectPascal、C++Builder的对象构造模型:
免责声明:本文仅代表作者个人观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
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 */
免责声明:本文仅代表作者个人观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。