Qt 对象树与继承关系

本文详细介绍了Qt中的对象树概念,说明了QObject如何通过父对象管理子对象的生命周期。当父对象被销毁时,其子对象也会随之删除,避免了内存泄漏。同时,讨论了对象构建和销毁的顺序,强调了正确创建和删除对象以维持对象树平衡的重要性。文中还通过示例代码展示了错误的构造顺序可能导致的双析构问题,提醒开发者注意这类潜在的编程陷阱。
摘要由CSDN通过智能技术生成

Qt 对象树与继承关系


QObject对象树

QObject将自己组织在对象树中。当您创建一个以另一个对象作为父对象的QObject时,它将被添加到父对象的children()列表中。因为 QObject 类就有一个私有变量 QList<QObject *>,专门存储这个类的子对象们。

typedef QList<QObject*> QObjectList;

比如创建一个 QObject 并指定父对象时,就会把自己加入到父对象的子对象 children() 列表中,也就是 QList<QObject *> 变量中。并在父对象被删除时被删除。事实证明,这种方法非常适合GUI对象的需求。例如,QLabel(标签)是相关窗口的子级,因此,当用户关闭该窗口时,快捷方式也会被删除。

QQuickItem是Qt Quick模块的基本可视元素,它继承自QObject,但是其可视父对象的概念不同于QObject父对象的概念。项的视觉父项不一定与它的对象父项相同。有关更多详细信息,请参见Qt Quick中的Concepts-Visual Parent。

//由于跟派生的主窗口类名相同,所以使用Ui命名空间区分开来。
//ui文件有uic.exe自动生成ui_mainwindow.h文件

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
	//moc元对象宏,使得对象继承信号槽等机制。
	//moc.exe会把该头文件自动转换为标准的c++
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
	//此处的ui指针不指向任何父对象
    Ui::MainWindow *ui;
};

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    //此处new 了一个Ui::MainWindow对象。
    , ui(new Ui::MainWindow)
{
	//组装界面,this是该窗口指针
	//将ui内定义的所有控件指针指向父窗口,而不是UI::MainWinow *ui;
	//使得父窗口析构之前,先释放子对象(控件)
	//setupUi(this)是由.ui文件生成的类的构造函数,
    //这个函数的作用是对界面进行初始化,它按照我们在Qt设计器里设计的样子把窗体画出来,
    //把我们在Qt设计器里面定义的信号和槽建立起来。
    //也可以说,setupUi是我们画界面和写程序之间的桥梁。
    ui->setupUi(this);    
}

MainWindow::~MainWindow()
{   
	//ui指针未指向任何父类,所以需要单独释放回收。
	//控件并非跟随ui释放
    delete ui;
}

QWidget是Qt Widgets模块的基本类,它扩展了父子关系。通常,子级也将成为子级小部件,即,它在其父级的坐标系中显示,并通过其父级的边界进行图形裁剪。例如,当应用程序在关闭后删除消息框时,消息框的按钮和标签也会被删除,就像我们想要的那样,因为按钮和标签是消息框的子级。Qt 运用对象树模式,当父对象被析构时,子对象自动就 delete 掉了,不用再写一大堆的代码了。

您也可以自己删除子对象,它们将从自己的父对象中删除。例如,当用户delete删除工具栏时,它可能导致应用程序删除其QToolBar对象之一,在这种情况下,工具栏的QMainWindow父级将检测到更改并相应地重新配置其屏幕空间。

当应用程序外观或行为异常时,调试功能QObject :: dumpObjectTree()和QObject :: dumpObjectInfo()通常很有用。

QObject的构建/销毁顺序

当在堆上创建QObject时(即,用new创建),可以以任何顺序从它们构造树,然后,可以以任何顺序销毁树中的对象。删除树中的任何QObject时,如果该对象具有父对象,则析构函数会自动从其父对象中删除该对象。如果对象有子对象,则析构函数会自动删除每个子对象。没有QObject中被删除两次,不管破坏的秩序。
正常情况下,最后被创建出来的会先被析构掉,用代码来演示一下:

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

父窗口和子窗口quit都是QObject,因为QPushButton继承QWidget, QWidget继承QObject。这段代码是正确的:quit的析构函数不会被调用两次,因为c++语言标准(ISO/IEC 14882:2003)规定本地对象的析构函数被调用的顺序与其构造函数相反。因此,子程序quit的析构函数首先被调用,它在调用window的析构函数之前从父程序window中删除自己。
但是现在考虑一下,如果我们交换构造的顺序会发生什么,如下面的第二个片段所示:

int main()()
{{
    QPushButton quit("Quit");
    QWidget window;

    quit.setParent(&window);
    ...
}

由于 window 的创建在quit对象之后,程序退出时先调用 window 的析构函数,此时由于 quit 是window的子对象,所以第一次被析构。接着程序再一次调用 quit 的析构函数,这是quit 的第二次被析构,这时由于被两次析构,已经找不到指针,程序崩溃。

这种特殊情况在编程中很隐蔽,不容易发现。因为编译的时候不会报错,只有运行时才会产生问题。所以我们要保持良好的编程习惯以及对事物产生顺序有科学的认知。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值