Qt deleteLater作用及源码分析

个人经验总结,如有错误或遗漏,欢迎各位大佬指正

在本篇文章中,我们将深入分析源码,探讨deleteLater的原理。

deleteLater是Qt框架提供的一个重要函数,用于在事件循环中延迟删除对象。

在软件开发中,延迟删除对象的概念是非常重要的,特别是当对象不再被需要,但由于某些原因(比如对象正在被访问、事件循环中仍有关于该对象的事件等),不能立即删除。deleteLater函数提供了一种机制,可以将对象的删除操作延迟执行,从而避免了可能的悬空指针和资源泄漏问题。

在本文中,我们将深入研究源码,揭示deleteLater函数的工作原理。我们将分析QCoreApplicationpostEvent函数以及QObjectevent函数,并解释它们如何协同工作来实现延迟删除的效果。

通过深入分析源码并理解deleteLater的内部工作原理,我们将更好地了解它在Qt框架中的作用和优势。

deleteLater的作用

根据官方文档中的解释:

Schedules this object for deletion.

The object will be deleted when control returns to the event loop. If the event loop is not running when this function is called (e.g. deleteLater() is called on an object before QCoreApplication::exec()), the object will be deleted once the event loop is started. If deleteLater() is called after the main event loop has stopped, the object will not be deleted. Since Qt 4.8, if deleteLater() is called on an object that lives in a thread with no running event loop, the object will be destroyed when the thread finishes.

Note that entering and leaving a new event loop (e.g., by opening a modal dialog) will not perform the deferred deletion; for the object to be deleted, the control must return to the event loop from which deleteLater() was called. This does not apply to objects deleted while a previous, nested event loop was still running: the Qt event loop will delete those objects as soon as the new nested event loop starts.

计划删除一个对象。

当控制返回到事件循环时,对象将被删除。如果在调用此函数时事件循环没有运行(例如,在QCoreApplication::exec()之前在对象上调用deleteLater()),则一旦事件循环启动,该对象将被删除。如果在主事件循环停止后调用deleteLater(),则不会删除对象。自Qt 4.8以来,如果deleteLater()在没有运行事件循环的线程中的对象上调用,则该对象将在线程结束时被销毁。

注意,进入和离开一个新的事件循环(例如,通过打开一个模态对话框)不会执行延迟删除;对于要删除的对象,必须返回到从中调用deleteLater()的事件循环。这不适用于在以前的嵌套事件循环仍在运行时删除的对象:Qt事件循环将在新的嵌套事件循环开始时删除这些对象。

‍♂️那为什么需要把这样一个删除动作丢到事件循环去呢?

因为也许在执行删除某个QObject对象的操作时,事件队列中仍然有关于这个QObject对象的事件,如果直接删除该对象,就会导致后面调用这个对象时,引起程序的崩溃。也就是解决悬空指针的问题。

源码分析

首先,我们先看deleteLater做了些什么操作:

void QObject::deleteLater()
{
QCoreApplication::postEvent(this, new QDeferredDeleteEvent());
}

可以看到,deleteLater函数中,仅仅只是往事件队列中添加了一个DeferedDelete事件。

其次,我们再看QObject的事件处理函数——event

bool QObject::event(QEvent *e)
{
switch (e->type()) {
// ... case QEvent::DeferredDelete:
qDeleteInEventHandler(this);
break; // ...
}
void qDeleteInEventHandler(QObject *o)
{
delete o;
}

从上面,我们可以看到,deleteLater发出来的事件,最后处理的时候,就是简简单单的调用delete进行析构。也就能印证deleteLater的作用:

计划删除一个对象。当控制返回到事件循环时,对象将被删除

但作为一位技术人,看到官方文档中,对这个函数有一段这么长的描述,总觉得事情是不是有点太简单了,难道是我打开的方式不对?抱着这个想法,我又看了一遍源码,也算是找到了一些另外的处理。

再探源码

这次,我从deleteLater中发送事件(postEvent)下手,看是不是这个里面有着特殊处理:

void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
{
// ... if (event->type() == QEvent::DeferredDelete && data == QThreadData::current()) {
// remember the current running eventloop for DeferredDelete
// events posted in the receiver's thread. // Events sent by non-Qt event handlers (such as glib) may not
// have the scopeLevel set correctly. The scope level makes sure that
// code like this:
// foo->deleteLater();
// qApp->processEvents(); // without passing QEvent::DeferredDelete
// will not cause "foo" to be deleted before returning to the event loop. // If the scope level is 0 while loopLevel != 0, we are called from a
// non-conformant code path, and our best guess is that the scope level
// should be 1. (Loop level 0 is special: it means that no event loops
// are running.)
int loopLevel = data->loopLevel;
int scopeLevel = data->scopeLevel;
if (scopeLevel == 0 && loopLevel != 0)
scopeLevel = 1;
static_cast<QDeferredDeleteEvent *>(event)->level = loopLevel + scopeLevel;
}
// ...
}

