3

我正在学习 Qt,并且对信号槽模式很感兴趣。我的问题是,信号和插槽只是事件侦听器和处理程序的语法糖,还是后台发生的事情具有不同的性质?如果是后者,根本区别是什么?

4

3 回答 3

3

这不仅仅是语法糖。在 Qt 信号/槽的背景下有一个真正的工作。这项工作由 MOC(元对象编译器)完成。这就是在所有 C++ 头文件中都有一个包含带有 Q_OBJECT 宏的类的过程的原因。

信号/插槽的“困难部分”是当您处于多线程上下文中时。事实上,connect() 函数的 Qt::ConnectionType 参数,它在单线程环境中是 Direct(例如,函数的直接调用),如果发送者和发射器不在同一个线程中,则在队列中排队。在这种情况下,信号必须由 Qt 事件循环处理。

更多详情:http: //qt-project.org/doc/qt-4.8/signalsandslots.html

于 2014-04-20T18:23:12.333 回答
2

信号和槽是语法糖还是更多?我的问题是,信号和插槽只是事件侦听器/处理程序的语法糖

不,它们存在的平均原因是排放和处理的脱钩

或者背景中发生的事情具有不同的性质?

这个想法是将发射与某个“事件”的处理分开。如果您考虑将直接函数调用作为替代方案,我想指出,对于它们,负责发射的代码需要了解实际处理“信号”的代码。那就是这两个部分彼此太紧了。

如果不更改负责信号发射的代码,就不可能为信号动态添加另一个处理程序。想象一个这样的例子:

  • 代码的某些部分发出信号“水果到了”

  • 此代码将直接调用“洗水果”方法。

如果有人想添加一种方法来计算水果的数量怎么办?

  • 需要修改前面的代码以包含对该方法的直接调用。

使用信号槽机制,您无需接触原始代码。您可以通过完全不同的代码将新插槽简单地连接到现有信号。这被称为良好的设计和自由。

当您拥有像 Qt 这样可以在不事先了解 Qt 应用程序的情况下发出信号的库时,这尤其有用。如何处理信号取决于 Qt 应用程序。

此外,这种模式还使应用程序响应更快,阻塞更少,方向函数调用就是这种情况。这是因为存在构建 Qt 信号槽机制的事件循环。当然,您也可以将线程与直接调用一起使用,但与理想世界中所需的相比,它的工作量更大且难以维护。

因此,正如部分已经触及的那样,后台有一个 QtEventLoop 将这些事件排队等待处理,尽管也可以执行“直接调用”。

真正的背景内部实现代码可以在那里找到,也可以在 moc(元对象编译器)中找到。Moc 基本上是为您没有为其定义主体的信号创建一个函数,因此您只需在需要时在 QObject 子类中声明它们。

您可以在此处阅读有关该主题的更多信息,但我认为我的解释可以让您继续前进:

Qt 信号和插槽

QtDD13 - Olivier Goffart - Qt 5 中的信号和插槽

Qt 信号和槽是如何工作的

Qt 信号和槽如何工作 - 第 2 部分 - Qt5 新语法

Qt5 中的信号和槽

使用元对象编译器 (moc)

于 2014-04-21T00:37:00.070 回答
2

信号和槽是一种将方法调用与被调用方法解耦的方法。它们根本不是语法糖,因为它们没有向 C++ 语言添加新语法。信号发射是一种方法调用。插槽是一个普通的旧实例方法。链接两者的代码是普通的旧 C++。这里没有什么新鲜事——没有任何种类的糖。

大多数你称之为“语法糖”的东西类似于注释——那些是空定义(Q_SLOT, Q_SIGNAL, signals, slots),用于标记元对象编译器(moc)处理的方法。Moc 基于声明的正常 C++ 语法生成自省信息和信号实现(有一些限制)。

我声称这不是语法糖,因为 moc 理解常规 C++,并且不是基于任何语法糖,而是基于通常的实例方法声明生成自省数据。“糖”是为了避免让 moc 为类声明中的所有内容生成元数据而过早悲观。它还让 moc 忽略方法定义 - 否则它需要解析它们,并假设没有定义的方法是信号。

emit宏仅供人类使用,仅表明方法调用实际上是信号发射。moc 不使用它。它被定义为空。

Q_OBJECTQ_GADGET宏声明了一些用于访问元数据的类成员。可以说,它们是唯一真正的“糖”,因为它使您不必输入几行声明。

使其工作可能涉及相当多的代码。

  1. 一个信号:

    • 是一个实例方法,其实现由 moc 生成,

    • 具有关于其名称和参数的完整自省信息。这可作为QMetaMethod.

  2. 一个插槽:

    • 是您提供其实现的实例方法,

    • 同样具有完整的内省信息。

元信息在运行时可用,并且可以被不知道信号或槽的签名的代码枚举和使用。

当您发出信号时,您只需调用 moc 生成的方法。此方法调用获取相关互斥锁的 Qt 库代码,迭代附加槽的列表,并执行调用,并在此过程中根据需要获取额外的互斥锁。正确执行此操作需要小心,因为发送者和接收者对象可以驻留在不同的线程中。必须避免将槽调用传递给不存在的对象。哦,你也不想要死锁。这需要一些先见之明。

由于信号和槽都只是方法,因此您当然可以将信号连接到其他信号 - 底层机制并不关心被调用的内容,它只是一个可调用的方法。不可调用的方法是那些没有元数据的方法。

发出相关信号时会调用插槽。信号发射只是对生成的信号主体的方法调用。这与事件侦听器模式不同,因为插槽调用可以是立即的(所谓的直接连接)或延迟到事件循环(所谓的排队连接)。通过复制参数并将它们捆绑在一个QMetaCallEvent. 这个事件被“转换”回一个方法调用QObject::event。这发生在事件循环将事件传递给目标对象时。

元数据不仅仅包含信号和槽签名。它还允许您默认和复制构造信号/槽参数类型 - 这是实现排队调用所必需的。它还包含用于枚举的键值对——这就是 Qt 相当容易编写脚本的原因。传递给 Qt 方法的所有枚举值都可以在运行时按名称查找!

于 2014-04-21T16:48:29.620 回答