Qt -对象树

博客主页:【夜泉_ly】
本文专栏:【暂无】
欢迎点赞👍收藏⭐关注❤️

在这里插入图片描述

目录

    • 前言
    • 构造
      • QObject::QObject
      • QObjectPrivate::setParent_helper
    • 析构
    • 提醒

前言

Qt 的对象树是什么?
我认为,是 Qt 基于C++的继承
让所有继承自 QObject 类的对象,
能够以父子关系组织起来,
从而达到用父对象管理子对象生命周期,
的一种 设计模式

具体来说,
在一个父对象释放时,
它会释放它的所有子对象,
这样就能做到 自动化的内存管理

我们创建对象时把它挂到树上,
就不用每次都手动调用 delete

下面,我们还是来看看源码吧,
分别从构造和析构两个方面,
简单理解 Qt 对象树的底层逻辑。

构造

QObject::QObject

首先是 QObjectpublic 构造
只有这一个:

QObject::QObject(QObject *parent): QObject(*new QObjectPrivate, parent)
{
}

作用很简单,
调用了另一个构造:

/*!\internal*/
QObject::QObject(QObjectPrivate &dd, QObject *parent): d_ptr(&dd)
{

这个跟 QObject 的设计有关,
Qt 把 QObject 所有的成员变量放进了 QObjectPrivate
QObject 中只保留了一个 QScopedPointer<QObjectData>

这也是一种设计模式,不过这不是今天的重点,而且设计模式我还没学多少。
所以直接贴出ai的回答:

这是一种称为"桥接模式"(Bridge Pattern)的设计模式,也常被称为"Pimpl惯用法"(Pointer to Implementation Idiom)或"不透明指针"(Opaque Pointer)。
在Qt的实现中,QObject类使用了这种模式,将其实现细节隐藏在QObjectPrivate类中,而QObject自身只保留了一个指向这些实现细节的指针(QScopedPointer)。
这种设计模式的主要优点包括:

  1. 封装实现细节:用户代码只需要与QObject的公共接口交互,而不需要了解其内部实现。
  2. 二进制兼容性:可以在不破坏二进制兼容性的情况下修改私有实现,因为公共类的内存布局不会改变。
  3. 减少编译依赖:使用QObject的代码不需要包含QObjectPrivate的完整定义,从而减少了编译依赖,加快了编译速度。
  4. 降低耦合度:将接口与实现分离,使系统更加灵活。
    在Qt框架中,这种模式被广泛使用,不仅仅是在QObject中,还在许多其他类中也采用了这种设计方式。

关于这个构造函数的实现,
我保留了关键的几句(我认为的):

QObject::QObject(QObjectPrivate &dd, QObject *parent): d_ptr(&dd)
{Q_ASSERT_X(this != parent, Q_FUNC_INFO, "Cannot parent a QObject to itself");if (parent) {setParent(parent);}
}

首先断言自己的父对象不能是自己,
然后调用 setParent 函数。

setParent 函数简单做了两句检查,
接着调用 QObjectPrivate::setParent_helper

QObjectPrivate::setParent_helper

这是对象树中的核心实现了:

void QObjectPrivate::setParent_helper(QObject *o)
{if (o == parent) // 避免重复设置return;if (parent) {QObjectPrivate *parentD = parent->d_func();// 处理各种特殊情况...// 从原父对象的children列表中移除:parentD->children.removeAt(index);// 发送子对象移除事件...}// 下面开始重新设定父对象parent = o;if (parent) {// object hierarchies are constrained to a single thread// ---翻译: 对象层次结构被约束到单个线程if (threadData.loadRelaxed() != parent->d_func()->threadData.loadRelaxed()) {qWarning("QObject::setParent: Cannot set parent, new parent is in a different thread");parent = nullptr;return;}// 添加到新父对象的children列表:parent->d_func()->children.append(q);// 发送子对象添加事件...}
}

从这里我们可以看见,

  1. 同一对象树的所有对象都必须在同一线程中,
    比如下面这种写法就是错误的:
    在这里插入图片描述
    创建 ThreadTest 对象并 start
    Qt 会报错(但不会终止程序):
    在这里插入图片描述
    下面这种写法也是错的:
    在这里插入图片描述
    Qt 会报错(但不会终止程序):
    在这里插入图片描述

  2. Qt 在每一步都对空指针进行了检查,
    因此,如果你想解除父子对象的关系,
    可以放心的 setParent(nullptr)

  3. 对于每个继承自 QObject 的对象,
    都会存在一个 QList<QObject*>children 用于存储子对象。
    (当然,严格讲,这个链表是 QObjectData 里的)。

析构

下面是 ~QObject 的核心(我认为的):

QObject::~QObject()
{d->wasDeleted = true;// 绑定数据清理:if (!d->bindingStorage.isValid()) {// 处理线程移动后的未完成绑定...}d->clearBindingStorage();// 处理智能指针(Qt的)引用计数...// 发送销毁信号:if (!d->wasWidget && d->isSignalConnected(0)) {emit destroyed(this);}// 处理信号和槽连接:QObjectPrivate::ConnectionData *cd = d->connections.loadAcquire();if (cd) {if (cd->currentSender) {// 处理当前发送者的连接...}// disconnect all receiversfor (int signal = -1; signal < receiverCount; ++signal) {// 处理接收者的连接...}// Disconnect all senders:while (QObjectPrivate::Connection *node = cd->senders) {// 处理发送者的连接...}}// 删除子对象:if (!d->children.isEmpty())d->deleteChildren();// 从父对象中移除:if (d->parent)d->setParent_helper(nullptr);
}

可以看见,
如果我们手动 delete
由于最后一句的 setParent_helper(nullptr)
对象也会从对象树上移除
不会被父对象重复 delete

deleteChildren 就只是很单纯的遍历然后 delete

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.size(); ++i) {currentChildBeingDeleted = children.at(i);children[i] = nullptr;delete currentChildBeingDeleted;}children.clear();currentChildBeingDeleted = nullptr;isDeletingChildren = false;
}

