qt-C++语法笔记之Qt中的delete ui、ui的本质与Q_OBJECT

code review!
文章目录
- [qt-C++语法笔记之Qt中的delete ui、ui的本质与Q_OBJECT](#qt-C++语法笔记之Qt中的delete ui、ui的本质与Q_OBJECT)
-
- [一、`Ui::MainWindow` 不是 QObject](#一、
Ui::MainWindow不是 QObject) - [二、为什么必须手动 `delete ui`](#二、为什么必须手动
delete ui) - [三、Q_OBJECT 宏的作用](#三、Q_OBJECT 宏的作用)
- [四、使用 Q_OBJECT 的必要条件](#四、使用 Q_OBJECT 的必要条件)
- 五、总结对比
- [一、`Ui::MainWindow` 不是 QObject](#一、
一、Ui::MainWindow 不是 QObject
这是很多初学者容易误解的地方。来看典型的 Qt Creator 生成代码:
cpp
// mainwindow.h
namespace Ui {
class MainWindow; // 前向声明,由 uic 工具从 .ui 文件自动生成
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui; // 注意:这是 Ui:: 命名空间下的类
};
uic 工具生成的 Ui::MainWindow 类大致长这样(在 ui_mainwindow.h 中):
cpp
// ui_mainwindow.h (自动生成,简化版)
namespace Ui {
class MainWindow // ← 普通 C++ 类,没有继承 QObject!
{
public:
QMenuBar *menubar;
QStatusBar *statusbar;
QPushButton *pushButton;
// ... 其他控件指针
void setupUi(QMainWindow *MainWindow)
{
// 创建所有控件,并将它们 parent 到 MainWindow
menubar = new QMenuBar(MainWindow);
pushButton = new QPushButton(centralwidget);
// ...
}
void retranslateUi(QMainWindow *MainWindow) { /* 翻译相关 */ }
};
} // namespace Ui
关键点:Ui::MainWindow 是一个纯粹的 C++ 类(POD-like),不继承 QObject,不参与 Qt 的对象树。
二、为什么必须手动 delete ui
cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow) // 普通 new,不进入 Qt 对象树
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui; // 必须手动 delete!
}
原因如下:
ui 本身 不是 QObject,没有 parent,Qt 的父子对象树机制管不到它,所以必须手动 delete。
ui 内部的控件 (如 pushButton、menubar)是 QObject/QWidget,它们在 setupUi() 中被设置了 parent。当 MainWindow 析构时,Qt 对象树会自动销毁这些子控件。
所以析构时的顺序是:
MainWindow::~MainWindow() 被调用
├── delete ui; // 手动释放 ui 这个"指针容器"
└── ~QMainWindow() 自动调用 // Qt 对象树自动 delete 所有子 QObject
├── delete menubar;
├── delete statusbar;
├── delete pushButton;
└── ...
delete ui 只是释放了那个存放指针的"壳",并不会 double-free 控件,因为 Ui::MainWindow 的析构函数是默认的(不 delete 成员指针)。
三、Q_OBJECT 宏的作用
cpp
class MainWindow : public QMainWindow
{
Q_OBJECT // ← 这个宏
// ...
};
Q_OBJECT 宏展开后大致包含:
cpp
#define Q_OBJECT \
public: \
static const QMetaObject staticMetaObject; \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
QT_TR_FUNCTIONS \
private: \
Q_OBJECT_NO_OVERRIDE_WARNING \
static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
它为类启用了 Qt 元对象系统 ,使 moc(Meta-Object Compiler)为该类生成额外代码,从而支持以下功能:
信号与槽 是最常见的用途。没有 Q_OBJECT,signals 和 slots 无法工作:
cpp
class MyClass : public QObject
{
Q_OBJECT
signals:
void dataChanged(int value); // 需要 Q_OBJECT
public slots:
void onUpdate(); // 需要 Q_OBJECT
};
qobject_cast 动态转换 ,比 dynamic_cast 更快且不依赖 RTTI:
cpp
QObject *obj = getObject();
MyClass *my = qobject_cast<MyClass*>(obj); // 需要 Q_OBJECT
属性系统 (Q_PROPERTY)、国际化 (tr())等也依赖它。
四、使用 Q_OBJECT 的必要条件
声明了 Q_OBJECT 的类必须满足:
1. 直接或间接继承 QObject
2. Q_OBJECT 宏放在 class 体内的 private 区域(通常在最前面)
3. 头文件被 moc 处理(在 .pro 中 HEADERS += 或 CMake 中开启 AUTOMOC)
常见错误 :添加 Q_OBJECT 后忘记重新运行 qmake/cmake,导致链接错误:
undefined reference to `vtable for MyClass'
解决方法是清理项目并重新构建(重新运行 qmake 或 cmake)。
五、总结对比
| 对象 | 是否 QObject | 谁管理生命周期 | 是否需要 Q_OBJECT |
|---|---|---|---|
MainWindow |
✅ 是(继承 QMainWindow) | Qt 对象树(有 parent 时)或手动 | ✅ 需要 |
Ui::MainWindow(即 ui 指向的对象) |
❌ 不是 | 必须手动 delete |
❌ 不需要 |
ui->pushButton 等控件 |
✅ 是 | Qt 对象树自动管理 | 通常不需要(除非自定义子类用信号槽) |
一句话总结:ui 是个普通 C++ 对象,只是一个装满控件指针的"工具袋",不参与 Qt 对象树,所以必须手动 delete;而 Q_OBJECT 是为继承了 QObject 的类启用元对象系统(信号槽等)的宏。