QT类库与Delphi VCL类库的体系结构对比——两者十分类似!

今天在看QT对象内存管理的一篇文章时:

http://blog.csdn.net/dbzhang800/article/details/6300025

想到了一个问题:就是QT类库体系结构与Delphi类库体系结构的对比问题。从它们都有parent属性,而且都可以管理子控件的内存释放,就可以猜测两者的体系结构十分相似。以下是我的过程,就把它当自己对Qobject和QWidget的一个熟悉过程吧。

---------------------------------------------------------------------------

先看它们的继承体系:

Delphi的TApplication->TComponent->TPersistent->TObject

QT的QApplication->QCoreApplication->QObject

注意,它直接继承自QObject,而省略了delphi的两个层次:TComponent和TPersistent

因此特意查看了一下,QObject已经包括了setParent,非常类似于TComponent的InsertComponent(owner)

QObject的setObjectName相当于TComponent的setName。这样一来,QObject即已经包括了TComponent的主要功能。

但是QObject貌似没有TPersistent的RTTI和流入流出的功能。不过不要紧,因为C++标准里就包括了RTTI和流入流出的功能,因此不需要在类库里体现这一点,而是随时随地可用的。而Delphi因为自成一体,语言和类库结合的过于紧密,不得不单独定义一层TPersistent来提供相应的功能(也就是说,不使用VCL的object pascal是没有RTTI和流入流出功能的)。

这么说来,一个QObject就已经包括了Delphi的TObject、TPersistent和TComponent这三个类层次的功能。并且QObject还多了对signal的管理,tr的功能。

而且还有一个最大的区别是从QObject开始就处理事件的方方面面,connect,过滤,阻塞,并且专门提供了一大堆相关的附加功能。Delphi虽然也可使用Dispatch把消息发给TObject,但事实上没有人这样使用。

此外,QObject还提供了deleteLater功能,即自删除。这可是没有父类控件的情况下提供的哦~

还有一个特点:QObject竟然是从属于某个线程的,而且专门提供了2个函数:thread()和moveToThread()。而其它语言包括Delphi的Object都没有这个特征。究其原因,是因为QT把信号槽机制放在QObject级了,换而言之每个Object都可接受和处理信号,而信号是从属于某个线程的,因此QObject也就只能从属于某个线程了。这只是我的粗浅理解,不知道对不对。而且我觉得能不能把QObject的最基本服务提炼出来作为更高一层呢?这样更易于理解,也便于与其它语言交互,还可少用内存和其它负担。目前QObject管的事情实在太多了,强迫QObject从属于某个线程感觉有点怪异。对象交互性是很重要,但从实际角度而言,主要是GUI对象交互管理比较麻烦,所以没有必要将信号槽机制放在根对象级。

QObject还可调用metaObject()返回QMetaObject,里面又包括很多东西(类名,超类名,属性,信号,槽等等,QObject提供的objectName()是实例的名称,不是类名)。每个使用Q_OBJECT宏的子类实例,都会包括一个metaObject。但是QMetaObject是通过宏关键字实现的,所以除非官方提供了相应方法,否则无法增加/修改其内容。但是偏偏官方还提供了Q_DECLARE_METATYPE(MyClass)。这样一来,QMetaObject所包含的东西几乎是无限制的,其功能可谓异常强大。

最后,QObject还包括了customEvent,childEvent和timerEvent三个事件,而TObject却没有事件,要等到TControl才有,因为到了TControl才开始真正处理GUI事件。顺便数了一下,QWidget有31个事件(都是些保护函数),其中包括了winEvent函数。

---------------------------------------------------------------------------

让我觉得最有趣的就是,QT也有一个Application,并且相当于也是继承自TComponent。

QCoreApplication 最重要的功能是用来管程序的事件过滤(包括non-GUI的程序),而且QApplication管的事情非常多:

1. main函数的参数都传给它(Delphi则使用ParamStr这个系统级的变量解决了问题)

2. 管理QInputContext,里面包括输入方法和状态

3. Session

4. 显示状况(字体,styleSheet,调色板,光标,文字左右方向)

5. threshold 自动最大化的门槛,警告窗口,焦点变化,关闭closeAllWindows,当前激活的窗口,记录最后关闭的窗口等等

6. 提交数据

7. 与session manager的交互

8. 与外部桌面,键盘,鼠标的交互,双击间隔等等

看了一下Delphi的TApplication,主要还是管程序的运行与终止,以及它所有的子窗口。总的来说,QApplication的功能比TApplication管的事情多的多,因为Delphi还有TScreen分担一小部分功能,既不需要跨平台,且有winapi助阵因而对其它功能不以为然,TApplication把它的子窗口都管理管理好就行啦!虽然程序员也可使用winapi帮助QApplication,但是那样代码就不跨平台啦!因此QApplication被迫设计成多管许多系统方面的闲事。最后还发现application.run与app.exec都是那么的一致,控制GUI线程的事件/消息循环,真是太逗了。

------------------------------------------------------------------------------

