Qt 类本质就是 C++ 的类,只是 Qt 框架扩展了一套【QObject 对象模型】

C++ 原生本身只有类、继承、成员、虚函数 ;QObject 对象模型,是 Qt 在标准 C++ 语法之上额外实现的一套扩展机制 ,依托QObject基类 + moc元对象编译器,给 C++ 类增加原生 C++ 做不到的能力:信号槽、运行时类型识别、动态属性、元信息、对象树内存管理。

你那句话:Qt 类本质就是 C++ 类,只是 Qt 扩展了 QObject 对象模型 含义:THints、THintsMenu、THintsDialog这些类,底层仍然是标准 C++ 的 class,能使用 C++ 所有语法(构造函数、继承、引用、指针、初始化列表);只要继承 QObject,就接入 Qt 这套额外的对象模型,获得原生 C++ 没有的特性;不继承 QObject 的普通 C++ 类,就没有这套能力。

一、标准原生 C++ 类,缺了什么?

原生 C++:

  1. 编译完成后,类的信息全部被编译器砍掉。运行时程序不知道:类叫什么名字、有哪些成员函数、有哪些属性;
  2. 没有内置 "回调绑定机制",想做事件回调,只能手动写函数指针;
  3. 没有动态属性:一个类定义完,成员就固定,运行时不能临时新增属性;
  4. 内存管理需要自己手动new/delete,没有自动父子托管;
  5. 多继承有虚继承、菱形继承的麻烦。

原生 C++ 的信息只在编译期存在;编译完二进制里面几乎不保留类的描述信息。

二、QObject 对象模型由哪几部分组成

1. 基类:QObject

所有想使用这套模型的类,必须直接 / 间接继承 QObject,就像你代码里:

  • THintsMenu : public QObject, public QDesignerTaskMenuExtension
  • THintsDialog : public QDialog,而QDialog继承QWidget,QWidget继承QObject

继承 QObject 是入场券。

2. 元对象宏 Q_OBJECT(核心!)

cpp 复制代码
class THintsMenu : public QObject
{
    Q_OBJECT  // 关键宏
    ...
};

只要写Q_OBJECT,编译前 Qt 的moc(Meta Object Compiler,元对象编译器) 会扫描你的头文件,自动生成额外 C++ 代码(moc_thintsmenu.cpp)。

moc 不是 C++ 标准编译器,是 Qt 单独的预处理器。 moc 会提取这个类的信息:类名、信号、槽、属性,生成一个元对象类 QMetaObject。

3. QMetaObject:元对象(保存类的描述信息)

moc 生成的QMetaObject,相当于这个类的 "运行时说明书",里面存:

  • 类名字符串
  • 所有信号列表、槽函数列表
  • 属性列表(property)
  • 方法信息

👉 原生 C++ 运行时没有这份说明书;QObject 模型把这份说明书打包进程序,运行时可以读取。

三、QObject 对象模型提供的五大核心能力(结合你刚才看的源码举例)

① 信号与槽(Signal & Slot)------ 最常用

原生 C++ 要用函数指针手动绑定回调,非常麻烦; Qt 信号槽由元对象系统实现:connect可以把一个对象的信号,连接到另一个对象的槽函数。

cpp 复制代码
connect(edit_state_action_, &QAction::triggered, this, &THintsMenu::edit_state);

原理: 运行时,通过QMetaObject找到信号、槽的信息;信号触发时,元对象系统自动找到所有连接的槽函数调用。

信号槽支持跨线程,这是普通 C++ 函数指针很难安全实现的。

② 运行时类型识别 qobject_cast(你代码里出现过!)

cpp 复制代码
THints *hints = qobject_cast<THints *>(object);
  • 原生 C++ 有dynamic_cast,但依赖 RTTI,有开销、限制多;
  • qobject_cast是 Qt 元对象系统实现的类型转换,不需要 C++ 的 RTTI 开关 ,只对继承 QObject 的类生效。 运行时读取QMetaObject的类名信息,判断对象是不是目标类型,失败返回 nullptr。 你工厂类THintsMenuFactory::createExtension就是靠这个判断选中的控件是不是 THints。

③ 动态属性系统 setProperty /property(你 on_finished 里面用到!)

cpp 复制代码
form_window->cursor()->setProperty("tips_type_", thints_.property("tips_type_"));

原生 C++:类定义写死成员变量,运行不能临时增加变量 。 QObject 对象模型: 可以在运行时,给 QObject 派生对象动态附加键值对属性,不需要在 class 里预先定义成员。 Qt 设计师保存控件属性到 ui 文件,底层就是这套动态 property。

④ 对象树(Object Tree)内存自动管理(Qt 内存管理核心)

QObject 派生类构造可以传入parent父对象指针:

cpp 复制代码
THintsMenu::THintsMenu(THints *control, QObject *parent)
    : QObject(parent)

规则:

  1. 子对象加入父对象的对象树;
  2. 父对象析构的时候,会自动 delete 所有子 QObject 对象;
  3. 不用你一个个手动 delete 子控件,避免内存泄漏。

注意:普通 C++ 对象没有这个父子托管;只有 QObject 子类才有对象树。 你代码里new QAction(tr("设置THints..."), this),this作为父对象,THintsMenu 销毁时自动释放这个 QAction。
区分:

  • ui指针是普通堆对象,不属于 QObject 对象树,所以你必须在析构写delete ui;
  • QAction、THintsMenu、THintsDialog 属于 QObject,走对象树。

⑤ 元属性、反射,支持 Qt Designer 属性面板

Qt 设计师可以在右侧属性面板直接修改控件参数,原理就是QMetaObject 反射: Qt 设计器运行时读取控件的元对象信息,自动识别属性,展示到属性面板;修改后用 setProperty 写回去。 你写的 THints 自定义控件能在 Qt 设计师拖拽、编辑属性,完全依赖这套对象模型。

四、关键限制(QObject 模型的代价)

  1. 必须继承 QObject,并且写 Q_OBJECT 宏,moc 才会处理;
  2. 多重继承时,QObject 必须放在第一个继承位置;
  3. 不能模板类直接使用 Q_OBJECT;
  4. moc 是预编译步骤,忘记 qmake/CMake 开启 moc 就编译报错。

五、一句话对比

  • 普通 C++ 类:编译完,类信息丢弃,只有代码逻辑;内存、回调、类型判断全部自己写。
  • 继承 QObject 的 Qt 类 :依然是 C++ 类,但是 moc 额外生成元对象代码,在运行时携带类的描述信息 ,以此实现信号槽、动态属性、对象树、qobject_cast,这套整体就叫 QObject 对象模型。
相关推荐
Cx330❀8 小时前
深入浅出 Qt 绘图机制:从 QPainter 到四大图像类解析
开发语言·qt·ui·性能优化·图形渲染
Littlehero_12110 小时前
QT自定义控件之水泥厂组态(源码开源)
开发语言·qt
代钦塔拉13 小时前
【C++/Qt 进阶】设计模式系列(6):桥接模式——把“两个独立变化的维度“拆开
c++·qt·设计模式
1560820721914 小时前
银河麒麟 Qt 可执行文件打包说明
开发语言·驱动开发·qt
Quz14 小时前
QML GridView 布局:网格排布与排列方向
qt
Quz14 小时前
QML GridView 动态:增删过渡与滚动定位
qt
沫璃染墨2 天前
《Qt从零入门系列(十四):QThread多线程详解——从线程创建到客户端阻塞处理》
开发语言·c++·笔记·qt·ui·交互·线程
代钦塔拉2 天前
【C++/Qt 进阶】设计模式系列(5):适配器模式——给“不兼容的接口“贴一层翻译膜
c++·qt·设计模式
把我格子衫拿来2 天前
VTK 9.5 与 Qt 6 联合开发:从嵌入三维窗口到可维护的桌面架构
c++·python·qt·vtk·三维可视化