信号和槽是什么?

27

有人能简单解释一下 "信号和槽" 模式吗?


2
你的意思是Qt信号和槽吗? - Johannes Schaub - litb
模式通常是这样的。维基百科表明Boost也可以做到这一点。 - JeffV
6个回答

29

信号和槽是一种解耦发送者(信号)和零个或多个接收者(槽)的方式。假设你有一个系统,其中存在你希望其他任何对这些事件感兴趣的系统部分可用的事件。与其将生成事件的代码硬编码到想要了解这些事件的代码中,你可以使用信号和槽模式。

当发送者发出一个事件(通常通过调用与该事件/信号关联的函数),所有该事件的接收者都会自动被调用。这使你可以在程序的生命周期内根据需要连接和断开接收者。

由于此问题标记为C ++,因此这里提供一个链接到Boost.Signals库的页面,该页面有更详细的说明。


16
我认为当你把信号和槽看作是观察者模式或发布/订阅模式的可能实现方式时,最能描述它们。例如,在发布方面有一个signal,例如buttonPressed(IdType)。每当按下按钮时,将调用连接到该信号的所有槽。槽位于订阅方面。例如,槽可以是sendMail(IdType)
随着事件“按钮按下”,由于id已经被传递,因此插槽将知道哪个按钮被按下。 IdType表示在发布者和订阅者之间发送的数据类型。订阅者可能执行的操作是connect(signal, slot),它可以连接buttonPressed(IdType)sendMail(IdType),以便如果按下按钮,则调用特定的插槽。
好处是订阅者(插槽端)无需关心信号的细节。它只需要连接即可。因此,这里我们拥有大量的松耦合。您可以更改按钮的实现,但插槽的接口仍将保持不变。
查看Qt Signals/SlotsBoost Signals以获取更多信息。

6
想象一下在你的应用程序中有一个GUI。大多数情况下,控制流程不会非常线性,即你会有一个与GUI交互的用户(如按钮、菜单等),这本质上是一个事件驱动模型,可以使用信号和槽模式很好地实现。信号是由对象(如GUI组件)生成的事件,而槽是这些事件的接收者。
以下是一个例子:假设你有一个复选框,在你的编程语言中表示为一个对象。该复选框可能发生多种事情:它可以被切换,这也意味着它被设置或取消设置。这些是它可以发出的信号。我们将它们命名为checkboxToggled、checkboxSet和checkboxUnset。正如你所看到的,在这个例子中,当复选框被切换时,它总是会发出checkboxToggled信号,但还会发出另外两个信号中的一个,具体取决于状态的变化方式。
现在想象一下有一些其他的对象,即一个标签(为了这个例子而始终存在为一个对象,但可以“出现”和“消失”)和一个系统蜂鸣声(也表示为一个对象),它可以简单地响铃。这些是这些对象拥有的槽。我们将它们称为“messageAppear”、“messageDisappear”和“beep”。
假设你想让系统蜂鸣声在每次复选框被切换时响起,并且标签出现或消失取决于用户是否选中了复选框。
因此,你需要将以下信号连接到以下槽(左边是信号,右边是槽):
checkboxToggled -> beep
checkboxSet -> messageAppear
checkboxUnset -> messageDisappear

这基本上就是它的全部。

信号和槽还可以带参数。例如,使用一个滑块来设置一个数值,您想在用户移动滑块时将改变后的数值与发出的信号一起发送: sliderChanged(int)。

当然,要实际做些有用的事情,您需要编写一些自己的类,其中包含自己的信号和槽。这很容易做到,并且使用这些自己的信号和槽,您可以以事件驱动的方式与GUI或代码的其他部分进行交互。

请记住,信号和槽通常是对称的,因为通常会有一个与槽相对应的信号。例如,复选框可能在切换时发出信号,但它也可能包含一个切换复选框本身的槽。很容易实现两个相互对立的单选框。


4
我假设您在谈论QT的信号和槽。
这非常简单。一个类的实例可以发出信号,另一个类的实例可以在一个槽中捕获该信号。这有点像函数调用,只是调用函数的人不需要知道谁想要接收该调用。
最好的方法是举个例子。QPushButton类有一个QPushButton::clicked()信号。每当按钮被点击时,该信号就会触发。按钮不需要知道谁对点击事件感兴趣,它只需触发信号,有兴趣的人可以连接到它上面。
实际上,放置按钮的QDialog对按钮点击事件很感兴趣。它具有MyDialog::buttonClicked()槽。在MyDialog c'tor中,您需要将按钮的click()信号连接到对话框的buttonClicked()槽,以便在触发信号时调用该槽。
还有一堆更高级的东西:
  • 参数,一个信号可以有参数,这些参数也可以选择性地传递给插槽。
  • 跨线程调用 - 如果您正在进行需要跨线程的信号槽连接,则QT会自动缓冲信号并将其排队到正确的线程中。例如,当GUI线程需要与工作线程通信时,这会自动发生。

这里是QT文档中更多信息。


1
有一个普遍的误解,认为类就像人、狗、自行车这样的名词。然后就有意义地认为一个人(实例)有一只狗和一辆自行车。
让我们从对象应该是什么开始说起。对象是数据和过程。程序是什么?数据和过程。对象应该是(相对)独立的“小型”子程序。因为面向对象编程被教得很含糊且被滥用(需要引证),人们认为所有东西都需要是一个类或一个对象。这并不是这样, 对象是具有“小”API(公共子程序)的独立“小”程序。一些程序员甚至不把他们的项目分解成子程序,只是在更适合处理数据和过程的地方使用对象。
现在,假设我们同意对象是程序,我们可以同意在大多数情况下,程序不需要拥有另一个大小和复杂性相似的程序的副本(即,一个对象不需要指向另一个对象)。它可能需要更小的程序来管理数据(比如数据结构),但在我的看法中不需要另一个对象。
因为耦合对象会使它们相互依赖,所以为什么这是不好的呢?因为当对象独立时,你可以测试它们,并向其他程序员和客户承诺该对象(一个小的独立程序)能够以高确定性执行某些任务。只要没有对该对象进行更改,你也可以确保它继续执行。
那么什么是槽和信号呢?如果你理解对象就像程序一样,它们理想情况下不应该持有其他对象的副本或指针,那么你需要一些方法让它们进行通信。例如,运行在计算机上的进程可以使用套接字、IP 地址和端口进行通信。对象可以使用类似于RPC的信号和槽。这些是数据结构,旨在作为两个存储对象子程序()的较大对象之间的中介,允许其他对象调用(信号)这些子例程()并传递适当的参数,而无需了解这些其他对象除了需要哪些参数之外的任何信息。
因此,底层结构是一组(可能是数组)强类型过程指针,其他对象可以使用适当的参数调用这些指针而无需指向这些对象的指针。调用者只需要访问信号对象即可(不包含实现细节),该对象定义了预期的参数。
这也很灵活,因为它允许一些特殊用例,例如仅响应一次信号的插槽,一个信号的多个插槽以及类似去抖动的其他用例。

1

3
请注意,仅包含链接的答案不被鼓励,在寻找解决方案时SO答案应该是终点(而不是另一个参考站点,随时间推移可能会过时)。请考虑在此处添加一个独立的概要,将链接作为参考。 - kleopatra
我认为有链接是可以的。 - VivekDev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接