在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
- #ifndef MYPUSHBUTTON_H
- #define MYPUSHBUTTON_H
-
- #include
-
- class MyPushButton : public QPushButton
- {
- Q_OBJECT
- public:
- explicit MyPushButton(QWidget *parent = nullptr);
- ~MyPushButton();
- };
-
- #endif // MYPUSHBUTTON_H
- #include "mypushbutton.h"
- #include
-
- MyPushButton::MyPushButton(QWidget *parent) : QPushButton(parent)
-
- {
- qDebug()<<"MyPushButton construct";
- }
-
- MyPushButton::~MyPushButton()
-
- {
- qDebug()<<"MyPushButton destory";
- }
-
main函数
- #include "mainwindow.h"
- #include
-
- int main(int argc, char *argv[])
- {
- QApplication a(argc, argv);
- MainWindow w;
- w.show();
- return a.exec();
- }
创建mainWindow
- #ifndef MAINWINDOW_H
- #define MAINWINDOW_H
-
- #include
-
-
- QT_BEGIN_NAMESPACE
- namespace Ui { class MainWindow; }
- QT_END_NAMESPACE
-
- class MainWindow : public QMainWindow
- {
- Q_OBJECT
-
- public:
- MainWindow(QWidget *parent = nullptr);
- ~MainWindow();
-
- private:
- Ui::MainWindow *ui;
- };
- #endif // MAINWINDOW_H
- #include "mainwindow.h"
- #include "ui_mainwindow.h"
- #include "mypushbutton.h"
-
- #include
-
- MainWindow::MainWindow(QWidget *parent)
- : QMainWindow(parent)
- , ui(new Ui::MainWindow)
- {
- qDebug()<<"MainWindow construct";
-
- ui->setupUi(this);
-
- MyPushButton *mybtn=new MyPushButton(this);
-
- mybtn->resize(120,50);
-
- mybtn->setText("MyPushButton");
-
- mybtn->move(200,20);
- }
-
-
- MainWindow::~MainWindow()
- {
- delete ui;
- qDebug()<<"MainWindow destory";
- }
-
MainWindow类的析构函数中默认已经有了销毁ui的语句,这里又添加了输出语句。
当MainWindow窗口被销毁时,将输出信息。下面运行程序,然后关闭窗口,在Qt Creator
的应用程序输出栏中的输出信息为:

可能有读者问这里为什么是MyPushButton后销毁,难道不应该先销毁MyPushButton,再销毁MainWindows吗?
因为这里与Qt的实现有关,堆栈信息如下,deleteChildren 在MainWindows的父类QWidget的析构函数中,C++是子类对象先析构,再进行父类对象的析构,而MyPushButton在QWidget的析构函数中调用,所以顺序与我们预想的不一致。

Qt 引入对象树的概念,在一定程度上解决了内存问题,但是也存在风险。
比如下面这段代码,程序崩溃了!!!
- {
- QPushButton quit("Quit");
-
- QWidget window;
-
- quit.setParent(&window);
- }
分析:作为父对象的 window 会首先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说, quit 此时就被析构了。然后,代码继续执行,在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了。
所以,对于规范的Qt程序,我们在main()函数中将主窗口部件创建在栈上,比如“Widget w;”,而不要在堆上进行创建(使用new操作符),避免内存泄漏。对于子窗口部件,可以使用new操作符在堆上进行创建,同时一定要指定其父部件(parent),这样就不需要再使用delete操作符来销毁该对象了。
参考: