Qt 的对象模型在 C++ 对象基础上加入元对象、信号连接、父子所有权和事件处理。先区分类的继承关系与对象之间的 parent 关系:前者决定类型能力,后者描述某些实例的归属和销毁责任,两者不是同一棵树。
常用类的直接基类
跳转到“常用类的直接基类”旧表把许多间接祖先写成直接基类,容易误解类层次。下面保留原来的 14 个类及用途,并写出实际的直接继承关系。
| 类 | 直接基类 | 主要用途 |
|---|---|---|
QObject | 无 | 对象树、元对象、信号连接和事件处理的基础 |
QApplication | QGuiApplication | Widgets 应用的事件循环与应用级管理 |
QWidget | QObject、QPaintDevice | Widgets 控件/窗口的共同基础,也是绘图设备 |
QMainWindow | QWidget | 主窗口,组织中央控件、菜单、工具栏、状态栏等 |
QDialog | QWidget | 模态或非模态对话框 |
QPushButton | QAbstractButton | 普通按钮 |
QLabel | QFrame | 文本或图片显示 |
QLineEdit | QWidget | 单行文本输入 |
QTextEdit | QAbstractScrollArea | 多行文本与富文本编辑 |
QComboBox | QWidget | 下拉选择/可编辑组合框 |
QCheckBox | QAbstractButton | 复选框 |
QRadioButton | QAbstractButton | 单选按钮 |
QSlider | QAbstractSlider | 滑动取值 |
QProgressBar | QWidget | 任务进度显示 |
QApplication → QGuiApplication → QCoreApplication → QObject 是逐级基类链。QAbstractButton、QAbstractSlider、QFrame 的直接基类都是 QWidget;QAbstractScrollArea 则继承 QFrame。因此表中控件最终都具有 QWidget 能力,但不能省略中间层后仍称其为“直接继承”。Qt 类层次
一个 Widgets 程序应在创建控件前构造一个 QApplication,并让它比控件活得更久。没有界面的程序通常使用 QCoreApplication;不能因为 QApplication 最终继承 QObject,就把三种应用类视为可随意互换。
parent 管理对象,普通指针只提供访问
跳转到“parent 管理对象,普通指针只提供访问”把 QObject* parent 传入子对象构造函数或调用 setParent(),会建立对象树。父对象销毁时会销毁仍属于它的子对象;子对象提前销毁时会从父对象中移除。局部指针变量消失则不会主动删除其指向的 QObject。
#include <QApplication>#include <QLabel>#include <QPointer>#include <QPushButton>#include <QVBoxLayout>#include <QWidget>#include <cassert>#include <type_traits>
int main(int argc, char** argv) { QApplication app(argc, argv); static_assert(!std::is_copy_constructible_v<QObject>); QPointer<QPushButton> observer; { QWidget window; auto* layout = new QVBoxLayout(&window); auto* label = new QLabel(QStringLiteral("Ready"), &window); auto* button = new QPushButton(QStringLiteral("Start"), &window); layout->addWidget(label); layout->addWidget(button); observer = button; assert(button->parentWidget() == &window); assert(window.findChildren<QPushButton*>().size() == 1); } assert(observer.isNull()); // window 已销毁其动态分配的子按钮
{ QWidget parent; // 先构造父对象 QPushButton child(&parent); // 后构造栈子对象 assert(child.parent() == &parent); } // child 先析构并移除自己,再析构 parent}QPointer 是会在被观察 QObject 销毁时清空的借用指针,不拥有该对象,也不能让跨线程并发删除自动安全。对象树示例遵循同一 GUI 线程内的寿命规则。QObject 所有权、QPointer
如果先构造栈子对象、后构造父对象,再把子对象挂到父对象下面,析构顺序就会变成父对象先尝试删除栈子对象;此后作用域结束还会再次析构它。不能以“Qt 会管理 parent”忽略 C++ 自动对象的销毁顺序。
QObject 不可复制,Q_OBJECT 由元对象编译器处理
跳转到“QObject 不可复制,Q_OBJECT 由元对象编译器处理”QObject 和常见 QObject 派生类不支持按值复制,通常通过指针、引用或对象身份交互。应通过父子所有权、明确的 RAII 所有权或其他单一约定管理释放,避免 parent 与另一个无协调所有者同时负责删除同一对象。
派生类需要声明自己的信号、属性等元对象功能时,通常应添加 Q_OBJECT,并确保构建启用 moc。成员函数指针形式的 connect 可以连接普通成员函数,不要求每个接收方法都写在 slots: 下。完整可运行的声明和构建示例见 自定义信号与信号转发。
对象所属线程与 C++ 调用关系又是另一条边界:直接调用 QObject 的方法仍在调用者线程执行。parent 和 child 必须满足 Qt 线程归属约束,Widgets 也应在 GUI 线程使用;跨线程通信规则见 信号连接与线程。