【问题标题】:When to use deleteLater何时使用 deleteLater
【发布时间】:2014-04-18 01:12:08
【问题描述】:

假设我有以下 sn-p,在 qto 的析构函数中为它可能管理的其他 QT 对象调用 deleteLater 是否安全?

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    MyQTObject qto;
    qto.show();
    return a.exec();
}

因为我已经使用泄漏检测器分析了类似的代码,并且调用了 deleteLater 的所有对象都没有正确释放,除非我用普通删除替换调用。 如果我理解正确的话,deleteLater 只会在 QT 消息队列中注册一个删除事件。这可能是在 main 范围的末尾调用 qto 的析构函数而 QT 消息循环已经以 a.exec 的返回结束的问题吗?因此删除事件将永远不会被处理,实际上甚至不会被推送到消息队列中,因为没有?

【问题讨论】:

  • 您不需要稍后在qto 上删除。它在堆栈上。顺便说一句,分析器可以报告误报。
  • @UmNyobe,问题询问是否可以安全地调用 deleteLater...对于它可能管理的其他 QT 对象,而不是 qto 本身。
  • 这很容易测试。只需将一些调试消息放入析构函数中,看看它们是否出现。你会注意到他们不会。这些对象不是qto 的子对象有什么原因吗?

标签: qt message-queue


【解决方案1】:

据我了解,当您需要从对插槽的调用中删除对象时,最常使用 deleteLater。如果在这种情况下使用 delete 并且从插槽返回时引用了该对象,则会发生对未初始化内存的引用。

因此,deleteLater 请求删除该对象,方法是在事件循环中放置一条消息,该消息在某个时间点进行处理,在从槽返回时可以安全删除。

我希望在析构函数中使用 deleteLater 意味着对象有可能超出范围,在其托管对象上调用 deleteLater,但在事件循环有机会删除对象之前退出,因为从 QApplication 退出: :exec() 将终止事件循环。

