Qt - 内存回收机制
1. 内存回收机制
Qt中有内存回收机制, 但是不是所有被new出的对象被自动回收, 满足条件才可以回收
如果想要在Qt中实现内存的自动回收, 需要满足以下两个条件:
- 创建的对象必须是QObject类的子类(间接子类也可以)
- 创建出的类对象, 必须要指定其父对象是谁, 一般情况下有两种操作方式
对于1:

QObject类是没有父类的, Qt中有很大一部分类都是从这个类派生出去的
Qt中使用频率很高的窗口类和控件都是 QObject 的直接或间接的子类
对于2:
// 方式1: 通过构造函数
// parent: 当前窗口的父对象, 找构造函数中的 parent 参数即可
QWidget::QWidget(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
QTimer::QTimer(QObject *parent = nullptr);
// 方式2: 通过setParent()方法
// 假设这个控件没有在构造的时候指定符对象, 可以调用QWidget的api指定父窗口对象
void QWidget::setParent(QWidget *parent);
void QObject::setParent(QObject *parent);
对于窗口类,QWidget是所有类的父类,使用QWidget *parent 父类指针指向子类对象
对于其他类,QObject 是所有类的父类,使用QObject *parent父类指针指向子类对象
主要就是这两种父类指针,父类指针创建的目的很大程度上就是为了资源回收。
当父对象析构的时候,先把所有的子对象析构,最后在析构父对象。
2. 内存回收本质
当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。当父对象析构的时候,这个列表中的所有对象也会被析构。
任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。
当父对象析构的时候,先把所有的子对象析构,最后在析构父对象。
3. 测试代码
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include "subwindows.h"
#include "mybutton.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
SubWindows* subwin = new SubWindows(this);
subwin->show();
MyButton* btn = new MyButton(subwin,"我的按钮");
btn->show();
}
MainWindow::~MainWindow()
{
delete ui;
qDebug()<<"MainWindow的析构函数"<<endl;
}
mybutton.h
#ifndef MYBUTTON_H
#define MYBUTTON_H
#include <QPushButton>
class MyButton: public QPushButton
{
public:
MyButton(QWidget *parent = 0,QString text ="");
~MyButton();
};
#endif // MYBUTTON_H
mybutton.cpp
#include "mybutton.h"
#include <QDebug>
MyButton::MyButton(QWidget *parent,QString text):QPushButton(parent)
{
this->setFixedSize(80,30);
this->setText(text);
}
MyButton::~MyButton()
{
qDebug()<<"MyButton的析构函数";
}
subwindows.h
#ifndef SUBWINDOWS_H
#define SUBWINDOWS_H
#include <QDialog>
namespace Ui {
class SubWindows;
}
class SubWindows : public QDialog
{
Q_OBJECT
public:
explicit SubWindows(QWidget *parent = 0);
~SubWindows();
private:
Ui::SubWindows *ui;
};
#endif // SUBWINDOWS_H
subwindows.cpp
#include "subwindows.h"
#include "ui_subwindows.h"
#include <QDebug>
SubWindows::SubWindows(QWidget *parent) :QDialog(parent),ui(new Ui::SubWindows)
{
ui->setupUi(this);
}
SubWindows::~SubWindows()
{
delete ui;
qDebug()<<"subwindows的析构函数"<<endl;
}

关闭主窗口:

