当前位置: 首页 > news >正文

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语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!

相关文章:

  • CSS预处理器对比:Sass、Less与Stylus如何选择
  • 操作系统之shell实现(下)
  • Laravel 对接阿里云 OSS 说明文档
  • GPIO(通用输入输出端口)详细介绍
  • 【Qt】控件的理解 和 基础控件 QWidget 属性详解(通俗易懂+附源码+思维导图框架)
  • PyTorch卷积层填充(Padding)与步幅(Stride)详解及代码示例
  • 深入理解 Spring @Configuration 注解
  • PyTorch深度学习框架60天进阶学习计划 - 第48天:移动端模型优化(二)
  • 4.22tx视频后台开发一面
  • 【愚公系列】《Python网络爬虫从入门到精通》063-项目实战电商数据侦探(主窗体的数据展示)
  • 前端框架的“快闪“时代:我们该如何应对技术迭代的洪流?
  • 媒体关注:联易融聚焦AI+业务,重塑供应链金融生态
  • CAD在线查看免费,可以支持DWG/GLB/GLTF/doc/wps/pdf/psd/eml/zip, rar/MP3/MP4/svg/OBJ/FBX格式
  • 2025年数字媒体设计与文化交流国际会议 (DMACE 2025)
  • 【Redis】字符串类型List 常用命令详解
  • 基于 PaddleOCR对pdf文件中的文字提取
  • 分布式之易混淆概念
  • vue浅试(1)
  • EasyRTC音视频实时通话:打造高清低延迟的远程会议新生态
  • (51单片机)LCD显示温度(DS18B20教程)(LCD1602教程)(延时函数教程)(单总线教程)
  • 中国围棋协会将不组队参加今年的LG杯世界棋王赛
  • 深一度|坚守17年,这件事姚明就算赔钱也在继续做
  • 视频丨普京称积极对待任何和平倡议
  • 王忠诚出任四川遂宁代市长,此前为成都市政府秘书长
  • 在现代东京,便利店如何塑造了饮食潮流、生活方式和日本社会
  • 特朗普“炮轰”美联储带崩美股!道指跌超900点,黄金再创新高