在Qt中,可以把对象分为两类,一类是QObject类及其派生类;另一类则是普通的C++类。
若是普通C++类则需要开发者自行管理,若是Qt的QObject类或其子类,那么Qt则拥有自己的内存回收管理机制,下面将进行简单介绍。
一 QObject的parent设置为NULL
如果对象在构造时直接指定了NULL,这说明当前实例无父对象存在,Qt也不能自动析构该实例,除非实例超出作用域导致析构函数被调用,然后使用deleteLater方法删除对象;
对于窗口基类QWidget,parent为NULL时代表其为一个顶层窗口,当parent不为NULL时他会显示在父widget中心区域的上层;如果QWidget的parent为NULL或是其他值,在其加入布局管理器或者QMainWindow设置widget时,会自动将parent设置为相应的父widget,在父控件销毁时这些子控件以及布局管理器对象会一并销毁。所有QWidget加入布局管理器或者QMainWindow设置widget时,就不需要手动释放其内存了,否则二次释放会崩溃。
对于在局部作用域上创建的父对象及其子对象,要注意对象销毁的顺序,因为父对象销毁时也会销毁子对象,当子对象会在父对象之后被销毁时会引发double free。
二 QObject的parent设置不为NULL
根据Qt的内存内存管理规则,QObject及其派生类的对象,如果其parent非0,那么其parent析构时会析构该对象。
Qt在管理对象时,会在底层依据对象的层级关系建议对象树,当该树上的某个分支节点或根节点对象使用完成后,Qt便会按照内存管理规则依次回收叶子节点、父节点对象所占用的内存,最后之直至回收到根节点对象。
设置窗口的Qt::WA_DeleteOnClose熟悉,当窗口销毁时,会全部回收与窗口相关的所有资源,包括启动的后台线程,这个熟悉在日常开发中,非常容易被忽略,这里提出来跟大家进行交流。
setAttribute(Qt::WA_DeleteOnClose); //当你想让关闭窗口的时候,让窗口销毁,在构造函数中设置Qt::WA_DeleteOnClose标志,对于单纯显示而不需要和父控件做交互的widget,直接设置DeleteOnClose即可,close时widget会被自动析构。
三 专用于容器内存回收的qDeleteAll和clear
qDeleteAll,专门用于指针容器,对容器或者迭代器中的每个对象进行delete操作,而不是从容器中移除对象。
四 Qt的智能指针
也可以使用QPointer、QScopedPointer、QSharedPointer和QWeakPointer进行内存管理。
五 对象引用计数管理内存
在对象被引用时,采用计数的方式进行管理,当对象被引用一次则计数加一,若解除引用则计数减一,直到最后计数为0则说明无任何引用,此时便可以放心回收内存了。
一般控件使用代码示例:
#include "mainwindow.h"
#include <QDoubleValidator>
#include <QLabel>
#include <QLineEdit>
#include<QPushButton>
#include<QVBoxLayout>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
resize(800,800);
setWindowTitle("window point");
QDoubleValidator* fValidator = new QDoubleValidator();
QWidget * widget = new QWidget(this);
QVBoxLayout * mainLayout = new QVBoxLayout(); //不指定父节点,当被其他控件包含时,Qt自动指定父节点。
QHBoxLayout * horLayout = new QHBoxLayout();
QVBoxLayout * vctLayout = new QVBoxLayout();
//
QLabel * _label = new QLabel("Please Enter Coordinate:");
horLayout->addSpacing(20);
horLayout->addWidget(_label);
//
QLabel * x_label = new QLabel("x:");
QLabel * y_label = new QLabel("y:");
QLabel * z_label = new QLabel("z:");
QLineEdit * x_edit = new QLineEdit();
x_edit->setValidator(fValidator);
QLineEdit * y_edit = new QLineEdit();
y_edit->setValidator(fValidator);
QLineEdit * z_edit = new QLineEdit();
z_edit->setValidator(fValidator);
QHBoxLayout * xLayout = new QHBoxLayout();
xLayout->addSpacing(20);
xLayout->addWidget(x_label);
xLayout->addWidget(x_edit);
QHBoxLayout * yLayout = new QHBoxLayout();
yLayout->addSpacing(20);
yLayout->addWidget(y_label);
yLayout->addWidget(y_edit);
QHBoxLayout * zLayout = new QHBoxLayout();
zLayout->addSpacing(20);
zLayout->addWidget(z_label);
zLayout->addWidget(z_edit);
vctLayout->addLayout(xLayout);
vctLayout->addLayout(yLayout);
vctLayout->addLayout(zLayout);
mainLayout->addLayout(horLayout);
mainLayout->addLayout(vctLayout);
mainLayout->addStretch();//在下方添加一个弹簧填空
widget->setLayout(mainLayout);
//当widget回收时,其下面挂载的所有布局、控件均会被依次回收!
this->setCentralWidget(widget);
}
MainWindow::~MainWindow()
{
}
Qt 的智能指针包括:
QSharedPointer
QScopedPointer
QScopedArrayPointer
QPointer
QSharedDataPointer
QWeakPointer
Qt智能指针使用示例:
QSharedPointer
关键代码:
#include "mainwindow.h"
#include "myobject.h"
#include "qdebug.h"
#include <QSharedPointer>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/*
QSharedPointer 是一个共享指针,它与 QScopedPointer 一样包装了new操作符在堆上分配的动态对象,
但它实现的是引用计数型的智能指针 ,也就是说,与QScopedPointer不同的是,QSharedPointer可以被自由地拷贝和赋值,
在任意的地方共享它,所以QSharedPointer也可以用作容器元素。
所谓的计数型指针,就是说在内部QSharedPointer对拥有的内存资源进行引用计数,
比如有3个QSharedPointer同时指向一个内存资源,那么就计数3,知道引用计数下降到0,那么就自动去释放内存啦。
*/
//使用智能指针,
QSharedPointer<MyOBject> obj =QSharedPointer<MyOBject>(new MyOBject, &QObject::deleteLater);
obj->print(); //使用智能指针对象调用类方法
}
MainWindow::~MainWindow()
{
qDebug()<<__FUNCTION__;
}
程序运行后的结果:可以看到,程序运行后,自动释放了MyOBject对象所占用的空间。
QScopedPointer
关键代码:
#include "mainwindow.h"
#include "myobject.h"
#include "qdebug.h"
#include <QSharedPointer>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
//使用智能指针QScopedPointer
/*
QScopedPointer的使用原理比较简单,实际上就是通过QScopedPointer类型,记录申请的某一片内存空间的地址,
在QScopedPointer类型变量生命周期结束时,会自动调用QScopedPointer的析构函数,从而达到自动释放堆上申请的内存空间的目的。
*/
QScopedPointer<MyOBject> sp=QScopedPointer<MyOBject>(new MyOBject);
sp->print();
}
MainWindow::~MainWindow()
{
qDebug()<<__FUNCTION__;
}
运行结果:智能指针自动回收了内存
QScopedArrayPointer
//使用智能指针QScopedArrayPointer: 主要用于数组对象,当数组对象使用完成后,智能指针自动回收内存!
QScopedArrayPointer<MyOBject> arrayPointer(new MyOBject[10]);
arrayPointer[0].print();
arrayPointer[5].print();
运行结果:我们新建了10对象,所有析构了十次,逐一回收了内存。
QPointer
//使用QPointer智能指针:当其指向的对象(必须是QObject及其派生类)被销毁时,它会被自动置NULL,避免野指针的出现
QPointer<MyOBject> p=new MyOBject;
p->print();
delete p; //当删除指针或自动回收后,QPoint会自动把p指针设为NULL, 避免野指针的出现
if(p==NULL) qDebug()<<"p is NULL";
//如果没有使用QPoint进行包裹,那么指针将不会自动指向NULL
MyOBject *p2=new MyOBject;
delete p2;
if(p2==NULL) qDebug()<<"p2 is NULL";
else qDebug()<<"p2 not is NULL";
运行效果:
QSharedDataPointer
参考博客:
Qt实现一个隐式共享类(使用QSharedDataPointer)
QWeakPointer
参考博客:
Qt智能指针–QWeakPointer
总结
- 当我们所使用的对象属于QObject或QObject的子类时,Qt框架会自动回收内存,不用我们操心;
- 当我们所使用的对象不属于QObject或QObject的子类,而是普通的C++类,那么我们就需要根据实际情况在合适的时机进行内存回收,通常可以采用的方式有: 析构函数回收内存、合适的时机直接delete、使用C++提供的智能指针等方式。
- 软件开发中,常说的内存通常是指栈内存和堆内存,栈内存有编译器根据对象的作用域不同进行自动回收,不需要我们操行。而上面所说的内存管理均指堆内存。
所谓堆内存,就是可以动态申请的内存,使用起来非常方便,但是随之而来的问题就是需要开发人员自己回收,上面的Qt框架已经帮我们做了很多工作内存管理方面的工作。 - 对象引用计数是一种通用的内存管理措施,凡是涉及内存管理的地方基本上都是使用这种方式进行管理。
下一篇博客:
Qt6教程之三(12) 文件管理
上一篇博客
Qt6教程之三(10) 异步与并发编程