关于Qt中的信号和插槽,delete和deleteLater如何工作?


76

有一个QNetworkReply类的对象。有一个插槽(在某些其他对象中)连接至其finish()信号。信号是同步的(默认信号)。只有一个线程。

在某个时刻,我想摆脱两个对象。没有更多信号或来自它们的任何东西。我希望他们走了。好吧,我想,我会用

delete obj1; delete obj2;

但是我真的可以吗?〜QObject的规范说:

在等待事件发送时删除QObject可能导致崩溃。

什么是“待处理事件”?这是否意味着在我打电话给我时delete,已经有一些“待处理事件”要传递,并且它们可能会导致崩溃,而我真的无法检查是否有任何事件?

假设我打电话给:

obj1->deleteLater(); obj2->deleteLater();

为了安全。

但是,我真的很安全吗?该deleteLater补充说,将在主回路控制时到达那里进行处理的事件。是否可以存在obj1obj2已经存在一些未决事件(信号),在处理deleteLater之前在主循环中等待处理?那将是非常不幸的。我不想编写代码来检查“某些已删除”状态,而忽略所有插槽中的传入信号。


4
看起来 obj->disconnect(); obj->deleteLater();是正确的方法:
2011年

1
阅读了QObject源代码之后,似乎deleteLater()只是将a发布QDeferredDeleteEvent到了deleteLater()被调用的对象上。当QObject接收到该事件时,其事件处理程序最终将调用常规delete,后者又将调用QObject的析构函数。直到析构函数的末尾才发生信号断开,因此我猜想QObject将运行插槽,这些插槽由DirectConnection信号调用,该DirectConnection信号在调用之后deleteLater()但在事件循环返回之前发出。
Kasheen '18年

Answers:


73

如果遵循以下两个基本规则,则删除QObject通常是安全的(即,在通常的实践中;可能是我不了解atm的病理情况):

  • 切勿删除插槽或方法中的对象,该插槽或方法被(要删除的对象)(同步,连接类型“直接”)信号直接或间接调用。例如,如果您有一个带有信号Operation :: finished()和插槽Manager :: operationFinished()的类Operation,则不想删除在该插槽中发出信号的操作对象。发出finished()信号的方法可能在发出之后继续访问“ this”(例如访问成员),然后对无效的“ this”指针进行操作。

  • 同样,切勿删除从对象的事件处理程序中同步调用的代码中的对象。例如,请勿在其SomeWidget :: fooEvent()或从此处调用的方法/插槽中删除SomeWidget。事件系统将继续对已删除的对象->崩溃进行操作。

由于回溯通常看起来很奇怪(就像访问POD成员变量时崩溃一样),尤其是当您具有复杂的信号/插槽链(其中删除可能会发生几步下降,最初是由信号或事件发起的)时,两者都很难跟踪。被删除的对象。

这种情况是deleteLater()的最常见用例。它确保当前事件可以在控件返回到事件循环之前完成,事件循环然后删除该对象。我发现,另一个更好的方法通常是使用排队的连接/ QMetaObject :: invokeMethod(...,Qt :: QueuedConnection)延迟整个操作。


1
发生此崩溃的一个示例是:从某些小部件的focusOut事件中,我删除了一些子级小部件。通过单击要删除的小部件之一触发聚焦。在此示例中,删除是不安全的,因为到达事件循环时,对象已经消失,并且在尝试向该小部件传递click事件时会导致崩溃。deleteLater是安全的,因为该对象已标记为删除,并且事件循环知道该对象已被删除,因此不应传递此事件
AKludges

21

您推荐的文档的后两行给出了答案。

〜QObject

在等待事件发送时删除QObject可能导致崩溃。如果QObject与当前正在执行的线程不在同一线程中,则不能直接删除它。使用deleteLater()代替,这将导致事件循环在所有未决事件传递到对象后删除该对象。

它专门说我们不要从其他线程中删除。由于您只有一个线程应用程序,因此删除是安全的QObject

否则,如果您必须在多线程环境deleteLater()中将其删除QObject,则在处理完所有事件后,使用它将删除您的内容。


6
那我的第二种情况呢?我在对象上调用deleteLater后,仍可以调用该对象中的插槽吗?
stach 2011年

15

您可以阅读有关以下内容的Delta对象规则之一找到问题的答案:

信号安全(SS)。
从一个对象的信号之一调用的插槽中调用对象(包括析构函数)上的方法必须是安全的。

分段:

从本质上讲,QObject支持在发信号时删除。为了利用它,您只需确保您的对象在删除后不尝试访问其自己的任何成员。但是,大多数Qt对象不是以这种方式编写的,也不要求它们两者都存在。因此,如果需要在对象的信号之一期间删除它,建议始终调用deleteLater(),因为很有可能“删除”只会使应用程序崩溃。

不幸的是,何时使用“ delete”和deleteLater()并不总是很清楚。也就是说,代码路径不一定具有信号源。通常,您可能会有一段代码在今天安全的某些对象上使用“删除”,但是在将来的某个时候,同一代码块最终会从信号源中被调用,现在您的应用程序突然崩溃了。解决此问题的唯一通用方法是始终使用deleteLater(),即使乍一看似乎没有必要。

通常,我认为Delta对象规则是每个Qt开发人员必须阅读的内容。这是优秀的阅读材料。


1
如果您遵循DOR的链接,则必须遵循该页面上的链接以进行进一步阅读,例如,遵循“ Signal Safe”的链接。没有上下文,很难理解链接的第一页。(我正在Windows上使用PyQt追赶退出时的崩溃,我的应用程序甚至没有删除任何对象,但我希望DOR的链接能够提供见解。)
bootchk 2014年

3

据我所知,如果对象存在于不同的线程中,这主要是一个问题。也许在您实际上正在处理信号时。

否则,删除QObject将首先断开所有信号和插槽并删除所有未决事件。就像对connect()的调用一样。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.