在这里面,把这个DeferedDelete事件中的循环级别设置了一下,以便待会进行事件发送的时候,能够进行当前事件是否要处理的判断。

void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type,
QThreadData *data)
{
// ... if (pe.event->type() == QEvent::DeferredDelete) {
// DeferredDelete events are sent either
// 1) when the event loop that posted the event has returned; or
// 2) if explicitly requested (with QEvent::DeferredDelete) for
// events posted by the current event loop; or
// 3) if the event was posted before the outermost event loop. int eventLevel = static_cast<QDeferredDeleteEvent *>(pe.event)->loopLevel();
int loopLevel = data->loopLevel + data->scopeLevel;
const bool allowDeferredDelete =
(eventLevel > loopLevel
|| (!eventLevel && loopLevel > 0)
|| (event_type == QEvent::DeferredDelete
&& eventLevel == loopLevel));
if (!allowDeferredDelete) {
// cannot send deferred delete
if (!event_type && !receiver) {
// we must copy it first; we want to re-post the event
// with the event pointer intact, but we can't delay
// nulling the event ptr until after re-posting, as
// addEvent may invalidate pe.
QPostEvent pe_copy = pe; // null out the event so if sendPostedEvents recurses, it
// will ignore this one, as it's been re-posted.
const_cast<QPostEvent &>(pe).event = nullptr; // re-post the copied event so it isn't lost
data->postEventList.addEvent(pe_copy);
}
continue;
}
} // ...
}

上面代码中,我们可以看到判断当前是否允许处理DeferredDelete事件时,有三个条件,也就是上面注释所说的:

  1. 当发布事件的事件循环返回时
  2. 事件在最外层的事件循环之前调用
  3. 显示的调用QEvent::DeferredDelete事件
const bool allowDeferredDelete =
(eventLevel > loopLevel
|| (!eventLevel && loopLevel > 0)
|| (event_type == QEvent::DeferredDelete
&& eventLevel == loopLevel));

如果DeferredDelete不能在当前的循环中被处理,那么将事件重新封装,重新添加到线程的事件列表中,等待能够处理该事件的事件循环去处理。

使用提示

通过上面的分析,调用deleteLater会延迟删除一个QObject对象,并最终妥善的删除。但是,在我们使用的过程中,仍然是要注意,将原指针置nullptr的,不然就会导致野指针了。

那么,我们应该在什么时候去进行置空处理呢

Qt官方文档中有做解释:

[signal] void QObject::destroyed(QObject *obj = nullptr)

This signal is emitted immediately before the object obj is destroyed, after any instances of QPointer have been notified, and cannot be blocked.

All the objects's children are destroyed immediately after this signal is emitted

在一个QObject对象析构时,会发射一个信号——destroyed。使用方法如下:

QObject* obj = new QObject();

connect(obj, &QObject::destroyed, [&] () { obj = nullptr; });

obj->deleteLater();

总结

  1. deleteLater为Qt提供了一个方便且安全的方式来延迟删除对象,避免了悬挂指针和资源泄漏的问题。它提供了一种简化代码的机制,在特定情况下非常有用。
  2. 相较于手动删除和直接调用delete,deleteLater为延迟删除对象提供了一种更加简介和安全的方式。手动删除需要开发人员自己管理对象的生命周期,而deleteLater则通过将删除请求加入到事件队列,实现了自动化的延迟删除。
  3. deleteLater适用于需要延迟删除对象的场景,特别是在槽函数中需要删除与信号关联的对象时。

创作不易,如果本篇博客对您有帮助,麻烦点赞、收藏、关注支持一下

Qt源码阅读(五)-deleteLater的更多相关文章

  1. Qt源码阅读(三) 对象树管理

    对象树管理 个人经验总结,如有错误或遗漏,欢迎各位大佬指正 @ 目录 对象树管理 设置父对象的作用 设置父对象(setParent) 完整源码 片段分析 对象的删除 夹带私货时间 设置父对象的作用 众 ...

  2. Qt源码阅读(四) 事件循环

    事件系统 文章为本人理解,如有理解不到位之处,烦请各位指正. @ 目录 事件系统 什么是事件循环? 事件是如何产生的? sendEvent postEvent 事件是如何处理的? 事件循环是怎么遍历的 ...

  3. Spark数据传输及ShuffleClient(源码阅读五)

    我们都知道Spark的每个task运行在不同的服务器节点上,map输出的结果直接存储到map任务所在服务器的存储体系中,reduce任务有可能不在同一台机器上运行,所以需要远程将多个map任务的中间结 ...

  4. JDK源码阅读(五)java.io.Serializable接口

    package java.io; public interface Serializable { } (1)实现Serializable接口的类,将会被提示提供一个 serialVersionUID ...

  5. QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数

    QT源码解析(一) QT创建窗口程序.消息循环和WinMain函数 分类: QT2009-10-28 13:33 17695人阅读 评论(13) 收藏 举报 qtapplicationwindowse ...

  6. Struts2源码阅读(一)_Struts2框架流程概述

    1. Struts2架构图  当外部的httpservletrequest到来时 ,初始到了servlet容器(所以虽然Servlet和Action是解耦合的,但是Action依旧能够通过httpse ...

  7. 【原】AFNetworking源码阅读(五)

    [原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...

  8. 【原】SDWebImage源码阅读(五)

    [原]SDWebImage源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 前面的代码并没有特意去讲SDWebImage的缓存机制,主要是想单独开一章节专门讲 ...

  9. 37 网络相关函数(五)——live555源码阅读(四)网络

    37 网络相关函数(五)——live555源码阅读(四)网络 37 网络相关函数(五)——live555源码阅读(四)网络 简介 10)MAKE_SOCKADDR_IN构建sockaddr_in结构体 ...

  10. Redis源码阅读(五)集群-故障迁移(上)

    Redis源码阅读(五)集群-故障迁移(上) 故障迁移是集群非常重要的功能:直白的说就是在集群中部分节点失效时,能将失效节点负责的键值对迁移到其他节点上,从而保证整个集群系统在部分节点失效后没有丢失数 ...

随机推荐

  1. kali linux 基本渗透测试流程

    渗透测试流程 1. 信息收集阶段 网络拓扑结构分析 使用nmap扫描目标网络,获取目标主机IP地址和开放端口信息 使用whois查询目标域名的注册信息和DNS服务器信息 使用nslookup查询目标域 ...

  2. 基于SqlSugar的开发框架循序渐进介绍(27)-- 基于MongoDB的数据库操作整合

    SqlSugar的开发框架本身主要是基于常规关系型数据库设计的框架,支持多种数据库类型的接入,如SqlServer.MySQL.Oracle.PostgreSQL.SQLite等数据库,非关系型数据库 ...

  3. Vuex刷新页面数据会丢失吗?咋解决的?

    1.问题描述:页面刷新的时候vuex里的数据会重新初始化,导致数据丢失.因为vuex里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,vuex里面的数据就会被重新赋值. 2.解决思 ...

  4. Linux进程管理(命令)入门

    进程是一个运行中的程序 进程查看 ps 能够查看当前终端下运行的进程 $ ps PID TTY TIME CMD 26305 pts/0 00:00:00 bash 26312 pts/0 00:00 ...

  5. Go语言网络编程: 模拟实现DNS服务器

    环境: 两台虚拟机,不限系统 写在前面 DNS服务器是干什么的?DNS服务器(Domain Name Server,域名服务器)是进行域名和与之相对应的IP地址进行转换的服务器,保存了一张域名和与之相 ...

  6. 【Vue2.x源码系列06】计算属性computed原理

    上一章 Vue2异步更新和nextTick原理,我们介绍了 JavaScript 执行机制是什么?nextTick源码是如何实现的?以及Vue是如何异步更新渲染的? 本章目标 计算属性是如何实现的? ...

  7. Django框架简单搭建增删改查页面 Django请求生命周期流程图

    目录 Django框架简单搭建增删改查页面 一.前期的配置文件以及连接MySQL的基本准备 二.在数据库中准备好数据 三.将MySQL的数据展示到页面(简单认识HTML模板语法 for循环) 在Dja ...

  8. [C++核心编程] 4.7 多态

    文章目录 4.7 多态 4.7.1 多态的基本概念 4.7.2 多态案例一-计算器类 4.7.3 纯虚函数和抽象类 4.7.4 多态案例二-制作饮品 4.7.5 虚析构和纯虚析构 4.7.6 多态案例 ...

  9. Hardhat 开发框架 - Solidity开发教程连载

    Decert.me 要连载教程了, <Solidity 开发教程> 力求系统.深入的介绍 Solidity 开发, 同时这是一套交互式教程,你可以实时的修改教程里的合约代码并运行. 本教程 ...

  10. 2022-04-22:给你两个正整数数组 nums 和 target ,两个数组长度相等。 在一次操作中,你可以选择两个 不同 的下标 i 和 j , 其中 0 <= i, j < nums.leng

    2022-04-22:给你两个正整数数组 nums 和 target ,两个数组长度相等. 在一次操作中,你可以选择两个 不同 的下标 i 和 j , 其中 0 <= i, j < num ...