Qt6入门教程 11:父子对象关系

在上一篇中的纯手写部分,不管是创建菜单、工具栏还是状态栏,我们new完之后都未显式的调用delete进行销毁,这样难道不会有内存泄漏么?

QMenuBar *menuBar = new QMenuBar(this);
QToolBar *toolBar = new QToolBar(this);
QStatusBar *statusBar = new QStatusBar(this);

但是它们在创建的时候有一个共同的特点:都传入了this指针,这意味着它们都是作为子对象存在的。下面是它们的构造函数。

QMenuBar(QWidget *parent = nullptr)
QToolBar(QWidget *parent = nullptr)
QStatusBar(QWidget *parent = nullptr)

前面说过QWidget是所有用户界面对象的基类,菜单栏、工具栏和状态栏也不例外。
Qt的对象模型提供了一种Qt对象之间的父子关系,当很多个对象都按一定次序建立起来这种父子关系的时候,就组织成了一颗树。当delete一个父对象的时候,Qt的对象模型机制保证了会自动的把它的所有子对象,以及孙对象,等等,全部delete,从而保证不会有内存泄漏的情况发生。
任何事情都有正反两面作用,这种机制看上去挺好,但是却会对很多Qt的初学者造成困扰:
1.new了一个Qt对象之后,在什么 情况下应该delete它?
2.Qt的析构函数是不是有bug?
3.为什么正常delete一个Qt对象却会产生segment fault?
这篇文章就是针对这些问题的详细解释。
在每一个Qt对象中,都有一个链表,这个链表保存有它所有子对象的指针。当创建一个新的Qt对象的时候,如果把另外一个Qt对象指定为这个对象的父对象,那么父对象就会在它的子对象链表中加入这个子对象的指针。另外,对于任意一个Qt对象而言,在其生命周期的任何时候,都还可以通过setParent函数重新设置它的父对象。当一个父对象在被delete的时候,它会自动的把它所有的子对象全部delete。当一个子对象在delete的时候,会把它自己从它的父对象的子对象链表中删除。
QWidget是所有在屏幕上显示出来的界面对象的基类,它扩展了Qt对象的父子关系。一个Widget对象也就自然的成为其父Widget对象的子Widget,并且显示在它的父Widget的坐标系统中。例如,一个对话框(dialog)上的按钮(button)应该是这个对话框的子 Widget。
关于Qt对象的new和delete,下面我们举例说明。
例如,下面这一段代码是正确的:

int main()
{
    QObject* parent = new QObject(NULL);
    QObject* child1 = new QObject(parent);
    QObject* child2 = new QObject(parent);
    delete parent;
}


在上述代码片段中,parent是child的父对象,在parent对象中有一个子对象链表,这个链表中保存它所有子对象的指针,在这里,就是保存了child1和child2的指针。在代码的结束部分,就只delete了一个对象parent,在 parent对象的析构函数会遍历它的子对象链表,并且把它所有的子对象(child1和child2)一一删除。所以上面这段代码是安全的,不会造成内存泄漏。
如果我们把上面这段代码改成这样,也是正确的:

int main()
{
    QObject* parent= new QObject(NULL);
    QObject* child1 = new QObject(parent);
    QObject* child2 = new QObject(parent);
    delete child1;
    delete parent;
}

在这段代码中,我们就只看一下和上一段代码不一样的地方,就是在delete parent对象之前,先delete child1对象。在delete child1对象的时候,child1对象会自动的把自己从parent对象的子对象链表中删除,也就是说,在child1对象被delete完成之后,parent对象就只有一个子对象(child2)了。然后在delete parent对象的时候,会自动把child2对象也delete。所以,这段代码也是安全的。
Qt的这种设计对某些调试工具来说却是不友好的,比如valgrind。比如上面这段代码,valgrind工具在分析代码的时候,就会认为child2对象没有被正确的delete,从而会报告说,这段代码存在内存泄漏。
我们再看一看这一段代码:

int main()
{
    QWidget window;
    QPushButton quit("Exit", &window);
}

在这段代码中,我们创建了两个widget对象,第一个是window,第二个是quit,他们都是Qt对象,因为QPushButton是从QWidget派生出来的,而QWidget是从QObject派生出来的。这两个对象之间的关系是,window对象是quit对象的父对象,由于他们都会被分配在栈(stack)上面,那么quit对象是不是会被析构两次呢?我们知道,在一个函数体内部声明的变量,在这个函数退出的时候就会被析构,那么在这段代码中,window和quit两个对象在函数退出的时候析构函数都会被调用。那么,假设,如果是window的析构函数先被调用的话,它就会去delete quit对象;然后quit的析构函数再次被调用,程序就出错了。事实情况不是这样的,C++标准规定,本地对象的析构函数的调用顺序与他们的构造顺序相反。那么在这段代码中,这就是quit对象的析构函数一定会比window对象的析构函数先被调用,所以,在window对象析构的时候,quit对象已经不存在了,不会被析构两次。
所以,如果我们把代码改成这个样子,就会出错了。

