qt内存自动释放的两种情况

qt内存管理机制

QObject的parent

我们时常能看到QWidget或者其他的控件的构造函数中有一项参数parent,默认值都为NULL,例如:

cpp 复制代码
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被单击后弹出某个自定义对话框:

cpp 复制代码
button.ConnectClicked(func (_ bool) {
  dialog := NewMyDialog()
  dialog.Exec()
})

因为dialog在close时会被隐藏,而且没有设置DeleteOnClose,所以Qt不会去释放dialog,而用户也无法回收dialog的资源,每次点击按钮后内存用量都会增加一点,没错,内存泄露了。

那么给dialog设置一个parent,像这样,会如何呢?

cpp 复制代码
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:

cpp 复制代码
#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:

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:

cpp 复制代码
#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:

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:

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()
{
}

运行情况:

cpp 复制代码
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!

是通过布局添加时的情况:

cpp 复制代码
#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");
}

运行情况:

cpp 复制代码
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!

总结

通过上面实验可知,成为子控件,或者成为布局中的控件,都会跟着释放。

如图所示:

相关推荐
白茶等风121384 分钟前
C#_结构(Struct)详解
开发语言·c#
ephemerals__7 分钟前
【c++】STL简介
开发语言·c++
赤橙红的黄36 分钟前
代理模式-动态代理
java·开发语言·代理模式
Au_ust39 分钟前
go的结构体、方法、接口
开发语言·golang
-VE-1 小时前
模板初阶(c++)
开发语言·c++
shigen011 小时前
结合HashMap与Java 8的Function和Optional消除ifelse判断
java·开发语言
CN.LG1 小时前
浅谈Python之协程
开发语言·python
莫叫石榴姐1 小时前
一份热乎的阿里25届数据分析面试题
数据库·数据仓库·sql·算法·数据挖掘·数据分析
国中之林1 小时前
【C++ Primer Plus习题】17.5
开发语言·c++·学习·刷题
cong*2 小时前
数据库提权【笔记总结】
数据库·笔记·adb