ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

为什么 Qt 使用 moc 来处理信号和槽?

为什么 Qt 使用 moc 来处理信号和槽? **为什么 Qt 使用 moc 来处理信号和槽**模板是 C 中的一种内置机制它允许编译器根据传入的参数类型即时生成代码。因此模板对于框架创建者极具吸引力并且我们在 Qt 的许多地方确实使用了高级模板。然而这也有其局限性有些功能可以很容易地用模板实现而有些功能则根本无法用模板表达。一个泛型向量容器类很容易实现甚至可以对指针类型进行偏特化但是一个根据给定的 XML 描述字符串来设置图形用户界面的函数则无法作为模板来实现。这之间还存在一个灰色地带有些功能可以通过模板技巧来实现但代价是代码膨胀、可读性下降、可移植性变差、可用性降低、扩展性受限最终导致设计美感缺失。模板和 C 预处理器都可以被发挥到极致做出令人难以置信的精妙之事。但仅仅因为能做到并不意味着这样做就是正确的设计选择。不幸的是代码并非为了出版成书而是要用真实的编译器在真实的操作系统上进行编译。以下是 Qt 使用 moc 的一些原因**语法至关重要**语法不仅仅是语法糖我们用于表达算法的语法会显著影响代码的可读性和可维护性。Qt 信号和槽所使用的语法在实践中已被证明非常成功。该语法直观、简单易用且易于阅读。学习 Qt 的人会发现这种语法有助于他们理解和运用信号和槽的概念——尽管这个概念本身具有高度的抽象性和通用性。这有助于程序员从一开始就做出正确的设计甚至无需考虑设计模式的问题。**代码生成器是好的工具**Qt 的 moc元对象编译器提供了一种干净的方式来超越编译语言本身的能力。它通过生成额外的 C 代码来实现这一点这些代码可以被任何标准的 C 编译器编译。moc 读取 C 源文件。如果发现一个或多个包含 Q_OBJECT 宏的类声明它就会生成另一个 C 源文件其中包含这些类的元对象代码。moc 生成的 C 源文件必须与类的实现一起编译和链接或者可以被 #include 到类的源文件中。通常moc 不是手动调用的而是由构建系统自动调用因此程序员无需额外操作。moc 并不是 Qt 使用的唯一代码生成器。另一个突出的例子是 uic用户界面编译器。它接收 XML 格式的用户界面描述并生成用于设置表单的 C 代码。在 Qt 之外代码生成器也很常见。例如用于使程序或对象能够跨进程或机器边界通信的 rpc 和 idl或者各种各样的扫描器和解析器生成器其中最著名的是 lex 和 yacc。它们接收文法规范作为输入并生成实现状态机的代码。代码生成器的替代方案包括被黑客修改过的编译器、专有语言或者带有单向对话框或向导的图形化编程工具这些工具在设计时而非编译时生成晦涩的代码。我们并没有将客户锁定在专有的 C 编译器或特定的集成开发环境IDE中而是让他们能够使用任何他们喜欢的工具。我们没有强制程序员将生成的代码添加到源代码仓库中而是鼓励他们将我们的工具集成到他们的构建系统中这种方式更干净、更安全也更符合 UNIX 的精神。**图形用户界面是动态的**C 是一种标准化、功能强大且精密的通用语言。它是唯一一种被广泛应用于如此广泛软件项目中的语言涵盖了从整个操作系统、数据库服务器、高端图形应用程序到普通桌面应用程序的各种应用。C 成功的关键之一在于其可扩展的语言设计它在保持与 ANSI C 兼容的同时专注于实现最高的性能和最小的内存消耗。尽管有这些优点但也存在一些缺点。对于 C 而言在基于组件的图形用户界面编程方面其静态对象模型相比于 Objective C 的动态消息传递方法是一个明显的劣势。适用于高端数据库服务器或操作系统的东西并不一定是 GUI 前端的正确设计选择。借助 moc我们将这个劣势转化为优势并增加了所需的灵活性以应对安全且高效的图形用户界面编程的挑战。我们的方法远远超出了模板所能实现的任何功能。例如我们可以拥有对象属性。我们还可以拥有重载的信号和槽这在使用重载作为关键概念的编程语言中感觉很自然。我们的信号为类实例的大小增加零字节这意味着我们可以添加新的信号而不会破坏二进制兼容性。另一个好处是我们可以在运行时探查对象的信号和槽。我们可以使用类型安全的按名称调用来建立连接而无需知道所连接对象的确切类型。这在使用基于模板的解决方案时是不可能实现的。这种运行时自省的能力开辟了新的可能性例如从 Qt Widgets Designer 的 XML UI 文件中生成并连接 GUI。**调用性能并非一切**Qt 的信号和槽实现不如基于模板的解决方案快。虽然发射一个信号在使用常见模板实现时大约相当于四个普通函数调用的开销但 Qt 需要相当于大约十个函数调用的开销。这并不奇怪因为 Qt 的机制包含通用的封送拆收器marshaller、自省、不同线程间的队列调用以及最终的脚本化能力。它不依赖于过度的内联和代码膨胀并且提供了无与伦比的运行时安全性。Qt 的迭代器是安全的而速度更快的基于模板的系统的迭代器则是不安全的。即使在向多个接收者发射信号的过程中这些接收者也可以被安全地删除而不会导致程序崩溃。如果没有这种安全性您的应用程序最终可能会因为难以调试的已释放内存读取或写入错误而崩溃。然而基于模板的解决方案难道不能提高使用信号和槽的应用程序的性能吗诚然Qt 为通过信号调用槽的成本增加了一点点开销但调用的成本仅占槽整个成本的一小部分。对 Qt 信号和槽系统的基准测试通常是在空槽的情况下进行的。一旦您在槽中执行任何有意义的操作例如一些简单的字符串操作调用开销就变得可以忽略不计。Qt 的系统经过了高度优化任何需要 operator new 或 delete 的操作例如字符串操作或从模板容器中插入/删除元素都比发射一个信号要昂贵得多。附带说明如果您在性能关键型任务的一个紧凑内循环中使用了信号和槽连接并且确定该连接是瓶颈那么可以考虑使用标准的监听器接口模式而不是信号和槽。在这种情况下您可能只需要 1:1 的连接。例如如果您有一个从网络下载数据的对象使用信号来指示请求的数据已到达是一种完全合理的设计。但是如果您需要将每个字节逐一发送给消费者那么请使用监听器接口而不是信号和槽。**没有限制**由于我们为了信号和槽而有了 moc我们可以将其他有用的功能添加到其中而这些功能是无法用模板实现的。其中包括通过生成的 tr() 函数实现的作用域翻译以及具有自省和扩展运行时类型信息的高级属性系统。仅属性系统本身就是一个巨大的优势如果没有一个强大且具有自省能力的属性系统像 Qt Widgets Designer 这样强大而通用的用户界面设计工具将会很难编写——甚至是不可能的。但这还不止于此。我们还提供了一种动态的 qobject_castT() 机制它不依赖于系统的 RTTI因此也没有其局限性。我们用它来从动态加载的组件中安全地查询接口。另一个应用领域是动态元对象。例如我们可以获取 ActiveX 组件并在运行时为其创建一个元对象。或者我们可以通过导出元对象将 Qt 组件导出为 ActiveX 组件。这些功能都无法用模板实现。本质上结合了 moc 的 C 为我们提供了 Objective-C 或 Java 运行时环境的灵活性同时保持了 C 独特的性能和可扩展性优势。这正是 Qt 成为我们今天所拥有的灵活且舒适工具的原因。
返回列表