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

原文链接 https://doc.qt.io/archives/qt-4.8/templates.html Qt 4.8 官方文档

模板是 C++ 内建的一种机制,它允许编译器根据传入参数的类型在编译期生成代码。因此,模板对框架开发者来说极具吸引力,Qt 在很多地方也确实使用了高级模板技术。

然而,模板是有局限性的。有些东西可以很容易用模板来表达,而有些则完全不可能。例如,一个通用的向量容器类很容易用模板来实现,甚至可以针对指针类型进行偏特化;但 一个 根据字符串形式的 XML 描述来构建图形界面 的函数,是不可能用模板来表达的。

在这两者之间还存在一个灰色地带: 有些事情 理论上 可以用模板“硬写”出来,但代价是代码体积、可读性、可移植性、易用性、可扩展性、健壮性,最终还有设计的优雅性都会受到严重影响。模板和 C 预处理器都可以被扩展,实现一些极其巧妙且令人惊叹的功能,但 能做,并不意味着应该这样做


现实中的编译器问题

我们必须提到一个非常现实的挑战:由于各种编译器实现不完善,在跨平台应用中,至今仍然无法充分、可靠地利用模板机制

遗憾的是,代码不是写在书里的,而是要用真实世界中的编译器,在真实世界中的操作系统上编译的。

即便在今天,许多被广泛使用的 C++ 编译器,在高级模板方面仍然存在问题。

例如:

不过,我们并不认为这些问题是决定性的限制。

即便假设所有 Qt 用户都拥有 完全符合标准、模板支持极佳的现代 C++ 编译器

我们仍然 不会 用基于模板的信号槽系统来取代当前基于字符串、由 moc 支持的方案。

原因如下:


一、语法很重要

语法不仅仅是“语法糖”。 我们用来表达算法的语法,会显著影响代码的可读性和可维护性

Qt 的信号槽语法在实践中被证明是非常成功的:

学习 Qt 的人往往会发现,这种语法能帮助他们理解并正确使用信号槽这一高度抽象、通用的概念。

此外,在类定义中声明信号可以确保信号受到保护,其保护方式类似于 C++ 的 protected 成员函数。

这能帮助程序员从一开始就形成正确的设计,而无需考虑各种设计模式。


二、代码生成器很好

Qt 的 moc(元对象编译器)提供了一种 超越语言本身能力的简洁方式

moc 的工作方式是:

生成的 C++ 文件会被编译并与类的实现一起链接(或者直接 #include 进源文件)。

通常,moc 是由构建系统自动调用的,对程序员几乎没有额外负担。


moc 并不是 Qt 唯一使用的代码生成器。另一个著名例子是 uic(用户界面编译器)

在 Qt 之外,代码生成器同样非常常见:

相比之下,替代方案通常是:

Qt 的选择是:

不把用户锁死在某个编译器或 IDE 中,而是让他们使用自己喜欢的工具,并将生成步骤集成进构建系统

这更简洁、更安全,也更符合 UNIX 哲学。


三、GUI 是动态的

C++ 是一种标准化、强大且复杂的通用语言,它被广泛应用于:

C++ 成功的关键在于其可扩展的语言设计

在保证高性能、低内存占用的同时,仍保持与 ANSI C 的兼容性。

但这些优点在 GUI 编程中也带来了缺点。


与 Objective-C 的动态消息机制相比,C++ 的 静态对象模型 在组件化 GUI 编程中是一个明显劣势。

moc 让我们把这一劣势转化为了优势,为安全、高效的 GUI 编程引入了所需的灵活性。

例如:

更重要的是:

这些都是 模板方案无法实现的

这种运行时内省机制开辟了新的可能性

例如可以根据 Qt Designer 的 XML UI 文件生成界面和信号槽。


四、调用性能并非一切

Qt 的信号槽确实比模板方案慢一些:

这是正常的,因为 Qt 的机制还包含:

Qt 不依赖于过多的内联和代码扩展,提供了 无与伦比的运行时安全性

例如:


很多性能测试都是对“空槽”进行的。一旦槽中执行了任何有意义的工作(例如一些简单的字符串操作),信号调用的开销就变得可以忽略不计了。

事实上:

都比发射一个 Qt 信号开销大。

备注: 如果你真的在性能关键的内层循环中(比如每个字节/每个样本/每次迭代都要触发)发现信号槽成为瓶颈,说明你可能需要的是 监听器接口(listener interface)模式,而不是信号槽。例如,如果您有一个从网络下载数据的对象,使用信号来指示请求的数据已到达是完全合理的。但是,如果您需要将每个字节逐个发送给消费者,则应使用监听器接口模式,而不是信号槽。

译者注:高频数据推送(如 1ms 一次或更细粒度)不宜使用信号槽,改用一对一回调或监听器接口,并尽量采用按块传输的方式。


五、没有限制

正是因为有 moc,我们才能加入 模板完全做不到的功能,例如:

这些能力是 Qt Designer、插件系统和组件互操作的基础,而 模板无法提供这些能力


总结

通过 moc,Qt 在 C++ 之上构建了一套机制,使其在灵活性上接近 Objective-C 或 Java 运行时,同时仍然保持了 C++ 在性能和可扩展性上的独特优势。

这正是 Qt 成为今天这样一个灵活、强大、易用工具的原因。