提醒

虽然 Qt 做了非常多的检查,
但我们还是最好不要乱搞。

比如这种循环引用代码:

QObject a, b;
a.setParent(&b);
b.setParent(&a);

在这里插入图片描述
再比如这种不分主次的手动delete:

QObject* obj_a = new QObject(this);
QObject* obj_b = new QObject(obj_a);
delete obj_a;
delete obj_b;

在这里插入图片描述

再比如没搞清生命周期情况下的,
智能指针和对象树的混用:

QObject* obj_a = new QObject(this);
std::shared_ptr<QObject> obj_b(new QObject(obj_a));
delete obj_a;

在这里插入图片描述

注:
并不是说不能手动 delete,或者不能智能指针和对象树混用,
而是你在用的时候,要能搞清楚你在干什么。

反正我是不会用的,就让对象树帮我自动销毁多好。

在这里插入图片描述


希望本篇文章对你有所帮助!并激发你进一步探索编程的兴趣!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/128.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript与TypeScript

TypeScript 和 JavaScript 都是用于构建 Web 应用的编程语言&#xff0c;但它们有着不同的设计目标和特性。 一、JavaScript 1. 定义与特点 动态脚本语言&#xff1a;由 Brendan Eich 在 1995 年创建&#xff0c;最初用于浏览器端的交互逻辑。弱类型/动态类型&#xff1a;变量…

教育行业网络安全:守护学校终端安全,筑牢教育行业网络安全防线!

教育行业面临的终端安全问题日益突出&#xff0c;主要源于教育信息化进程的加速、终端设备多样化以及网络环境的开放性。 以下是教育行业终端安全面临的主要挑战&#xff1a; 1、设备类型复杂化 问题&#xff1a;教育机构使用的终端设备包括PC、服务器等&#xff0c;操作系统…

Linux常见指令介绍中(入门级)

1. man 在Linux中&#xff0c;man命令是用于查看命令手册页的工具&#xff0c;它可以帮助用户了解各种命令、函数、系统调用等的详细使用方法和相关信息。 用法&#xff1a;在终端中输入man加上要查询的命令或工具名称&#xff0c;例如man ls&#xff0c;就会显示ls命令的手册…

linux安装mysql数据库

1.判断系统是多少位的 file /sbin/init2.下载linux安装包 5.7.25.64位安装包链接&#xff1a;https://pan.baidu.com/s/13vFuRikwJaI96K0AmUQXzg提取码&#xff1a;ga7h其他版本安装 去官网下载&#xff1a;https://dev.mysql.com/downloads/mysql/3.创建mysql文件夹 mkdir /…

基于超启发鲸鱼优化算法的混合神经网络多输入单输出回归预测模型 HHWOA-CNN-LSTM-Attention

基于超启发鲸鱼优化算法的混合神经网络多输入单输出回归预测模型 HHWOA-CNN-LSTM-Attention 随着人工智能技术的飞速发展&#xff0c;回归预测任务在很多领域得到了广泛的应用。尤其在金融、气象、医疗等领域&#xff0c;精确的回归预测模型能够为决策者提供宝贵的参考信息。为…

深度解析算法之位运算

33.常见位运算 1.基础位运算 << 左移操作符 > >右移操作符号 ~取反 &按位与&#xff1a;有0就是0 |按位或&#xff1a;有1就是1 ^按位异或&#xff1a;相同为0&#xff0c;不用的话就是1 /无进位相加 0 1 0 0 1 1 0 1 0 按位与结果 0 1 1 按位或结果 0 0 1 …

python生成项目依赖文件requirements.txt

文章目录 通过pip freeze去生成通过pipreqs去生成 通过pip freeze去生成 pip freeze > requirements.txt会将整个python的Interceptor的环境下lib包下所有的依赖都生成到这个文件当中&#xff0c;取决于我们使用的python的版本下所有的安装包。不建议使用这种方式&#xff…

C++11特性补充

目录 lambda表达式 定义 捕捉的方式 可变模板参数 递归函数方式展开参数包 数组展开参数包 移动构造和移动赋值 包装器 绑定bind 智能指针 RAII auto_ptr unique_ptr shared_ptr 循环引用 weak_ptr 补充 总结 特殊类的设计 不能被拷贝的类 只能在堆上创建…

C语言之高校学生信息快速查询系统的实现

&#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 C语言之高校学生信息快速查询系统的实现 目录 任务陈述与分析 问题陈述问题分析 数据结构设…

关于进程状态

目录 进程的各种状态 运行状态 阻塞状态 挂起状态 linux中的进程状态、 进程状态查看 S状态&#xff08;浅睡眠&#xff09; t 状态&#xff08;追踪状态&#xff09; T状态&#xff08;暂停状态&#xff09; ​编辑 kill命令手册 D状态&#xff08;深度睡眠&#…

【网络编程】从零开始彻底了解网络编程(二)

本篇博客给大家带来的是网络编程的知识点,. &#x1f40e;文章专栏: JavaEE初阶 &#x1f680;若有问题 评论区见 ❤ 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 . 王子,公主请阅&#x1f680; 要开心要快乐顺便进步 1. …

node.js|环境部署|源码编译高版本的node.js

一、 前言 本文就如何二进制部署和源码编译安装部署node.js环境做一个简单的介绍 node的版本大体是以18版本为界限&#xff0c;也就是说18版本之前对glibc版本没有要求&#xff0c;其后的版本都对glibc版本有要求&#xff0c;node的版本越高&#xff0c;glibc需要的版本也越…