为什么Qt不用模板来实现信号和槽?

Qt 3.0.5

主页 | 所有的类 | 主要的类 | 注释的类 | 分组的类 | 函数

为什么Qt不用模板来实现信号和槽?

一个简单的答案是,当初Qt被设计的时候,因为各种各样的编译器的不充分,所以在多平台应用程序中完全使用模板机制是不可能的。甚至今天,许多被广泛使用的C++编译器在使用高级模板的时候还是有问题的。例如,你不能安全地依靠部分模板的示例,这对一些不平常的问题领域是必要的。因此Qt中模板的用法不得不保守。记住Qt是一个多平台的工具包,在Linux/g++平台上的进步不一定能够在其它情况下获得改进。

那些在模板执行上比较弱的编译器终将得到改进。但是,即使我们所有的用户以极好的模板支持接近一个完全现代的C++编译器的标准,我们也不会放弃通过使用我们的元对象编译器的基于字符串的途径。这里是为什么这样做的五个原因:

1. 语法问题

语法不是糖:我们用来表达我们的运算法则的语法较大程度上影响我们的代码的可读性和可维护性。Qt中信号和槽所用的语法在实践中被证明是非常成功的。这种语法是直观的、容易使用的和容易读的。人们在学习Qt时发现这种语法帮助他们理解和使用信号和槽的概念——而不管它们的高度抽象和通用的性质。此外,在类定义中信号声明保证了信号就像被保护的C++成员函数一样被保护。这帮助了程序员在刚开始的时候就获得了他们的设计权力,而不用不得不考虑设计模式。

2. 预编译程序是好的

Qt的moc(元对象编译器)提供了一种的方式除了那些编译语言的工具。它可以生成任何一个标准的C++编译器都能编译的额外的C++代码。元对象编译器读取C++源文件。如果它发现其中有一个或多个类的声明中含有“Q_OBJECT”这个宏,它就会为这些类生成另外一个包含元对象代码的C++源文件。这个由元对象编译器生成的C++源文件必须和它的类实现一起编译和连接(或者它也可以被#included到个类的源文件中)。有特色的是元对象编译器不是用手工来调用的,它可以自动地被连编系统调用,所以它不需要程序员额外的付出努力。

这里有一些其它的预编译程序,比如,rpcidl,它们使程序或者对象能够通过进程或者machine boundaries来进行通讯。预编译程序的选择是编写编译器,专有的语言或者使用对话框或向导这些图形编程工具来生成晦涩的代码。我们能使我们的客户使用他们所喜欢的工具,而不是把他们锁定在一个专有的C++编译器或者一个特殊的集成开发环境。我们不强迫程序员把生成的代码添加到源程序仓库中,而是鼓励他们把我们的工具加入到他们的连编系统中:更加干净,更加安全和更加富有UNIX精神。

3. 灵活性为王

C++是一种标准化的、强大的和精心制作的多用途语言。它只是用来开发很多领域的软件项目的一种语言,生成许多种应用程序,从整个操作系统、数据库服务器和高性能的图形应用程序到普通的桌面应用程序。C++成功的关键之一是它着重于最大效能和最小内存占用同时保持ANSI-C的的兼容性的可伸缩语言设计

在这些优势当中,也有一些不利方面。对于C++,当它用来构成基于组件的图形用户界面编程的时候,静态的对象模型在使用Objective C途径的动态消息机制方面是明显的劣势。对于一个高端数据库服务器或者一个操作系统使用正确的图形用户界面前端工具的这一设计选择不是必须的。使用元对象编译器,我们可以把这一劣势转化为优势并且会加入当我们遇到安全的和有效的图形用户界面程序编程这一挑战的时候所需要的灵活性。

我们的方法比你用模板所能做到的一切更好。比如,我们有对象属性。并且我们可以重载信号和槽,当你在使用可以重载这一关键理念的语言进行程序设计的时候你会感到很自然。我们的信号只对一个类实例的大小增加了零个字节,也就是说我们能在不破坏二进制程序的兼容性的同时加入新的信号。因为我们不像模板那样过多地依靠内嵌,我们可以使得代码变得更小。添加一个新的连接就是增加一个简单地函数调用而不是一个复杂地模板函数。

另外一个好处就是我们可以在运行时探测对象的信号和槽。我们可以通过使用类型安全的名称调用而不用我们知道我们要连接的对象的确切类型来建立连接。这在一个基于模板的解决方案中是不可能的。这种运行时的自我检测扩充了一种新的功能,比如我们可以使用Qt设计器的XML格式的ui文件来生成和连接图形用户界面。

4. 调用性能不是一切

Qt的信号和槽的执行没有基于模板的解决方案那样快。发射一个信号的时间大约和普通模板实现中的四个普通函数调用的时间差不多,Qt要求努力控制到和十个普通函数调用差不多。这也不必惊讶,因为Qt机制中包括了一个通用调度器,自我测量和基本的脚本化的能力。它不过分依赖内嵌和代码扩展,并且它提供了运行时得无比安全性。Qt的迭代(iterator)是安全的而那些基于模板的更快的系统确不是。甚至在你发射一个信号到多个接收器的过程中,那些接收器可以被安全地删除而不会导致你的程序崩溃。没有了这种安全,你的程序在调试自由的内存读或写错误这种困难情况下最终会崩溃。

虽然如此,一个基于模板的解决方案不是能比使用信号和槽更加提高应用程序的性能吗?虽然Qt通过一个信号调用槽的时候会增加一点时间开销是真的,这个调用的开销只占整个槽调用的开销的很小比例。以上的情况都是基于Qt的信号和槽系统使用典型的空槽。一旦你在槽里面做任何有意义的事情时,比如一些简单的字符串操作,调用的时间开销就可以忽略不计了。Qt的系统非常的优化了,以至于任何东西都要求操作符new或者delete(比如,字符串操作或者从一个模板容器插入/删除一些东西)的时间开销要比发射一个信号多的多。

另外:如果你在一个性能为关键的任务中的一个严紧的内部回路中使用信号和槽并且你认为这种连接是瓶颈的话,建议你使用标准的监听接口模式来替代信号和槽。当这种情况发生时,总之你也许只需要一个一对一的连接。比如,你有一个对象从网络上下载数据,你使用信号来说明所需要的数据已经到达的这种设计是非常明智的。但是如果你需要向接收者一个字节一个字节地发送数据,使用监听接口要比信号和槽好。

5. 没有限制

因为我们有元对象编译器来处理信号和槽,我们可以向它添加一些其它模板不能做的但很有用的东西。在这之中,有我们利用生成的tr()函数进行作用域翻译,和一个自我测量和扩展的运行时的类型信息的先进的属性系统。属性系统有一个独一无二的优势:没有一个强大的和自我测量的属性系统——如果这不是不可能的——一个Qt设计器这样的强大的和通用的用户界面设计工具就很难被写出来。

带有元对象编译器预处理器的C++从本质上给我们带来对象的C的灵活性或一个Java的运行时环境,当保持C++的唯一特性和可伸缩的优点。它使得Qt成为我们今天拥有的灵活和舒适的工具。


Copyright © 2002 Trolltech Trademarks 译者:Cavendish
Qt 3.0.5版