int main()
{
    QPushButton quit("Exit");
    QWidget window;
    quit.setParent(&window);
}

但是我们自己在写程序的时候,也必须重点注意一项,千万不要delete子对象两次,就像前面这段代码那样,程序肯定就crash了。
最后,让我们来结合Qt源码,来看看这parent/child关系是如何实现的。
所有Qt对象的私有数据成员的基类是QObjectData类,这个类的定义如下:(在源码中的路径为:src\qtbase\src\corelib\kernel\qobject.h)

typedef QList<QObject*> QObjectList;
class QObjectData
{
public:
    QObject *parent;
    QObjectList children;
    // 忽略其它成员定义
};

我们可以看到,在这里定义了指向parent的指针和保存子对象的列表。其实,把一个对象设置成另一个对象的父对象,无非就是在操作这两个数据。把子对象中的这个parent变量设置为指向其父对象;而在父对象的children列表中加入子对象的指针。当然,我这里说的非常简单,在实际的代码中复杂的多,包含有很多条件判断,具体可以去阅读Qt源码。
下面来说说是在哪儿delete子对象的。
在QObject的析构函数中,有如下代码:(在源码中的路径为:src\qtbase\src\corelib\kernel\qobject.cpp)

QObject::~QObject()
{
.......................
    if (!d->children.isEmpty())
        d->deleteChildren();
.......................
}

这里先判断children是否为空,如果不为空,删除所有的子对象

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 siblings
    for (int i = 0; i < children.count(); ++i) {
        currentChildBeingDeleted = children.at(i);
        children[i] = 0;
        delete currentChildBeingDeleted;
    }
    children.clear();
    currentChildBeingDeleted = 0;
    isDeletingChildren = false;
}

因为父子关系,在多层嵌套的widget中,比如说QMainWdow中有个QTabWidget,QTabWidget中有多个QTextEdit,如果获取某个QTextEdit呢?
Qt中提供了findChildren来解决这个问题:

