乐于分享
好东西不私藏

Qt deleteLater 作用及源码分析

Qt deleteLater 作用及源码分析

在 Qt 开发中,内存管理和对象生命周期一直是需要谨慎对待的话题。deleteLater() 是 Qt 提供的一个非常重要的函数,它允许我们安全地延迟删除一个 QObject 对象。本文将结合官方文档和 Qt 源码(以 Qt 5.15 为例),深入剖析 deleteLater() 的工作原理,并分享一些实际使用中的经验。

1. deleteLater() 的作用

根据 Qt 官方文档的描述:

简单概括:

  • 延迟删除:对象不会立即被销毁,而是等到事件循环(event loop)再次回到执行状态时才会被删除。
  • 事件循环未启动:如果在调用 exec() 之前调用了 deleteLater(),则对象会在事件循环启动时被删除。
  • 主事件循环已停止:调用 deleteLater() 后对象将不会被删除(因为已经没有事件循环来处理延迟删除事件)。
  • 无事件循环的线程:自 Qt 4.8 起,如果对象所属的线程没有运行事件循环,则对象会在线程结束时被销毁。
  • 嵌套事件循环:打开模态对话框等操作会进入一个新的事件循环,但并不会导致已调度的删除立即执行;必须返回到最初调用 deleteLater() 的那个事件循环,删除才会发生。不过,如果一个对象在旧的嵌套事件循环中已被调度删除,当新的事件循环启动时,这些对象会被立即删除。

2. 为什么需要 deleteLater

最直接的原因是避免“悬空指针”导致的崩溃。考虑以下场景:

  • 一个对象正在处理某个事件(如定时器超时、网络数据到达)。
  • 在事件处理过程中,我们希望删除这个对象(例如,对话框关闭后自动销毁)。
  • 如果直接调用 delete,那么对象就被立即销毁了。但该对象可能还在事件队列中留有未处理的事件(例如,刚发出的信号触发的槽函数尚未执行),或者当前正在执行的函数后续还会访问该对象的成员。这将导致未定义行为甚至程序崩溃。

deleteLater() 巧妙地解决了这个问题:它将删除操作推迟到当前事件处理结束之后,事件循环下次获取事件之前。这样,所有待处理的事件都可以安全完成,对象才被真正销毁。

3. 源码分析

3.1 deleteLater() 的实现

deleteLater() 是 QObject 的公有成员函数,其定义位于 qobject.cpp 中:voidQObject::deleteLater(){    QCoreApplication::postEvent(thisnew QDeferredDeleteEvent());}

代码非常简洁:它通过 QCoreApplication::postEvent() 向对象自身发送一个 QDeferredDeleteEvent 事件。这个事件是 Qt 内部定义的,继承自 QEvent,其类型为 QEvent::DeferredDelete

3.2 事件的投递

postEvent() 会将事件添加到接收对象所在线程的事件队列中。关键点在于:事件是“投递”而不是“发送”,这意味着它会进入队列,等待事件循环的处理。即使当前没有事件循环,事件也会被保存下来,直到事件循环启动。

3.3 事件的处理