那么接下去呢?Delphi单独写了一个TControl,主要目的是给图形类也提供一些鼠标处理的功能和文字字体颜色对齐这些基本的显示功能(比如用户可点击TLabel),这是Delphi非常独特和强大的功能。而QT接下去就是QWidget,相当于把TControl和TWinControl的功能直接合并了,QLabel就是继承自它的,这样一来每个QWidget都有句柄(问题:为啥Spy++探测不到QT程序的每个子元素),有了句柄还不是可以随心所欲的做各种处理,这就和标准Windows控件没有什么区别了。

那么QT的图像显示类怎么处理?基本上是自成一体,查看一下QPaintDevice是原类(不继承自任何类),这时C++的多继承特性就可发挥作用了,QWidget直接继承自QObject和QPaintDevice,即把Delphi的TWinControl和TGraphicControl的功能强行融合在一起(因为多继承的原因,这种融合是轻而易举的),准确的说,是把Delphi的TCustomControl和TGraphic的功能强行融合在一起,并且对图像控件和非图像控件不做区分,即每个QWidget子类都可自绘,而不依赖于系统提供的原生控件的功能与界面。Delphi之所以提供TControl是为了在提供一些功能的同时还能节省系统资源,即图形控件不需要系统句柄。而QWidget看起来没有强行区分图像控件和句柄控件,但它在更底层的实现上,实现了所有控件都是自绘,因此不需要在更上层的类库划分里再次区分两者。这么说来,两者还真是异曲同工呢,只是实现层面不同而已。

再进一步说,无论QT还是Delphi,首先它的是一个GUI元素(有没有句柄则不一定),它才可以具备自绘功能。但是如何自绘,以及哪些功能是通用的,这就需要一个单独的类进行定义,而不可能直接放到QWidget和TGraphicControl里,此时QT的QPaintDevice和Delphi的TGraphic就扮演了这种功能。不同之处在于QPaintDevice是原类,而TGraphic却仍继承了TObject和TPersistent,前面说过了TPersistent的功能,QPaintDevice也有(尽管它是原类),但继承自TObject并获取它提供的功能感觉不是很必要,也许这是Delphi基于RAD的设计的原因——为了管理和使用上的方便,因此才这样规定所有类都必须继承TObject。TGraphic还必须实现TInterface, IStreamPersist两个接口,说明还提供了引用计数和随心所欲的流入流出(TPersistent是针对DFM文件格式的)。

TComponent.Name与QObject.objectName相似(注意,TObject没有Name属性)

TComponent.tag与QWidget::accessibleName和QWidget::accessibleDescription比较相似

最后一个问题,QT为什么没有Delphi里对应的TForm?目前的情况是QMainWindow对应Delphi的主TForm,它继承自QWidget,相当于把控件专门强化后专门用来做主界面。而QT的每一个QWidget,都可作为一个显示窗口(除了主Form以外的每一个TForm),因此不需要单独定义TForm对应的组件,换而言之QWidget提供的功能过于强大,每一个QWidget既可以自己作为窗口,也可作为窗口的一个子元素。

QWidget过于强大,看看这些函数就知道了,大概QT官方是想尽可能的提供最方便的全部界面功能:

setGraphicsEffect

setMask

setStyle

windowModality

setWindowRole

而Delphi尽管也异常强大,但也没有直接提供如此之多的功能,更多的还是要基于Winapi的特性来扩展相应的功能。其实QT这么做也是没有办法呀。Delphi控件在取得Windows句柄后可以有winapi随时来助阵,但QT一切都是自绘,如果它官方不提供这种基本的特殊功能(好像有点绕口),普通程序员是很难进行扩充的。

这里还要提一句,体系结构类的函数多不要紧,那个完全不占内存,只有类里的数据成员才会增加对象的内存大小。

还有QDialog,还有。。。以后再补充。

------------------------------------------------------------------------------

今天又发现,QDesktopWidget相当于TScreen,真是差点笑死:

QDesktopWidget* d = QApplication::desktop();

区别在于QDesktopWidget它没有提供一个全局实例,而是每次都要返回指针。但事实上也是一直存在于内存中。

但是Delphi没有提供QSessionManager,也许是它有winapi,所以不需要吧。

------------------------------------------------------------------------------

还有一个重大的区别,就是Delphi只使用了绝对坐标体系,而QT同时使用了Layout可变的相对坐标体系(其实Delphi最新的FMX体系也提供了类似的layout)和setGeometry绝对坐标体系,还有QSS也能控制一部分界面排版(又跟VCL Style对应上了),这三者可同时起作用,并且相互影响或屏蔽,因此导致我在开发的时候经常产生一些困惑。

------------------------------------------------------------------------------

总的来说,QT的体系结构与Delphi类库体系结构十分相似,没有什么本质区别,但是鉴于Delphi严格区分图像控件和非图像控件,说明管理的更精细一些,把TForm与普通控件区分开也是这个原因,节省系统资源。而Delphi强调TComponent,除了提供父子控件的内存管理功能以外,还方便对非可视的算法与系统功能也可制作控件,也算是它的一大特色。