4. 拓展知识
首先看这个代码
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QPushButton>
#include "mypushbutton.h"
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;
QPushButton* btn1 ;
QPushButton* btn2 ;
QPushButton* btn3 ;
myPushButton* mybtn1;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QHBoxLayout>
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
btn1 = new QPushButton("btn1");
btn2 = new QPushButton("btn2");
btn3 = new QPushButton("btn3");
mybtn1 = new myPushButton("mybtn1");
btn1->setFixedSize(60,36);
btn2->setFixedSize(60,36);
btn3->setFixedSize(60,36);
mybtn1->setFixedSize(60,36);
QHBoxLayout *hlayout = new QHBoxLayout;
hlayout->addWidget(btn1);
hlayout->addWidget(btn2);
hlayout->addWidget(btn3);
hlayout->addWidget(mybtn1);
hlayout->setSpacing(5);
// 创建中央部件并设置布局
QWidget *centralWidget = new QWidget(this);
centralWidget->setLayout(hlayout);
this->setCentralWidget(centralWidget); // 关键步骤!
}
MainWindow::~MainWindow()
{
delete ui;
}
mypushbutton.h
#ifndef MYPUSHBUTTON_H
#define MYPUSHBUTTON_H
#include <QPushButton>
#include <QEvent>
class myPushButton : public QPushButton
{
Q_OBJECT // 必须包含宏以支持信号和槽
public:
// 构造函数
explicit myPushButton(QWidget *parent = nullptr); // 默认构造函数
explicit myPushButton(const QString &text, QWidget *parent = nullptr); // 带文本的构造函数
~myPushButton();
// 可选:自定义功能函数
void setHoverStyle(const QString &style); // 设置悬停样式
protected:
// 覆盖事件处理函数
void enterEvent(QEvent *event) override; // 鼠标进入按钮区域
void leaveEvent(QEvent *event) override; // 鼠标离开按钮区域
private:
QString m_normalStyle; // 正常状态样式
QString m_hoverStyle; // 悬停状态样式
};
#endif // MYPUSHBUTTON_H
mypushbutton.cpp
#include "mypushbutton.h"
#include <QEvent>
#include <QDebug>
// 默认构造函数
myPushButton::myPushButton(QWidget *parent) : QPushButton(parent)
{
// 初始化默认样式
m_normalStyle = "QPushButton { background-color: #4CAF50; color: white; border: none; padding: 8px; border-radius: 4px; }";
m_hoverStyle = "QPushButton:hover { background-color: #45a049; }";
// 应用初始样式
this->setStyleSheet(m_normalStyle + m_hoverStyle);
}
// 带文本的构造函数
myPushButton::myPushButton(const QString &text, QWidget *parent) : QPushButton(text, parent)
{
// 同上,初始化样式
m_normalStyle = "QPushButton { background-color: #4CAF50; color: white; border: none; padding: 8px; border-radius: 4px; }";
m_hoverStyle = "QPushButton:hover { background-color: #45a049; }";
this->setStyleSheet(m_normalStyle + m_hoverStyle);
}
myPushButton::~myPushButton()
{
qDebug()<<"~myPushButton析构函数";
qDebug()<< "myPushButton destroyed:" << this->text();
}
// 设置悬停样式
void myPushButton::setHoverStyle(const QString &style)
{
m_hoverStyle = style;
this->setStyleSheet(m_normalStyle + m_hoverStyle);
}
// 鼠标进入事件
void myPushButton::enterEvent(QEvent *event)
{
Q_UNUSED(event);
// 可以在此处添加额外逻辑(例如播放动画)
this->setCursor(Qt::PointingHandCursor); // 改变鼠标指针形状
}
// 鼠标离开事件
void myPushButton::leaveEvent(QEvent *event)
{
Q_UNUSED(event);
this->unsetCursor(); // 恢复默认鼠标指针
}
在MainWindow的构造函数中创建了四个按钮:三个QPushButton和一个myPushButton实例。这些按钮都被添加到一个水平布局中,而布局设置到了中央部件上。中央部件又被设置为MainWindow的中央部件。此时,用户没有显式地为这些按钮指定父对象,但布局的addWidget函数是否会影响父对象呢?
在 Qt 中,对象的父子关系是内存管理的关键机制。针对你的代码,以下是详细分析:
结论
你的代码目前不会造成内存泄漏,但需要理解其背后的原因。关键点在于:
-
当控件被添加到布局(
hlayout->addWidget(...))时,布局会将这些控件的父对象自动设置为布局所在的父部件(即centralWidget)。 -
当父部件(
centralWidget)被销毁时,Qt 会自动递归销毁其所有子对象(包括按钮)。
因此,即使你在创建 QPushButton 和 myPushButton 时没有显式指定父对象,它们的父对象最终会被正确设置为 centralWidget,从而由 Qt 自动管理内存。
详细分析
1. 控件创建时未指定父对象
你的代码中按钮的创建方式:
btn1 = new QPushButton("btn1"); // 没有指定 parent 参数,默认 parent = nullptr
mybtn1 = new myPushButton("mybtn1"); // 同样 parent = nullptr
此时这些按钮的父对象为 nullptr,理论上需要手动 delete 才能释放内存,否则会导致内存泄漏。
2. 控件被添加到布局后
当调用 hlayout->addWidget(...) 时,布局会执行以下操作:
void QLayout::addWidget(QWidget *widget) {
widget->setParent(parentWidget()); // 关键步骤:将控件的父对象设为布局的父部件
// ...其他操作
}
由于 hlayout 被设置到 centralWidget(通过 centralWidget->setLayout(hlayout)),所有添加到布局中的控件的父对象会被自动设置为 centralWidget。
3. 父部件销毁时的自动清理
当 centralWidget 被销毁时(例如主窗口关闭时),Qt 会自动销毁其所有子对象(包括按钮和布局),因此无需手动释放内存。
代码验证
你可以在 MainWindow 的析构函数中添加调试输出,观察按钮是否被销毁:
MainWindow::~MainWindow()
{
delete ui;
qDebug() << "MainWindow destroyed";
}
// 在 myPushButton 析构函数中添加输出
myPushButton::~myPushButton()
{
qDebug() << "myPushButton destroyed:" << this->text();
}
// 在 QPushButton 子类化(如果允许)或通过事件过滤器验证
运行程序后关闭窗口时,会看到所有按钮的析构函数被调用,证明内存已正确释放。
最佳实践建议
尽管当前代码没有内存泄漏,但为了代码的清晰性和可维护性,建议在创建控件时显式指定父对象:
// 直接指定父对象为 centralWidget
QWidget *centralWidget = new QWidget(this);
btn1 = new QPushButton("btn1", centralWidget); // 显式设置父对象
mybtn1 = new myPushButton("mybtn1", centralWidget);
这样做有以下好处:
-
明确所有权:直观表明控件的生命周期由
centralWidget管理。 -
避免意外错误:防止在布局被设置前控件未被正确关联父对象的情况。
潜在风险
以下情况可能导致内存泄漏:
-
控件未被添加到布局或父部件:如果某个按钮被创建但未添加到布局或设置父对象,则必须手动
delete。 -
动态移除控件:如果通过
removeWidget()从布局中移除控件,需要显式调用delete或重新指定父对象。
总结
-
代码是安全的,因为 Qt 的布局机制自动设置了父对象。
-
显式指定父对象是更好的编程习惯,尤其是在复杂项目中。
-
使用 Qt 的内存管理机制可以有效减少手动
new/delete的错误。

浙公网安备 33010602011771号