当事件循环(QEventLoop)获得控制权并处理事件时,会通过 QCoreApplication::notify() 最终调用到 QObject::event() 函数。QObject::event() 中对 QEvent::DeferredDelete 的处理如下(简化自 Qt 5.15 源码):boolQObject::event(QEvent *e){switch (e->type()) {// ...case QEvent::DeferredDelete:// 如果对象正处于“发送事件”的状态(比如正在处理另一个事件的槽函数中),// 我们不能立即删除,否则可能会破坏堆栈状态。此时需要再次投递一个 DeferredDelete 事件。if (d->isSender) {            QCoreApplication::postEvent(thisnew QEvent(QEvent::DeferredDelete));returntrue;        }// 如果已经有一个待处理的 DeferredDelete 事件,则执行真正的删除。if (d->postedDeferredDelete) {            d->postedDeferredDelete = false;deletethis;      // 注意:这里直接 delete 对象本身!        }returntrue;// ...    }returnfalse;}

这段代码有几个要点:

  • d->isSender 是一个内部标志,指示对象是否正处于信号发送的过程中(即某个槽函数正在执行)。如果在信号发送过程中收到 DeferredDelete 事件,直接删除对象可能会导致正在执行的槽函数访问非法内存,所以 Qt 会再次投递一个相同的事件,延迟到更安全的时机。
  • d->postedDeferredDelete 标志用来防止重复删除。当第一次收到 DeferredDelete 事件时,该标志被设置,然后再次投递一个新的事件(注意此时事件类型是普通的 QEvent::DeferredDelete 而不是 QDeferredDeleteEvent?实际上 QDeferredDeleteEvent 会在构造时记录当前的循环层级等信息,但最终的删除逻辑仍是这个)。
  • 真正的删除动作是 delete this,它调用对象的析构函数,释放内存。

3.4 线程与事件循环的细节

3.4.1 有事件循环的线程

当对象的线程运行着事件循环(即调用了 exec())时,deleteLater() 投递的事件会在下一次循环迭代中被处理,对象被安全删除。

3.4.2 没有事件循环的线程

如果对象所在线程没有启动事件循环(例如,继承 QThread 但未调用 exec(),或者对象是在工作线程中创建但线程只执行一次性的工作),按照常规逻辑,DeferredDelete 事件将永远不会被处理。但自 Qt 4.8 起,Qt 增加了对这种情况的支持:当线程结束时,Qt 会遍历该线程中所有尚未删除的、有 DeferredDelete 事件待处理的对象,并销毁它们。这是通过 QThreadPrivate::finish() 中的清理代码实现的。

3.4.3 嵌套事件循环

嵌套事件循环(如打开模态对话框、调用 QEventLoop::exec())会导致一个新的局部事件循环。Qt 保证:在一个嵌套事件循环启动时,会先处理所有外部事件循环中已投递的 DeferredDelete 事件。这样,对象不会因为进入模态对话框而迟迟无法删除。这也是文档中提到的“新的嵌套事件循环开始时删除这些对象”。

3.5 QDeferredDeleteEvent 的作用

QDeferredDeleteEvent 是 QEvent 的子类,它携带了额外的信息——事件投递时的事件循环层级(loopLevel)。在嵌套事件循环的场景下,Qt 需要知道哪些事件属于当前层级,哪些属于更外层。当新的事件循环启动时,Qt 可以过滤出所有比当前层级更早(更低层级)的 DeferredDelete 事件并立即处理。这个机制确保了延迟删除的行为符合直觉。

4. 使用经验与注意事项

4.1 何时使用 deleteLater

  • 在对象的槽函数中删除自身:这是最常见的用法,例如 QDialog 的 accept() 或 reject() 中调用 deleteLater(),对话框在关闭后延迟销毁,从而允许其槽函数执行完毕。
  • 跨线程删除对象:如果对象 A 属于线程甲,而线程乙想要删除它,直接 delete A 是不安全的(违反线程所有权)。此时应该通过信号触发 A 的 deleteLater() 槽,或者使用 QMetaObject::invokeMethod(A, "deleteLater", Qt::QueuedConnection)。这样删除操作会在对象所属线程的事件循环中执行,保证了线程安全。
  • 智能指针结合使用:Qt 提供的 QPointer 可以监控对象是否已被删除,与 deleteLater() 结合可以防止野指针。

4.2 注意事项

  • **不要多次调用 deleteLater**:多次调用是安全的(Qt 内部有标志位防止重复投递事件),但应避免不必要的调用,以免增加事件队列负担。
  • 删除后不要再使用对象:即使调用 deleteLater() 后对象指针仍然存在(未被置空),也不能再访问该对象的成员函数或属性,因为它随时可能被销毁。建议配合 QPointer 使用。
  • 没有事件循环的线程:如果线程中没有启动事件循环,deleteLater() 会延迟到线程结束时才执行。但如果线程永远不结束(比如一个常驻的后台线程,却忘记调用 exec()),那么对象将永远不会被删除,造成内存泄漏。因此,在编写长时间运行的工作线程时,要么启动事件循环,要么确保手动删除对象。
  • **在析构函数中调用 deleteLater**:这通常没有意义,因为对象已经在析构中,再次投递删除事件可能导致重复释放。应当避免。
  • **不要混用 delete 和 deleteLater**:对一个对象同时使用 delete 和 deleteLater 是未定义行为。一旦调用了 deleteLater,就应该完全信任 Qt 的机制,不要手动 delete

5. 总结

deleteLater() 是 Qt 提供的一个优雅的对象延迟删除机制。它利用事件循环的特性,将对象的销毁操作延后到安全的时间点,有效避免了悬空指针和资源访问冲突。通过源码分析,我们可以看到其背后依赖于 QCoreApplication::postEvent()QDeferredDeleteEvent 以及 QObject::event() 的协同工作。理解这些内部原理,有助于我们更合理地使用 deleteLater(),写出健壮且高效的 Qt 程序。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Qt deleteLater 作用及源码分析

评论 抢沙发

7 + 2 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