QList<T> QObject::findChildren(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const

这个函数第一个参数是控件的objectName,如果不指定的话,就是获取所有T类型的控件,比如:

QList<QTextEdit *> textEdit=ui->tabWidget->findChildren<QTextEdit *>();

就是获取tabWidget上的所有的QTextEdit。而:

QList<QTextEdit *> textEdit=ui->tabWidget->findChildren<QTextEdit *>("logEdit");

只获取objectName为logEdit的QTextEdit。
下图是Qt Designer中设置objectName的地方。

如果用代码设置

void  QObject::setObjectName(const QString &name)

为了方便我们窥视对象树的层次结构,Qt还专门提供了QObject:dumpObjectTree()和QObject::dumpObjectInfo()函数,这两个函数见名知义,就是将对象树和对象信息打印到Output窗口中。
本文参考Inside Qt系列文章,原文已无法找到出处。大家在Qt Assistant中搜索Object Trees &Ownership关键字,也能找到相关内容的介绍。

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

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

相关文章

OpenHarmony从TypeScript到ArkTS的适配规则

本文通过提供简洁的约束&#xff0c;将标准的TypeScript代码重构为ArkTS代码。 尽管ArkTS是基于TypeScript设计的&#xff0c;但出于性能考虑&#xff0c;一些TypeScript的特性被限制了。因此&#xff0c;在ArkTS中&#xff0c;所有的TypeScript特性被分成三类。 完全支持的特…

Linux sudo与/etc/sudoers

sudo介绍 sudo命令可以让普通用户在执行需要超级用户权限的命令时&#xff0c;临时提升为超级用户。例如&#xff0c;普通用户可以使用sudo执行系统管理任务&#xff0c;如安装软件、修改系统配置等。访问控制&#xff1a;sudo命令通过sudoers文件中的配置&#xff0c;可以对用…

什么是Spring

文章目录 什么是Spring什么是 IoC Spring的IoCDI的概念 什么是Spring Spring 是一个包含了众多工具方法的 IoC容器。 什么是 IoC Inversion of Control — 控制反转 在传统的开发中&#xff0c;假设A类依赖于B类&#xff0c;那么创建A对象实例就需要先new一个B类对象&#x…

【操作系统】实验八 proc文件系统

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

网络原理-初识(2)

协议分层 对于网络协议来说,往往分成几个层次进行定义. 网络通信的过程中,需要涉及到的细节,其实非常多.如果要有一个协议来完成网络通信,就需要约定好方方面面的内容,导致非常复杂. 而如果拆分的话,就十分复杂,庞大,因此需要分层. 什么是协议分层 即只有相邻的层次可以沟通,…

并查集与图

并查集与图 一、并查集概念实现原理代码实现查找根节点合并两颗树判断是否是同一棵树树的数量 二、图的基本概念定义分类完全图顶点的度连通图 三、图的存储结构分类邻接表邻接表的结构代码实现 邻接矩阵代码实现 四、图的遍历方式广度优先深度优先 五、最小生成树概念Kruskal算…

图中点的层次——树与图的广度优先遍历

问题描述 代码实现 #include <cstring> #include <iostream> #include <algorithm>using namespace std;const int N 1e5 10;int n, m; int h[N], ne[N * 2], e[N * 2], idx; int d[N]; // 从节点1到当前节点的距离 int q[N * 2]; // 数组模拟队列void ad…

BabylonJS 6.0文档 Deep Dive 摄像机(五):多视角(二)

1. 摄像机激活 一般来说&#xff0c;一个场景&#xff08;Scece&#xff09;只有一个激活相机&#xff0c;可以使用的activeCamera属性来指定它。但您也可以使用以下代码定义多个active相机来达成多视角的效果&#xff1a; scene.activeCameras.push(camera); scene.activeCa…

TensorRT英伟达官方示例解析(三)

系列文章目录 TensorRT英伟达官方示例解析&#xff08;一&#xff09; TensorRT英伟达官方示例解析&#xff08;二&#xff09; TensorRT英伟达官方示例解析&#xff08;三&#xff09; 文章目录 系列文章目录前言一、04-BuildEngineByONNXParser----pyTorch-ONNX-TensorRT生成…

探索设计模式的魅力:深入理解面向对象设计的深层原则与思维

如何同时提高一个软件系统的可维护性 和 可复用性是面向对象对象要解决的核心问题。 通过学习和应用设计模式&#xff0c;可以更加深入地理解面向对象的设计理念&#xff0c;从而帮助设计师改善自己的系统设计。但是&#xff0c;设计模式并不能够提供具有普遍性的设计指导原则。…

SAP ERP 物料主数据同步外围系统

物料主数据集成在很多项目是比较常见的需求&#xff0c;在做系统实现之前我们需要明确涉及的业务流程和需求范围&#xff0c;并且对每个系统的业务边界进行明确&#xff1a; 如果是从SAP ERP 向其他系统推送数据&#xff0c;并且实时性要求高的情况下&#xff0c;我一般倾向于在…

开关电源空载电流测试方法大全

空载电流测试原理 开关电源空载电流是指电源在没有负载的情况下所消耗的电流。空载电流的大小会影响到电源的工作效率和稳定性&#xff0c;因此测量开关电源的空载电流是非常必要的。 开关电源空载电流测试是在不接任何负载的条件下&#xff0c;用万用表、电流表或者其它专用测…

[MRCTF2020]Ez_bypass1

代码审计&#xff0c;要求gg和id的MD5值相等而gg和id的值不等或类型不等 相同MD5值的不同字符串_md5相同的不同字符串-CSDN博客 不过这道题好像只能用数组 下一步是passwd不能是纯数字&#xff0c;但是下一个判断又要passwd等于1234567 这里通过passwd1234567a实现绕过 原…

【操作系统】实验六 分析源代码

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

【grafana】使用教程

【grafana】使用教程 一、简介二、下载及安装及配置三、基本概念3.1 数据源&#xff08;Data Source&#xff09;3.2 仪表盘&#xff08;Dashboard&#xff09;3.3 Panel&#xff08;面板&#xff09;3.4 ROW&#xff08;行&#xff09;3.5 共享及自定义 四、常用可视化示例4.1…

【mongoDB】图形化界面工具(mongoDB Compass)

官网地址&#xff1a;https://www.mongodb.com/try/download/compass 下载完之后直接安装 桌面上会产生一个快捷方式 双击就会进入mongoDB图形化界面工具

IP组播地址

目录 1.硬件组播 2.因特网范围内的组播 IP组播地址让源设备能够将分组发送给一组设备。属于多播组的设备将被分配一个组播组IP地址 组播地址范围为224.0.0.0~239.255.255.255(D类地址)&#xff0c;一个D类地址表示一个组播组。只能用作分组的目标地址。源地址总是为单播地址…

Vue+OpenLayers7入门到实战:鹰眼控件简单介绍,并使用OpenLayers7在地图上添加鹰眼控件

返回《Vue+OpenLayers7》专栏目录:Vue+OpenLayers7 前言 本章介绍OpenLayers7添加鹰眼控件到地图上的功能。 在OpenLayers中,想要实现鹰眼控件,必须要新建一个数据源,且不能跟其他图层混用,相当于鹰眼是一个单独图层。 补充知识,鹰眼控件是什么? 鹰眼控件是一种在地…

AI教我学编程之SQL Server常见指令以及数据类型

前言 今天在工作的过程中&#xff0c;遇到了许多常见的属性&#xff0c;在此做下记录&#xff0c;方便以后查询 目录 SQL Server 常见指令 对话AI 光有概念怎么行 阶段总结 SQL Server关键字 边学边练 数据类型 看图说话 对话AI 数据类型我知道 括号里的神秘数字 疑问 边练…

彩色图像处理之彩色图像分割的python实现——数字图像处理

原理 彩色图像分割是图像处理领域的一个重要技术&#xff0c;它旨在将一幅彩色图像划分为多个区域或对象。其基本原理包括以下几个方面&#xff1a; 像素特征的提取&#xff1a;彩色图像分割首先涉及到像素级的特征提取。在彩色图像中&#xff0c;常用的特征包括颜色、纹理和…