• Qt的对象树


    一、问题

    在C++中中,我们都知道:delete 和 new 必须配对使用(一 一对应):delete少了,则内存泄露。

    为什么Qt使用new来创建一个控件,但是却没有使用delete来进行释放?

    二、对象树

    Qt中使用对象树来组织和管理所有的QObject类及其子类的对象。

    当创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。

    每个 QObject 内部都有一个list,用来保存所有的 children,还有一个指针,保存自己的parent。当它自己被析构时,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。

     比如Widget中的组件,如果是在堆上创建(使用new操作符),那么只要指定Widget为其父窗口(创建时指定parent参数为this)就可以了,也不需要进行delete操作。当整个应用程序关闭时,会去销毁该Widget对象,而此时又会自动销毁它的所有子组件,这些都是Qt的对象树所完成的。

    三、对象树的实例

    我们来分析一个Button对象的创建到销毁的过程。

    创建一个MyPushButton对象,继承QPushButton

    1. #ifndef MYPUSHBUTTON_H
    2. #define MYPUSHBUTTON_H
    3. #include
    4. class MyPushButton : public QPushButton
    5. {
    6. Q_OBJECT
    7. public:
    8. explicit MyPushButton(QWidget *parent = nullptr);
    9. ~MyPushButton();
    10. };
    11. #endif // MYPUSHBUTTON_H
    1. #include "mypushbutton.h"
    2. #include
    3. MyPushButton::MyPushButton(QWidget *parent) : QPushButton(parent)
    4. {
    5. qDebug()<<"MyPushButton construct";
    6. }
    7. MyPushButton::~MyPushButton()
    8. {
    9. qDebug()<<"MyPushButton destory";
    10. }

    main函数

    1. #include "mainwindow.h"
    2. #include
    3. int main(int argc, char *argv[])
    4. {
    5. QApplication a(argc, argv);
    6. MainWindow w;
    7. w.show();
    8. return a.exec();
    9. }

    创建mainWindow

    1. #ifndef MAINWINDOW_H
    2. #define MAINWINDOW_H
    3. #include
    4. QT_BEGIN_NAMESPACE
    5. namespace Ui { class MainWindow; }
    6. QT_END_NAMESPACE
    7. class MainWindow : public QMainWindow
    8. {
    9. Q_OBJECT
    10. public:
    11. MainWindow(QWidget *parent = nullptr);
    12. ~MainWindow();
    13. private:
    14. Ui::MainWindow *ui;
    15. };
    16. #endif // MAINWINDOW_H
    1. #include "mainwindow.h"
    2. #include "ui_mainwindow.h"
    3. #include "mypushbutton.h"
    4. #include
    5. MainWindow::MainWindow(QWidget *parent)
    6. : QMainWindow(parent)
    7. , ui(new Ui::MainWindow)
    8. {
    9. qDebug()<<"MainWindow construct";
    10. ui->setupUi(this);
    11. MyPushButton *mybtn=new MyPushButton(this);
    12. mybtn->resize(120,50);
    13. mybtn->setText("MyPushButton");
    14. mybtn->move(200,20);
    15. }
    16. MainWindow::~MainWindow()
    17. {
    18. delete ui;
    19. qDebug()<<"MainWindow destory";
    20. }

    MainWindow类的析构函数中默认已经有了销毁ui的语句,这里又添加了输出语句。
    当MainWindow窗口被销毁时,将输出信息。下面运行程序,然后关闭窗口,在Qt Creator
    的应用程序输出栏中的输出信息为:

     可能有读者问这里为什么是MyPushButton后销毁,难道不应该先销毁MyPushButton,再销毁MainWindows吗?

    因为这里与Qt的实现有关,堆栈信息如下,deleteChildren 在MainWindows的父类QWidget的析构函数中,C++是子类对象先析构,再进行父类对象的析构,而MyPushButton在QWidget的析构函数中调用,所以顺序与我们预想的不一致。

    四、总结

    Qt 引入对象树的概念,在一定程度上解决了内存问题,但是也存在风险。

    比如下面这段代码,程序崩溃了!!!

    1. {
    2. QPushButton quit("Quit");
    3. QWidget window;
    4. quit.setParent(&window);
    5. }

     分析:作为父对象的 window 会首先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说, quit 此时就被析构了。然后,代码继续执行,在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了。

    所以,对于规范的Qt程序,我们在main()函数中将主窗口部件创建在栈上,比如“Widget w;”,而不要在堆上进行创建(使用new操作符),避免内存泄漏。对于子窗口部件,可以使用new操作符在堆上进行创建,同时一定要指定其父部件(parent),这样就不需要再使用delete操作符来销毁该对象了。

    参考:

    QT对象树 - 安静点-- - 博客园

    Qt对象树_不想在北京做开发的博客-CSDN博客_qt 对象树

  • 相关阅读:
    云服务器相比较物理服务器的好处有哪些?
    Linux基础教程:7、文件i/o操作补充
    Android shortcuts快捷方式
    数据结构 哈希表
    通向净零之路:TIWAG子公司TINEXT为INNIO在颜巴赫的主要运营设施提供绿氢
    做区块链卡牌游戏有什么好处?
    高数_第3章重积分_三重积分的奇偶性
    【树莓派】起步(Snappy Ubuntu Core)
    在 Rainbond 上使用 Curve 云原生存储
    ES6~ES13新特性(二)
  • 原文地址:https://blog.csdn.net/sinat_31608641/article/details/126715341