Qt 对象树详解:从原理到运用
1. 什么是对象树?
对象树是一种基于父子关系的对象管理机制。在 Qt 中,所有继承自 QObject
的类都可以参与到对象树中。
当一个对象被设置为另一个对象的父对象时,子对象会被添加到父对象的内部列表中,形成一种树状结构。
Qt 提供了一个调试工具方法 dumpObjectTree()
,可以帮助开发者打印对象树的结构。
QObject* parent = new QObject();QObject* child = new QObject();child->setParent(parent);parent->dumpObjectTree();
1.1 对象树结构
在 Qt 中,对象树的每个节点是一个 QObject 对象,而 “边” 代表父子关系。具体来说:
- 根节点:没有父对象的对象(即 parent == nullptr)。
- 子节点:有父对象的对象,它们会被添加到父对象的
子对象列表
中。 - 叶子节点:没有子对象的对象。
grandparent 是根节点,parent 是 grandparent 的子节点,child 是 parent 的子节点,形成了一个三层的树形结构。
1.2 为什么选择 new 而不是栈上分配?
如果对象是在栈上创建的(如 局部变量),当作用域结束时,该对象会自动销毁。可能导致以下问题:
- 如果子对象比父对象先销毁,父对象的 子对象列表 可能会指向无效内存。直观表现可能为:子对象区域显示为空白状态。
通过 new 创建对象,可以确保对象的生命周期由对象树管理,而不是由作用域控制。 这样可以避免上述问题。
2. 对象树的工作原理
在 Qt 中,继承自 QObject 的对象能够参与到对象树的前提是 该类必须正确地声明
Q_OBJECT
宏。
对象树通过以下两个关键成员变量实现:
QObject* parent
:指向父对象的指针;QList<QObject*> children
:存储子对象的列表。
每个 QObject 对象可以有一个父对象 parent,和有多个子对象 children;子对象会被自动添加到父对象的 children 列表中。
2.1 对象创建时
当创建一个新的 QObject 对象并指定父对象时,会发生:
- 新对象的 parent 指针被设置为指定的父对象;
- 新对象被自动添加到父对象的 children 列表中。
2.2 对象销毁时
当一个对象被销毁时,以下操作会发生:
- 该对象的所有子对象会被递归销毁;
- 该对象从其父对象的 children 列表中移除。
Qt 的实现确保了在子对象的析构函数中,它会调用父对象的相关方法(如 QObjectPrivate::removeChild()
),将自己从父对象的 children 列表中移除;显式移除子对象,是为了避免父对象的 children 列表中残留无效指针,从而确保对象树的完整性。
class MyObject : public QObject
{Q_OBJECT
public:QString name; // 对象名称explicit MyObject(QString name, QObject *parent) : name(name), QObject(parent) {}~MyObject(){qDebug() << name << " entering destructor.";if (parent()) {qDebug() << name << " is being removed from parent's children list.";}}
};void CreateObjects()
{MyObject* grandparent = new MyObject(QString("grandparent"));MyObject* parent = new MyObject(QString("parent"), grandparent);MyObject* child = new MyObject(QString("child"), parent);delete grandparent;
}
实际销毁顺序是:child -> parent -> grandparent —— 根据打印信息,child 开始析构时 parent 仍然存在。
3. 对象树的其它作用
在 Qt 中,对象树的核心作用是 简化对象的生命周期管理。通过对象树,开发者无需过多担心内存泄露或悬空指针等问题。
“简化对象的生命周期管理” 在 “工作原理” 部分已经解释过,除此之外,对象树的作用还包括:
3.1 层次化组织对象
在 GUI 应用程序中,对象之间的关系通常是层次化的。例如,一个窗口可能包含多个控件,而每个控件又可能包含子控件。对象树提供了一种自然的方式来组织这些对象之间的关系。
int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;w.show();return a.exec();
}
-
Widget
是一个继承自 QWidget 的类,而 QWidget 又继承自 QObject。因此 Widget 对象可以参与到 Qt 的对象树机制中。 -
代码中
w
是一个在栈上分配的对象。如果用户手动关闭窗口(通过点击窗口的关闭按钮 或 调用
w.close()
),窗口会被隐藏(调用了hide()
方法),但 w 本身不会被销毁,因此它的子对象也不会被销毁 —— 栈上分配的对象由 C++ 的作用域管理,只有当作用域结束时(即 main() 函数返回时),w 才会被销毁。 -
设置
Qt::WA_DeleteOnClose
。Qt::WA_DeleteOnClose 是一个窗口属性,用于指示窗口在关闭时自动删除自身。如果设置了该属性,当用户手动关闭窗口时,w 会被销毁,并触发对象树的递归销毁。
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);// 设置关闭时自动销毁this->setAttribute(Qt::WA_DeleteOnClose); }
3.2 其它作用
-
支持信号与槽机制:
Qt 的信号与槽机制依赖于对象树来确保对象的生命周期一致性。
如果一个对象被销毁,Qt 会自动断开与其相关的信号和槽连接;
这一机制由 QObject 的析构函数触发,确保信号不会发送到已销毁的对象。
-
简化资源管理:
通过将资源封装成 QObject 的子类,并将其添加到对象树中,可以确保资源在父对象销毁时被正确释放。
class FileWrapper : public QObject {Q_OBJECT public:FileWrapper(const QString& path, QObject* parent = nullptr): QObject(parent), file(path){}~FileWrapper(){file.close();} private:QFile file; };QObject* parent = new QObject(); FileWrapper* file = new FileWrapper("test.txt", parent);
-
提供遍历和查找功能:通过 QObject::children() 方法,可以获取某个对象的所有子类对象。
-
避免重复释放:对象树通过集中管理对象的销毁过程,避免了重复释放的问题。
4. 注意事项
-
避免循环引用:如果两个对象互相设置对方为父对象,会导致循环引用,从而引发内存泄露。
解决方案: 确保父子关系是单向的。
-
避免跨线程操作:如果父子对象位于不同的线程中,销毁顺序可能会受到线程调度的影响.
QThread thread; QObject* obj = new QObject(); obj->moveToThread(&thread); QObject* child = new QObject(obj);
如果子对象在父对象销毁后仍然被访问(如 调用 child->parent() ),会导致未定义行为。
解决方案:
确保父子对象始终位于同一个线程;
如果需要跨线程操作,可以使用信号与槽机制进行线程间通信,而不是直接操作对象树。
-
避免手动干预销毁:如果手动调用 delete 销毁一个对象,而该对象同时参与对象树,可能会导致重复释放的问题。
QObject* parent = new QObject(); QObject* child = new QObject(parent);delete parent; // 自动销毁 child delete child; // 再次销毁 child,重复释放
解决方案:
让对象树完全管理对象的生命周期,避免手动调用 delete ;
如果必须手动管理对象,确保不将其添加到对象树中。
相关文章:

Qt 对象树详解:从原理到运用
1. 什么是对象树? 对象树是一种基于父子关系的对象管理机制。在 Qt 中,所有继承自 QObject 的类都可以参与到对象树中。 当一个对象被设置为另一个对象的父对象时,子对象会被添加到父对象的内部列表中,形成一种树状结构。 Qt 提…...
【软路由】ImmortalWrt 编译指南:从入门到精通
对于喜欢折腾路由器,追求极致性能和定制化的玩家来说,OpenWrt 无疑是一个理想的选择。而在众多 OpenWrt 衍生版本中,ImmortalWrt 以其更活跃的社区、更激进的特性更新和对新硬件的支持而备受关注。 本文将带你深入了解 ImmortalWrt࿰…...

【智能音频新风尚】智能音频眼镜+FPC,打造极致听觉享受!【新立电子】
智能音频眼镜,作为一款将时尚元素与前沿科技精妙融合的智能设备,这种将音频技术与眼镜形态完美结合的可穿戴设备,不仅解放了用户的双手,更为人们提供了一种全新的音频交互体验。新立电子FPC在智能音频眼镜中的应用,为音…...

第2章 windows故障排除(网络安全防御实战--蓝军武器库)
网络安全防御实战--蓝军武器库是2020年出版的,已经过去3年时间了,最近利用闲暇时间,抓紧吸收,总的来说,第2章开始带你入门了,这里给出了几个windows重要的工具,说实话,好多我也是第一…...

深度学习笔记——线性回归的从0开始实现
记录学习到的知识: 语义分割是将标签或类别与图片的每个像素关联的一种深度学习算法。 它用来识别构成可区分类别的像素集合。 图像分割是一个端到端图像分析过程,它将数字图像分成多个片段,并对每个区域中包含的信息进行分类。三种图像分割…...
配置Spring Boot中的Jackson序列化
配置Spring Boot中的Jackson序列化 在开发基于Spring Boot的应用程序时,Jackson是默认的JSON序列化和反序列化工具。它提供了强大的功能,可以灵活地处理JSON数据。然而,Jackson的默认行为可能无法完全满足我们的需求。例如,日期格…...
AWS跨账号服务全解析:安全共享资源的最佳实践
在复杂的云环境中,企业常常需要将不同业务部门、项目或环境分配到独立的AWS账户中,以实现资源隔离和权限管控。然而,跨账户的资源共享与协作需求也随之而来。AWS为此提供了丰富的跨账号服务,允许不同账户之间安全、高效地共享资源。本文将深入解析这些服务,并结合实际场景…...

Rust~String、str、str、String、Box<str> 或 Box<str>
Rust语言圣经中定义 str Rust 语言类型大致分为两种:基本类型和标准库类型,前者由语言特性直接提供,后者在标准库中定义 str 是唯一定义在 Rust 语言特性中的字符串,但也是几乎不会用到的字符串类型 str 字符串是 DST 动态大小…...

SpringBoot五:JSR303校验
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 松散绑定 意思是比如在yaml中写的是last-name,这个和lastName意思是一样的,-后的字母默认是大写的 JSR303校验 就是可以在字段增加…...

Oracle 数据库基础入门(四):分组与联表查询的深度探索(上)
在 Oracle 数据库的学习进程中,分组查询与联表查询是进阶阶段的重要知识点,它们如同数据库操作的魔法棒,能够从复杂的数据中挖掘出有价值的信息。对于 Java 全栈开发者而言,掌握这些技能不仅有助于高效地处理数据库数据࿰…...

基于SpringBoot的绿城郑州爱心公益网站设计与实现现(源码+SQL脚本+LW+部署讲解等)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...

创建一个简单的spring boot+vue前后端分离项目
一、环境准备 此次实验需要的环境: jdk、maven、nvm和node.js 开发工具:idea或者Spring Tool Suite 4,前端可使用HBuilder X,数据库Mysql 下面提供maven安装与配置步骤和nvm安装与配置步骤: 1、maven安装与配置 1…...
标签使用笔记
文章目录 文件夹结构可以有多个功能吗?标签是如何保存的 标签做成对外接口保存、修改查询删除标签列表标签表设计标签和分类的区别 虽然大体知道怎么设计做,但是整理出来更清晰,那么整理下。 一般来说有两种索引就够。 1、标题文字索引。 # 用于搜索文章…...
Unity图集使用事项
一. 图集布局算法 紧密填充是一种常见的图集布局算法,它的主要目标是尽可能地减少图集的空间浪费。该算法会根据图像的形状和大小,将它们紧密地排列在图集中,以确保最小化空白区域的存在。这样可以有效地利用内存,并减少图集的尺…...

Flutter 学习之旅 之 flutter 在 Android 端读取相册图片显示
Flutter 学习之旅 之 flutter 在 Android 端读取相册图片显示 目录 Flutter 学习之旅 之 flutter 在 Android 端读取相册图片显示 一、简单介绍 二、简单介绍 image_picker 三、安装 image_picker 四、简单案例实现 五、关键代码 代码说明: 一、简单介绍 Fl…...
RagFlow专题二、RagFlow 核心架构(数据检索、语义搜索与知识融合)
深入解析 RagFlow 核心架构:数据检索、语义搜索与知识融合 在前一篇文章中,我们对 RagFlow 的核心理念、与传统 RAG 的区别以及其适用场景进行了深入探讨。我们了解到,RagFlow 通过动态优化检索、增强生成质量以及实时知识管理,使得大模型在复杂任务中的表现更加稳定和高效…...

解决各大浏览器中http地址无权限调用麦克风摄像头问题(包括谷歌,Edge,360,火狐)后续会陆续补充
项目场景: 在各大浏览器中http地址调用电脑麦克风摄像头会没有权限,http协议无法使用多媒体设备 原因分析: 为了用户的隐私安全,http协议无法使用多媒体设备。因为像摄像头和麦克风属于可能涉及重大隐私问题的API,ge…...

【SpringBoot+Vue】博客项目开发二:用户登录注册模块
后端用户模块开发 制定参数交互约束 当前,我们使用MybatisX工具快速生成的代码中,包含了一个实体类,这个类中包含我们数据表中的所有字段。 但因为有些字段,是不应该返回到前端的,比如用户密码,或者前端传…...

(十 二)趣学设计模式 之 享元模式!
目录 一、 啥是享元模式?二、 为什么要用享元模式?三、 享元模式的实现方式四、 享元模式的优缺点五、 享元模式的应用场景六、 总结 🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支…...

leetcode第77题组合
原题出于leetcode第77题https://leetcode.cn/problems/combinations/ 1.树型结构 2.回溯三部曲 递归函数的参数和返回值 确定终止条件 单层递归逻辑 3.代码 二维数组result 一维数组path void backtracking(n,k,startindex){if(path.sizek){result.append(path);return ;}…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...

label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...