跳转到内容
新建笔记

Qt 对象模型:类层次、parent 所有权与元对象

Qt 的对象模型在 C++ 对象基础上加入元对象、信号连接、父子所有权和事件处理。先区分类的继承关系与对象之间的 parent 关系:前者决定类型能力,后者描述某些实例的归属和销毁责任,两者不是同一棵树。

旧表把许多间接祖先写成直接基类,容易误解类层次。下面保留原来的 14 个类及用途,并写出实际的直接继承关系。

类直接基类主要用途
QObject无对象树、元对象、信号连接和事件处理的基础
QApplicationQGuiApplicationWidgets 应用的事件循环与应用级管理
QWidgetQObject、QPaintDeviceWidgets 控件/窗口的共同基础,也是绘图设备
QMainWindowQWidget主窗口,组织中央控件、菜单、工具栏、状态栏等
QDialogQWidget模态或非模态对话框
QPushButtonQAbstractButton普通按钮
QLabelQFrame文本或图片显示
QLineEditQWidget单行文本输入
QTextEditQAbstractScrollArea多行文本与富文本编辑
QComboBoxQWidget下拉选择/可编辑组合框
QCheckBoxQAbstractButton复选框
QRadioButtonQAbstractButton单选按钮
QSliderQAbstractSlider滑动取值
QProgressBarQWidget任务进度显示

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 线程使用;跨线程通信规则见 信号连接与线程。