【讨论】:

    【解决方案2】:

    deleteLater() 命令仅由事件循环执行是正确的。

    Qt documentationQObject

    计划删除此对象。

    当控制权返回事件时,该对象将被删除 环形。如果此函数运行时事件循环未运行 调用(例如 deleteLater() 之前在对象上调用 QCoreApplication::exec()),对象将被删除一次 事件循环开始。如果在主事件循环之后调用 deleteLater() 已停止,对象不会被删除。 从 Qt 4.8 开始,如果 deleteLater() 被调用在一个对象上 没有运行事件循环的线程,对象将在 线程结束。

    注意进入和离开一个新的事件循环(例如,通过打开一个模式 dialog) 将不会执行延迟删除;对象是 删除后,控件必须返回到事件循环,从中 调用了 deleteLater()。

    注意:多次调用此函数是安全的;当。。。的时候 第一个延迟删除事件被传递,任何挂起的事件 对象从事件队列中移除。

    如果您希望在删除 qto 时删除所有子 QObjects,请确保以 qto 作为父级创建它们。

    【讨论】:

      【解决方案3】:

      一般来说,只有少数情况应该使用 deleteLater。很可能您根本不应该使用它。

      在 QObject 的析构函数中使用它是错误的,对于非子对象。正如您发现的那样,QObjects 很可能在没有事件循环的情况下被破坏。例如,qtbase Qt 模块的对象析构函数中没有 deleteLater 调用。

      这里要小心:例如~QTcpServer()调用close()调用d->socketEngine->deleteLater(),但是socket引擎已经是服务器的子引擎,会被~QObject()删除反正 em>。

      据我所知,MyQTObject 应该执行以下操作之一:

      • 使用像QScopedPointerstd::unique_ptr 这样的智能指针,
      • 将对象作为常规(非指针)成员,
      • 使用原始指针并让对象成为它的子对象。

      【讨论】:

        【解决方案4】:

        这篇文章相当陈旧,但我想添加我自己问这个问题时希望遇到的答案。

        deleteLater() 与异步操作结合起来非常有用。我认为,最近将信号连接到 lambda 函数的可能性尤为突出。

        假设您有一些longComputation() 想要异步执行(不是多线程意义上的,在事件循环中调度执行的意义上)。你可以这样做:

        void MyClass::deferLongComputation()
        {
            QTimer* timer = new QTimer();
        
            connect(timer, 
                    &QTimer::timeout,
                    [this, timer](){this->longComputiation(); timer->deleteLater();});
        
            timer->setSingleShot(true);
            timer->start();
        }
        

        deleteLater() 负责在其职责完成后安全处置QTimer,并避免否则会发生内存泄漏。

        QFutureWatcher 可以在多线程中使用相同的模式。

        【讨论】:

        • 请您评论一下为什么这与使用静态不同:QTimer::singleShot(0, this, longComputation);
        • @guitarpicva 在这个简单的例子中没有区别。 deleteLater() 是一个相当低级的功能,您通常可以找到为您完成这项工作的更高级别的构造。
        • @guitarpicva 在一些假设的用例中,您可能更喜欢手动实现单次射击,您希望先设置计时器,然后再触发它。 (我已经有一段时间没有使用 Qt 并且我不记得具体的用例了,但我记得我在 Qt 的日子里遇到过一两个)
        • 我认为singleShot 可以解决这种情况,因为它是静态的,您可以在通话中提供等待时间。我只是好奇而已。您的实现看起来很像 Qt 文档对 singleShot 功能的解释。我非常广泛地使用QTimer::singleShot 来处理串行和套接字连接的硬件设备。在发送另一个命令之前,必须经常等待它们“安定下来”。谢谢。
        • 如果this在定时器超时之前被删除了怎么办?除非我们将this 作为目标添加到connect(...),否则不会导致崩溃吗?那么QTimer不会泄露吗?
        【解决方案5】:

        这个问题很老了,但我会把这个留给下一代) 被标记为答案的答复是正确的,但措辞很奇怪。 其实你的问题包含一个正确的答案:

        消息循环已经以 a.exec 的返回结束?就这样 删除事件永远不会被处理,实际上甚至不会被推入 消息队列,因为没有。

        这正是正在发生的事情。 deleteLater() 所做的一切只是将删除事件发布到外部事件循环中。当事件被处理时 - 对象被删除。但是如果没有外部事件循环并且在执行流程的后面没有遇到事件循环 - 事件将永远不会被发布,因此对象永远不会被删除。

        如果您在对象的析构函数中调用 deleteLater() 并将对象放入堆栈 - 当对象超出范围时调用 deleteLater()。在您的示例中,当遇到 main() 函数的右括号时,会发生“超出范围”。但是,到那时,a.exec()(代表 Qt App 的主事件循环)已经返回 --> 没有事件循环了 --> @987654327 @ 被调用,但它无处发布删除事件 --> 对象应该是“deletedLater”永远不会被删除...

        关于“何时使用 deleteLater()”部分:

        库巴奥伯回答:

        一般来说,在少数情况下, 应该使用 deleteLater。您很可能根本不应该使用 它...

        别听它,它作为整个答案是绝对不正确的。阅读this article 后,您应该做什么和不应该更好地决定什么。虽然它主要是关于 Qt 线程,但这篇文章也讲述了异步编程(并且,正如 Emerald Weapon 所提到的,它正是 deleteLater() 的用途)。

        此外,智能指针和 QObject 父所有权与 deleteLater() 的删除调度无关。这两种技术实际上都在后台使用了一个简单的delete 操作。正如文章所示以及 Emerald Weapon 的回答所示:delete 不能解决问题 deleteLater() 可以。因此,如果您需要删除对象,请使用delete,如果您需要安排删除对象,请使用deleteLater()

        顺便说一句,如果你想在deleteLater() 中使用智能指针,你可以指定删除器:

        // Shared Pointer
        QSharedPointer<MyObject> obj = 
                QSharedPointer<MyObject>(new MyObject, &QObject::deleteLater);
        // Scoped Pointer
        QScopedPointer<MyObject, QScopedPointerDeleteLater> customPointer(new MyObject);
        

        最后,对于非子对象,在QObject 的析构函数中使用deleteLater() 是一个NOT错误。

        【讨论】:

          猜你喜欢
          • 2012-09-19
          • 2015-07-26
          • 1970-01-01
          • 2017-05-15
          • 1970-01-01
          • 2017-04-21
          • 2023-02-05
          • 2023-04-01
          • 1970-01-01
          相关资源
          最近更新 更多