qt内存管理机制
QObject的parent
我们时常能看到QWidget或者其他的控件的构造函数中有一项参数parent,默认值都为NULL,例如:
QLineEdit(const QString &contents, QWidget *parent = nullptr);
QWidget(QWidget *parent = nullptr, Qt::WindowFlags f = ...);
这个parent的作用就在于使当前的对象实例加入parent指定的QObject及其派生类的children中,当一个QObject被delete或者调用了它的析构函数时,所有加入的children也会全部被析构。
如果parent设置为NULL,会有如下的情况:
1、如果是构造时直接指定了NULL,那么当前实例不会有父对象存在,Qt也不能自动析构该实例除非实例超出作用域导致析构函数被调用,或者用户在恰当的实际使用delete操作符或者使用deleteLater方法;
2、如果已经指定了非NULL的parent,这时将它设置成了NULL,那么当前实例会从父对象的children中删除,不再受到QObject & parent机制的影响;
对于QWidget,parent为NULL时代表其为一个顶层窗口,也可以就是独立于其他widget在系统任务栏单独出现的widget,对于永远都是顶层窗口的widget,例如QDialog,当parent不为NULL时他会显示在父widget中心区域的上层;
3、如果QWidget的parent为NULL或是其他值,在其加入布局管理器或者QMainWindow设置widget时,会自动将parent设置为相应的父widget,在父控件销毁时这些子控件以及布局管理器对象会一并销毁。
所以我们可以看出,QObject对象实际上拥有一颗类实例关系树,在树中保存了所有通过指定parent注册的子对象,而子对象里又保存有其子对象的关系树,所以当一个父对象被销毁时,所有依赖或间接依赖于它的对象都会被正确的释放,使用者无需手动管理这些资源的释放操作。
内存使用建议
基于此原理,我们可以放心的让Qt管理资源,这里有几个建议:
对于QObject及其派生类,如果彼此之间存在一定联系,则应该尽量指定parent,对于QWidget应该指定parent或者加入布局管理器由管理器自动设置parent。
对象只需要在局部作用域存在时可以选择不进行内存分配,利用局部作用域变量的生命周期自动清理资源。
对于非QWidget的对象来说,如果不指定非NULLparent,则需要自己管理对象资源。QWidget比较特殊,我们在下一节讲解。
对于在局部作用域上创建的父对象及其子对象,要注意对象销毁的顺序,因为父对象销毁时也会销毁子对象,当子对象会在父对象之后被销毁时会引发double free。
QWidget和内存的释放
QWidget也是QObject的子类,所以在parent机制上是没有区别的,然而实际使用时我们更多的是使用“关闭”(close)而不是delete去删除控件,所以差异就出现了。
先提一下widget关闭的流程,首先用户触发close()槽,然后Qt向widget发送QCloseEvent,默认的QCloseEvent会做如下处理:
将widget隐藏,也就是hide();
如果有设置Qt::WA_DeleteOnClose,那么会接着调用widget的析构函数
我们可以看到,widget的关闭实际是将其隐藏,而没有释放内存,虽然我们有时会重写closeEvent但也不会手动释放widget。
看一个因为close机制导致的内存泄漏的例子,我们在button被单击后弹出某个自定义对话框:
button.ConnectClicked(func (_ bool) {dialog := NewMyDialog()dialog.Exec()
})
因为dialog在close时会被隐藏,而且没有设置DeleteOnClose,所以Qt不会去释放dialog,而用户也无法回收dialog的资源,每次点击按钮后内存用量都会增加一点,没错,内存泄露了。
那么给dialog设置一个parent,像这样,会如何呢?
dialog.SetParent(self)
遗憾的是,并没有什么区别,因为这样只是把dialog加入父控件的children,并没有删除dialog,只有父对象被销毁时内存才会真正释放。
解决办法也有三个。
第一种是使用deleteLater,例如:
dialog.DeleteLater()
这会通知Qt的eventloop在下次进入主循环的时候析构dialog,这样一来确实解决了内存泄露,不过缺点是会有不可预测的延迟存在,有时候延迟是难以接受的。
第二种是手动删除widget,适用于parent为NULL的场合:
C++:
delete dialog;
说明一下,调用底层C++对象的析构函数。
第三种比较简单,对于单纯显示而不需要和父控件做交互的widget,直接设置DeleteOnClose即可,close时widget会被自动析构。
总结
Qt提供了一套方便的机制帮助我们进行内存和资源管理,使我们从繁重的劳动中得到了部分的解放,但同时也要注意到那些很容易坑,这样才能写出健壮的正确执行的程序。
具体哪种情况自动释放呢,先程序验证一下:
代码情况:
myLabel.h:
#ifndef MYLABEL_H
#define MYLABEL_H
#include <QWidget>
#include <QLabel>
class MyLabel : public QLabel
{Q_OBJECT
public:explicit MyLabel(QWidget *parent = nullptr);~MyLabel();
signals:
public slots:
};
#endif // MYLABEL_H
myLabel.cpp:
#include "myLabel.h"
MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{qDebug("enter function MyLabel::MyLabel");qDebug("exit function MyLabel::MyLabel");
}
MyLabel::~MyLabel()
{qDebug("enter function MyLabel::~MyLabel");qDebug("exit function MyLabel::~MyLabel");
}
main.cpp:
#include "widget.h"
#include <QApplication>
#include <QDebug>
#pragma execution_character_set("utf-8")
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{qDebug("program begin!");QApplication a(argc, argv);Widget *w = new Widget();w->resize(300,400);w->show();delete w;qDebug("program end!");return a.exec();
}
myVBoxLayout.h:
#ifndef MYVBOXLAYOUT_H
#define MYVBOXLAYOUT_H
#include <QWidget>
#include <QVBoxLayout>
class MyVBoxLayout : public QVBoxLayout
{Q_OBJECT
public:explicit MyVBoxLayout(QWidget *parent = nullptr);~MyVBoxLayout();
signals:
public slots:
};
#endif // MYVBOXLAYOUT_H
myVBoxLayout.cpp:
#include "myVBoxLayout.h"
MyVBoxLayout::MyVBoxLayout(QWidget *parent) : QVBoxLayout(parent)
{qDebug("enter function MyVBoxLayout::MyVBoxLayout");qDebug("exit function MyVBoxLayout::MyVBoxLayout");
}
MyVBoxLayout::~MyVBoxLayout()
{qDebug("enter function MyVBoxLayout::~MyVBoxLayout");qDebug("exit function MyVBoxLayout::~MyVBoxLayout");
}
是父子关系时的情况:
widget.cpp:
#include "widget.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <myLabel.h>
Widget::Widget(QWidget *parent): QWidget(parent)
{MyLabel *label1 = new MyLabel(this);label1->setText("this is test label1");MyLabel *label2 = new MyLabel(this);label2->setText("this is test label2");
// QVBoxLayout *v_layout_main = new QVBoxLayout(this);
// //this->setLayout(v_layout_main);
// v_layout_main->addWidget(label1);
// //v_layout_main->addWidget(label2);
// QHBoxLayout *h_layout_buttons = new QHBoxLayout(this);
// h_layout_buttons->addWidget(label2);
}
Widget::~Widget()
{
}
运行情况:
program begin!
enter function MyLabel::MyLabel
exit function MyLabel::MyLabel
enter function MyLabel::MyLabel
exit function MyLabel::MyLabel
1127, FromFilemap
enter function MyLabel::~MyLabel
exit function MyLabel::~MyLabel
enter function MyLabel::~MyLabel
exit function MyLabel::~MyLabel
program end!
是通过布局添加时的情况:
#include "widget.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include "myLabel.h"
#include "myVBoxLayout.h"
Widget::Widget(QWidget *parent): QWidget(parent)
{
// MyLabel *label1 = new MyLabel(this);
// label1->setText("this is test label1");
// MyLabel *label2 = new MyLabel(this);
// label2->setText("this is test label2");
// MyLabel *label1 = new MyLabel(this);
// label1->setText("this is test label1");
// MyLabel *label2 = new MyLabel(this);
// label2->setText("this is test label2");
// QVBoxLayout *v_layout_main = new QVBoxLayout(this);
// this->setLayout(v_layout_main);
// v_layout_main->addWidget(label1);
// v_layout_main->addWidget(label2);
// QHBoxLayout *h_layout_buttons = new QHBoxLayout(this);
// h_layout_buttons->addWidget(label2);MyLabel *label1 = new MyLabel();label1->setText("this is test label1");MyLabel *label2 = new MyLabel();label2->setText("this is test label2");MyVBoxLayout *v_layout_main = new MyVBoxLayout();this->setLayout(v_layout_main);v_layout_main->addWidget(label1);v_layout_main->addWidget(label2);
}
Widget::~Widget()
{qDebug("enter function Widget::~Widget");qDebug("exit function Widget::~Widget");
}
运行情况:
program begin!
enter function MyLabel::MyLabel
exit function MyLabel::MyLabel
enter function MyLabel::MyLabel
exit function MyLabel::MyLabel
enter function MyVBoxLayout::MyVBoxLayout
exit function MyVBoxLayout::MyVBoxLayout
1127, FromFilemap
enter function Widget::~Widget
exit function Widget::~Widget
enter function MyVBoxLayout::~MyVBoxLayout
exit function MyVBoxLayout::~MyVBoxLayout
enter function MyLabel::~MyLabel
exit function MyLabel::~MyLabel
enter function MyLabel::~MyLabel
exit function MyLabel::~MyLabel
program end!
总结
通过上面实验可知,成为子控件,或者成为布局中的控件,都会跟着释放。
如图所示: