Qt源码阅读(三) 对象树管理
对象树管理
个人经验总结,如有错误或遗漏,欢迎各位大佬指正 😃
文章目录
- 对象树管理
- 设置父对象的作用
- 设置父对象(setParent)
- 完整源码
- 片段分析
- 对象的删除
- 夹带私货时间
设置父对象的作用
众所周知,Qt中,有为对象设置父对象的方法——setParent。
而设置父对象的作用主要有,在父对象析构的时候,会自动去析构其子对象。如果是一个窗口对象,如果其父对象设置了样式表(Style Sheet),子对象也会继承父对象的样式。
所以,这篇文章,咱们主要看一下setParent的源码以及QObject是怎么进行对象管理的。
设置父对象(setParent)
我们可以看到,setParent这个函数就是调用了QObjectPrivate类的setParent_helper这个函数。
void QObject::setParent(QObject *parent)
{Q_D(QObject);Q_ASSERT(!d->isWidget);d->setParent_helper(parent);
}
所以,我们进一步分析setParent_helper这个函数
完整源码
void QObjectPrivate::setParent_helper(QObject *o)
{Q_Q(QObject);// 不能把自己设为父对象Q_ASSERT_X(q != o, Q_FUNC_INFO, "Cannot parent a QObject to itself");
#ifdef QT_DEBUG// 检查对象树的循环const auto checkForParentChildLoops = qScopeGuard([&](){int depth = 0;auto p = parent;while (p) {if (++depth == CheckForParentChildLoopsWarnDepth) {qWarning("QObject %p (class: '%s', object name: '%s') may have a loop in its parent-child chain; ""this is undefined behavior",q, q->metaObject()->className(), qPrintable(q->objectName()));}p = p->parent();}});
#endif// 如果要设置的父对象就是当前的父对象,直接返回if (o == parent)return;if (parent) {QObjectPrivate *parentD = parent->d_func();if (parentD->isDeletingChildren && wasDeleted&& parentD->currentChildBeingDeleted == q) {// don't do anything since QObjectPrivate::deleteChildren() already// cleared our entry in parentD->children.} else {const int index = parentD->children.indexOf(q);if (index < 0) {// we're probably recursing into setParent() from a ChildRemoved event, don't do anything} else if (parentD->isDeletingChildren) {parentD->children[index] = 0;} else {// 如果对象已经存在父对象的列表中,将原先存在的对象删除,并发送事件parentD->children.removeAt(index);if (sendChildEvents && parentD->receiveChildEvents) {QChildEvent e(QEvent::ChildRemoved, q);QCoreApplication::sendEvent(parent, &e);}}}}// 设置父对象parent = o;if (parent) {// object hierarchies are constrained to a single threadif (threadData != parent->d_func()->threadData) {qWarning("QObject::setParent: Cannot set parent, new parent is in a different thread");parent = nullptr;return;}// 父对象添加子对象,并发送事件parent->d_func()->children.append(q);if(sendChildEvents && parent->d_func()->receiveChildEvents) {if (!isWidget) {QChildEvent e(QEvent::ChildAdded, q);QCoreApplication::sendEvent(parent, &e);}}}if (!wasDeleted && !isDeletingChildren && declarativeData && QAbstractDeclarativeData::parentChanged)QAbstractDeclarativeData::parentChanged(declarativeData, q, o);
}
片段分析
-
一些先决条件的判断
-
判断设置的父对象是否是自己
// 不能把自己设为父对象 Q_ASSERT_X(q != o, Q_FUNC_INFO, "Cannot parent a QObject to itself"); /*...*/ // 如果要设置的父对象就是当前的父对象,直接返回 if (o == parent) return; -
判断原来的父对象是否处于正在删除子对象的过程中,并且当前对象已经被删除了,如果是,则什么都不做(有点迷惑)
if (parentD->isDeletingChildren && wasDeleted && parentD->currentChildBeingDeleted == q) {// don't do anything since QObjectPrivate::deleteChildren() //already cleared our entry in parentD->children.} -
判断是不是通过从
ChildRemoved事件递归到setParent()if (index < 0) { // we're probably recursing into setParent() from a ChildRemoved event,// don't do anything } else if (parentD->isDeletingChildren) { parentD->children[index] = 0; } -
判断对象是不是已存在父对象的列表中,如果存在,就将对象删除,并发送事件
else { // 如果对象已经存在父对象的列表中,将原先存在的对象删除,并发送事件 parentD->children.removeAt(index); if (sendChildEvents && parentD->receiveChildEvents) { QChildEvent e(QEvent::ChildRemoved, q);QCoreApplication::sendEvent(parent, &e); } }
-
-
设置父对象,这里有一个限制,就是新设置的父对象,必须和当前对象在同一个线程,否则不能设置。
// 设置父对象parent = o;if (parent) {// object hierarchies are constrained to a single threadif (threadData != parent->d_func()->threadData) {qWarning("QObject::setParent: Cannot set parent, \new parent is in a different thread");parent = nullptr;return;}// 父对象添加子对象,并发送事件parent->d_func()->children.append(q);if(sendChildEvents && parent->d_func()->receiveChildEvents) {if (!isWidget) {QChildEvent e(QEvent::ChildAdded, q);QCoreApplication::sendEvent(parent, &e);}}}
对象的删除
然后就是对象的管理,也就是在父对象析构的时候,自动析构掉所有的子对象。这一个在我们使用窗口部件的时候很有用,因为一个界面可能有很多个子控件,比如按钮、label等,这时候,如果一个小窗口被关闭,我们也不需要一个一个的去析构,由Qt的对象树去进行析构就好了。
QObject::~QObject()
{/*...*/// 删除子对象if (!d->children.isEmpty())d->deleteChildren();#if QT_VERSION < 0x60000qt_removeObject(this);
#endifif (Q_UNLIKELY(qtHookData[QHooks::RemoveQObject]))reinterpret_cast<QHooks::RemoveQObjectCallback>(qtHookData[QHooks::RemoveQObject])(this);Q_TRACE(QObject_dtor, this);if (d->parent) // remove it from parent objectd->setParent_helper(nullptr);
}
将所有的子对象进行删除,遍历容器,按照子对象所加入进来的顺序进行析构。
void QObjectPrivate::deleteChildren()
{// 清空子对象Q_ASSERT_X(!isDeletingChildren, "QObjectPrivate::deleteChildren()", "isDeletingChildren already set, did this function recurse?");isDeletingChildren = true;// delete children objects// don't use qDeleteAll as the destructor of the child might// delete siblingsfor (int i = 0; i < children.count(); ++i) {currentChildBeingDeleted = children.at(i);children[i] = 0;delete currentChildBeingDeleted;}children.clear();currentChildBeingDeleted = nullptr;isDeletingChildren = false;
}
夹带私货时间
在使用Qt的对象树这个功能的时候,可能会遇到一种问题,会导致程序崩溃:就是手动的管理(也就是直接delete)一个有父对象的QObject,为什么会出现这样的情况呢,因为,你在delete子对象之后,并没有把这个对象从父对象的对象树里移除。在父对象进行析构的时候,还是会去遍历子对象容器,一个一个析构。这个时候,就会出现,一个对象指针被删除了两次,自然就会崩溃。
那么,如果非要自己管理这个对象,有什么办法呢?我们从对象树下手,有两种办法:
-
使用
deleteLater就是调用
QObject对象的deleteLater函数,来实现删除。关于deleteLater的分析,可以看这个大佬的文章Qt 中 deleteLater() 函数的使用QObject *object = new QObject(); QObject *m_child = new QObject(object);// 需要手动删除的时候 m_child->deleteLater(); -
先将父对象设置为空,再直接delete
QObject *object = new QObject(); QObject *m_child = new QObject(object);// 需要手动删除的时候 m_child->setParent(nullptr); delete m_child; m_child = nullptr; -
先将父对象设置为空,再直接delete
QObject *object = new QObject(); QObject *m_child = new QObject(object);// 需要手动删除的时候 m_child->setParent(nullptr); delete m_child; m_child = nullptr;
个人建议使用第一种方法,也就是调用deleteLater
相关文章:
Qt源码阅读(三) 对象树管理
对象树管理 个人经验总结,如有错误或遗漏,欢迎各位大佬指正 😃 文章目录对象树管理设置父对象的作用设置父对象(setParent)完整源码片段分析对象的删除夹带私货时间设置父对象的作用 众所周知,Qt中,有为对象设置父对象…...
【Python入门第四十二天】Python丨NumPy 数组裁切
裁切数组 python 中裁切的意思是将元素从一个给定的索引带到另一个给定的索引。 我们像这样传递切片而不是索引:[start:end]。 我们还可以定义步长,如下所示:[start:end:step]。 如果我们不传递 start&…...
Anaconda配置Python新版本tensorflow库(CPU、GPU通用)的方法
本文介绍在Anaconda环境中,下载并配置Python中机器学习、深度学习常用的新版tensorflow库的方法。 在之前的两篇文章基于Python TensorFlow Estimator的深度学习回归与分类代码——DNNRegressor(https://blog.csdn.net/zhebushibiaoshifu/article/detail…...
加载模型时出现 OSError: Unable to load weights from pytorch checkpoint file 报错的解决
加载模型时出现 OSError: Unable to load weights from pytorch checkpoint file 报错的解决报错信息原因查明网传解决措施好消息我的解决措施报错信息 查了下,在网上还是个比较常见的报错 一般为加载某模型时突然报错 原因查明 一般为下载某个 XXX_model.bin 的…...
sessionStorage , localStorage 和cookie的区别
一.sessionStorage(临时存储)sessionStorage是HTML5中新增的Web Storage API之一,用于在浏览器中存储键值对数据,与localStorage类似,但是sessionStorage存储的数据在会话结束时会被清除。可以通过以下方式使用sessionStorage:存储…...
C# 实例详解委托之Func、Action、delegate
委托是.NET编程的精髓之一,在日常编程中经常用到,在C#中实现委托主要有Func、Action、delegate三种方式,这个文章主要就这三种委托的用法通过实例展开讲解。 【Func】:Func是带返回值的委托: 原型函数如下(以下展示的…...
如何选电脑
1、CPU(中央处理器) 怎么看CPU型号:CPU:系列-代数等级核心显卡型号电压后缀 例如CPU:i7-10750H : 1、系列:Intel的酷睿i3、i5、i7、i9这四个系列的CPU,数字越大就代表越高端。 2、代数:代表…...
SpringBoot项目创建
如果使用spring的源地址创建项目失败,就使用 阿里云的springBoot项目创建地址:https://start.aliyun.com/ 1.new 一个新的项目: 2.选择合适的版本java的JDK和maven项目 3.选择spring web依赖 4.直接finish 5. 删除无用的包,然后…...
神经衰弱该如何判断?确诊为神经衰弱,日常要做好这7大护理!
神经衰弱是由于长时间处于紧张或者压力的情况下导致精神出现兴奋或者疲乏现象而伴随着一系列症状。如情绪烦恼、容易激怒、睡眠障碍、肌肉出现紧张性疼痛等,生活中有很多人在自己的不到休息或者遇到强大打击时就会嘲笑自己患上神经衰弱。甚至一些会盲目采取措施&…...
Linux之进程替换
进程替换1.什么是进程替换2.替换函数2.1 execl函数2.2 execv函数2.3 execlp函数2.4 execvp函数2.5 在自己的C程序上如何运行其他语言的程序?2.6 execle 函数2.7 小结3.一个简易的shell1.什么是进程替换 fork()之后,父子各自执行父进程代码的一部分&…...
关于清除浮动
浮动最早是用来做图文排版,为了让块级元素同行显示,而html中块元素是有自己的排列规则,一般独占一行。所以有了浮动元素,一旦元素浮动了就会脱离文档流,产生问题。怎么去清除浮动:(1)…...
Uber H3 index 地图索引思考
H3 是 uber 设计的六边形空间索引,go 语言操作包是 h3-go,可以通过经纬度获取所在的 h3 六边形边界,每个经纬度对应的六边形都是确定的,每个六边形唯一对应了一个 h3index。在业务开发中,我们可以通过 h3index 来对地理…...
多线程的几种状态
Java-多线程的几种状态🔎1.NEW( 系统中线程还未创建,只是有个Thread对象)🔎2.RUNNABLE( (就绪状态. 又可以分成正在工作中和即将开始工作)🔎3.TERMINATED(系统中的线程已经执行完了,Thread对象还在)🔎4.TIMED_WAITING(指定时间等待…...
【算法题】1574. 删除最短的子数组使剩余数组有序
题目: 给你一个整数数组 arr ,请你删除一个子数组(可以为空),使得 arr 中剩下的元素是 非递减 的。 一个子数组指的是原数组中连续的一个子序列。 请你返回满足题目要求的最短子数组的长度。 示例 1: …...
理解对数——金融问题中的自然对数(以e为底的对数)
第3章 金融问题(Financial Matters)——金融问题中的自然对数If thou lend moneyto any ofMy people. ...thou shalt not beto him as a creditor;neither shall yelay upon him interest.(如果你借钱给我的任何人。 ……你不应该是他的债权人;也不可向他加息。)——…...
vue2进阶学习之路
HTML、CSS和JavaScript基础 在学习Vue2之前,需要掌握HTML、CSS和JavaScript的基础知识。包括HTML的标签、CSS的布局和样式、JavaScript的变量类型、条件语句、循环语句等。 Vue2的基础知识 掌握Vue2的基本概念和语法,包括Vue2实例、数据绑定、指令、组件…...
决策树ID3算法
1. 决策树ID3算法的信息论基础 机器学习算法其实很古老,作为一个码农经常会不停的敲if, else if, else,其实就已经在用到决策树的思想了。只是你有没有想过,有这么多条件,用哪个条件特征先做if,哪个条件特征后做if比较优呢&#…...
C++模板基础(一)
函数模板(一) ● 使用 template 关键字引入模板: template void fun(T) {…} – 函数模板的声明与定义 – typename 关键字可以替换为 class ,含义相同 – 函数模板中包含了两对参数:函数形参 / 实参;模板形…...
生产者消费者模型线程池(纯代码)
目录 生产者消费者模型 条件变量&&互斥锁(阻塞队列) makefile Task.hpp BlockQueue.hpp BlockQueueTest.cc 信号量&&互斥锁(环形队列) makefile RingQueue.hpp RingQueueTest.cc 线程池(封…...
K8s 应用的网络可观测性: Cilium VS DeepFlow
随着分布式服务架构的流行,特别是微服务等设计理念在现代应用普及开来,应用中的服务变得越来越分散,因此服务之间的通信变得越来越依赖网络,很有必要来谈谈实现微服务可观测性中越来越重要的一环——云原生网络的可观测。K8s 是微服务设计理念能落地的最重要的承载体,本文…...
【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...
【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL
ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…...
