一 说明
在qt代码中,我们经常给类的构造函数传入父指针,这个有什么用?类实例化后又是在什么时候释放的?
同样很多人担心,在构造函数中new对象,如果析构函数中不释放会不会造成内存泄漏?为什么我看很对按钮等控件new后都没有delete?所以我自己new的到底需不需要手动释放?
为了避免代码运行中出现内存泄漏,规范编程习惯,下面做一个验证。
其次也是对qt中对象树做一些了解,qt对象树学习文章:https://zhuanlan.zhihu.com/p/43523879
对象树在 GUI 编程中是非常非常有用的!当父对象被析构时子对象也会被析构,这样一定程度上简化了内存回收机制。
二 测试类设计
使用定时器打印信息,这样表示类实例化后是否被释放。
h文件
#ifndef TESTCLASS_H
#define TESTCLASS_H
#include <QObject>
#include <QTimer>
class TestClass : public QObject
{
Q_OBJECT
public:
explicit TestClass(QObject *parent = nullptr,QString info = "");
signals:
public slots:
private:
QString myInfo;
};
#endif // TESTCLASS_H
cpp文件
#include "testclass.h"
#include <qDebug>
TestClass::TestClass(QObject *parent,QString info) :
QObject(parent),
myInfo(info)
{
QTimer *time = new QTimer(this);
connect(time,&QTimer::timeout,this,[=](){
qDebug()<<Q_FUNC_INFO<<myInfo;
});
time->start(1000);
}
三 测试代码设计
使用mainwindows中加入一个按钮,使用按钮控制From类的创建与释放,然后再From类中使用各种方式创建TestClass类。
1 mainwindow类设计
h文件
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "form.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
Form *win;
};
#endif // MAINWINDOW_H
cpp文件
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
win = NULL;
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
if(win != NULL)
{
delete win;
win = NULL;
}
else
{
win = new Form();
win->show();
}
}
2 From类测试的实现
h文件
#ifndef FORM_H
#define FORM_H
#include <QWidget>
#include "testclass.h"
namespace Ui {
class Form;
}
class Form : public QWidget
{
Q_OBJECT
public:
explicit Form(QWidget *parent = 0);
~Form();
private:
Ui::Form *ui;
TestClass c;
};
#endif // FORM_H
cpp文件
#include "form.h"
#include "ui_form.h"
Form::Form(QWidget *parent) :
QWidget(parent),
ui(new Ui::Form),
c(NULL,"C") //设置父对象无效,this对象释放的时候,c依据被释放
{
ui->setupUi(this);
TestClass *a = new TestClass(this,"A"); //等this对象(From)释放时释放
TestClass *b = new TestClass(nullptr,"B"); //需要等进程关闭后,系统自动回收
TestClass *e = new TestClass(parent,"E"); //需要等parent对象(Mainwindow)释放时释放
}
Form::~Form()
{
delete ui;
}
四 测试结论
- new实例化,传入父类指针,随着父类指针释放的时候释放,子类前于父类执行析构函数。
- new实例化,传入父指针为空,即无父对象时,需要手动释放,即使用delete(一般放在析构函数中),否则需要等待进程关闭后系统回收(此情况为内存泄漏)。
- 类成员子类,实例化在构造的时候,释放的方式无法通过修改父对象修改,即使后期将父对象改为空。如:在From释放的时候,From的成员子类c,虽然后面改了c的父对象,在From对象释放的时候,c依据被释放。 原因分析: c作为成员,c的内存段在From内存范围内,在From释放后,其所有内存都要被释放,所以c也一定会被释放。
在From类代码的注释中也有体现,如果有兴趣的可以使用代码跑一下体验体验。
五 注意点
标准 C++ (ISO/IEC 14882:2003)要求,局部对象的析构顺序应该按照其创建顺序的相反过程。
如果函数内部执行如下代码会导致内存错误:
QPushButton push("Quit");
QWidget widget;
push.setParent(&widget);
在上面的代码中,作为父对象的 widget会首先被析构,因为它是最后一个创建的对象。
父对象widget会调用子对象列表中每一个对象的析构函数,也就是说, push此时就被析构了。然后,代码继续执行,在 widget析构之后,push也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了。