【问题标题】:How delete and deleteLater works with regards to signals and slots in Qt?关于 Qt 中的信号和插槽,delete 和 deleteLater 是如何工作的?
【发布时间】:2011-06-20 18:22:52
【问题描述】:

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

有时我想摆脱这两个对象。没有更多的信号或来自他们的任何东西。我要他们走。 好吧,我想,我会用

delete obj1; delete obj2;

但我真的可以吗? ~QObject 的规格说:

在挂起的事件等待传递时删除 QObject 可能会导致崩溃。

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

假设我打电话:

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

为了安全。

但是,我真的安全吗? deleteLater 添加了一个事件,当控制到达那里时,该事件将在主循环中处理。是否已经存在obj1obj2 的一些未决事件(信号),等待在主循环中处理 before deleteLater 将被处理?那将是非常不幸的。我不想编写代码检查“有点删除”状态并忽略所有插槽中的传入信号。

【问题讨论】:

  • 看起来obj->disconnect(); obj->deleteLater(); 是正确的选择:
  • 阅读 QObject 源代码后,deleteLater() 似乎只是将QDeferredDeleteEvent 发布到调用deleteLater() 的对象上。当 QObject 接收到该事件时,其事件处理程序最终将调用常规的delete,而后者又调用 QObject 的析构函数。信号断开直到析构函数结束才会发生,因此我猜 QObject 将运行由 DirectConnection 信号调用的槽,这些信号在调用 deleteLater() 之后但在事件循环返回之前发出。

标签: qt qt-signals slot


【解决方案1】:

如果您遵循两个基本规则,删除 QObjects 通常是安全的(即在正常实践中;可能存在我不知道的病理情况):

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

  • 同样,永远不要在从对象的事件处理程序同步调用的代码中删除对象。例如。不要在 SomeWidget::fooEvent() 或从那里调用的方法/插槽中删除 SomeWidget。事件系统将继续在已删除的对象上运行 -> 崩溃。

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

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

【讨论】:

  • 这种崩溃的一个例子是:从一些小部件的 focusOut 事件中,我删除了一些子小部件。通过单击要删除的小部件之一触发焦点移出。在此示例中,删除是不安全的,因为当到达事件循环时,对象已经消失并在尝试向该小部件传递单击事件时导致崩溃。 deleteLater 是安全的,因为该对象被标记为删除,并且事件循环知道不应传递此事件,因为该对象已被删除
【解决方案2】:

您引用的文档的下两行说明了答案。

来自~QObject

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

它特别告诉我们不要从其他线程中删除。由于您有一个单线程应用程序,因此删除QObject 是安全的。

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

【讨论】:

  • 我的第二种情况呢?对象中的槽在我调用 deleteLater 后仍然可以调用吗?
  • @stach,似乎这值得一个单独的问题。在 QtForum 上更好。
【解决方案3】:

您可以阅读有关Delta Object Rules 之一的问题的答案,其中指出:

信号安全 (SS)。
必须是安全的 在对象上调用方法,包括 析构函数,来自槽内 被其中一个信号调用。

片段:

QObject 的核心是支持被 发信号时删除。为了 利用它,你只需要 确保您的对象不会尝试 之后访问它自己的任何成员 被删除。然而,大多数 Qt 对象不是这样写的,并且 没有要求他们是 任何一个。为此,它是 建议你总是打电话 deleteLater() 如果你需要删除一个 物体在其信号之一期间, 因为很有可能“删除”会 让应用程序崩溃。

不幸的是,这并不总是很清楚 什么时候应该使用“删除” vs 删除后()。也就是说,它不是 总是很明显,代码路径有 信号源。通常,您可能有一个 使用“删除”的代码块 一些今天安全的物体,但是 在未来的某个时候,同样的 代码块最终被调用 从一个信号源,现在突然 您的应用程序正在崩溃。唯一的 这个问题的一般解决方案是 一直使用 deleteLater(),甚至 如果乍一看似乎没有必要。

一般来说,我认为Delta Object Rules 是每个Qt 开发人员的必读内容。这是很好的阅读材料。

【讨论】:

  • 如果您点击 DOR 的链接,您必须点击该页面上的链接以进一步阅读,例如点击链接到“信号安全”。如果没有上下文,链接的第一页很难理解。 (我在 Windows 上使用 PyQt 在退出时追逐崩溃,我的应用程序甚至没有删除任何对象,但我希望 DOR 的链接能够提供见解。)
【解决方案4】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多