何时使用deleteLater

20

假设我有以下代码片段,在qto的析构函数中调用deleteLater对于它可能管理的其他QT对象是否安全?

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

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

因为我使用泄漏检测器分析了类似的代码,并且所有被调用deleteLater的对象,除非我将其替换为普通的delete,在内存中都没有正确地释放。 如果我理解得正确,deleteLater只是在QT消息队列中注册一个删除事件。这可能是问题所在吗?因为qto的析构函数是在main作用域结束时被调用,而QT消息循环已经随着从a.exec返回而结束。因此,删除事件将永远不会被处理,事实上,甚至没有被推入消息队列,因为根本就没有队列存在?


1
你不需要在 qto 上使用 delete 操作符,因为它已经存在于栈中。顺便提一下,分析器可能会报告错误的结果。 - UmNyobe
2
@UmNyobe,该问题询问是否安全调用deleteLater...来管理其他QT对象,而不是qto本身。 - TheDarkKnight
这很容易测试。只需在析构函数中放置一些调试消息,看看它们是否显示出来。您会注意到它们不会显示。这些对象为什么不是qto的子级? - thuga
5个回答

20

这篇文章有些过时,但我想添加我的答案,因为当我自己在问这个问题时,我很希望能找到这个答案。

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


1
请问您能否评论一下为什么这种方式与使用静态方法 QTimer::singleShot(0, this, longComputation) 不同呢? - guitarpicva
@guitarpicva 在这个简单的例子中没有区别。deleteLater()是一个相当低级的特性,你经常可以找到更高级别的构造来完成工作。 - Emerald Weapon
@guitarpicva 在某些假设用例中,您可能更喜欢手动实现单次触发器,其中您想先设置计时器,然后再稍后启动它。(我已经有一段时间没有使用Qt了,我不记得具体的用例,但我记得在我的Qt日子里遇到过一两个这样的情况) - Emerald Weapon
我认为singleShot可以解决这种情况,因为它是静态的,并且您可以在调用中提供等待时间。我只是好奇。您的实现看起来很像Qt文档对singleShot的解释。我经常使用QTimer::singleShot来处理串行和套接字连接的硬件设备。在发送另一个命令之前,通常必须等待它们“稳定”。谢谢。 - guitarpicva
1
如果在计时器超时之前删除了 this,那么这不会导致崩溃吗?除非我们将 this 添加为 connect(...) 的目标,否则会发生崩溃吗?然后 QTimer 不就会泄漏吗? - Ben

14

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

因此,deleteLater通过在事件循环中放置一条消息来请求删除对象,在从槽返回并且可以安全删除时处理该消息。

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


7
这个问题很旧了,但我会把它留给未来的一代)被标记为答案的回复是正确的,但表述奇怪。实际上你的问题包含了一个正确的答案: 消息循环已经以从a.exec返回的方式结束了吗?因此删除事件将永远不会被处理,实际上甚至没有被推入消息队列,因为没有消息队列。这正是正在发生的事情。deleteLater()所做的一切都只是将删除事件发布到外部事件循环中。当事件被处理时,对象就会被删除。但是,如果没有外部事件循环,并且在执行流中后面也没有遇到事件循环,那么事件将永远不会被发布,因此对象永远不会被删除。
如果您在对象的析构函数中调用deleteLater()并将对象放在堆栈上,则在对象超出范围时将调用deleteLater()。在您的示例中,“超出范围”是在遇到main()函数的闭合括号时发生的。然而,在那个时候,a.exec()(代表Qt应用程序的主事件循环)已经返回-->没有事件循环了-->已经调用deleteLater(),但无处发布删除事件-->应该被“稍后删除”的对象永远不会被删除...
关于“何时使用deleteLater()”部分: Kuba Ober回答道:

一般来说,只有在非常特殊的情况下才应该使用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);

最后,对于非子对象,在析构函数中使用 deleteLater() 不是错误,而不是错误。

3
您说得对,deleteLater() 命令只能由事件循环执行。根据 Qt documentation 中的说明,QObject 对象如下:
调度此对象进行删除。
当控件返回到事件循环时,对象将被删除。如果在调用此函数时事件循环未运行(例如,在 QCoreApplication::exec() 之前在对象上调用 deleteLater()),则一旦开始事件循环,该对象将被删除。如果在主事件循环停止后调用 deleteLater(),则对象不会被删除。自 Qt 4.8 以来,如果在没有运行事件循环的线程中调用了 deleteLater(),则对象将在该线程完成时被销毁。
请注意,进入和离开新的事件循环(例如通过打开模态对话框)不会执行延迟删除;为了删除对象,控件必须从调用 deleteLater() 的事件循环返回。 注意:多次调用此函数是安全的;当传递第一个延迟删除事件时,将从事件队列中删除任何待处理的对象事件。
如果您希望在删除 qto 时删除所有子 QObjects,请确保使用 qto 作为其父级创建它们。

2

通常情况下,只有在特定的场景中才应该使用deleteLater函数。大多数情况下,你不需要使用该函数。

在非子对象的QObject析构函数中使用它是错误的。正如你所发现的那样,在没有事件循环的情况下QObjects可能会被销毁。例如,在qtbase Qt模块的对象析构函数中没有任何deleteLater调用。

在这里必须小心:例如,~ QTcpServer() 调用 close(),然后调用 d->socketEngine->deleteLater(),但是socket引擎已经是服务器的子对象,并且将被~QObject()删除。

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

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

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