Qt中引入了元对象系统对标准C++语言进行了扩展,增加了对象间的通信机制(信号和槽)、动态属性系统、运行时类型信息、动态翻译等特性,它使Qt能够更好的实现GUI用户图形界面编程。

Qt的元对象系统不支持C++模板。

元对象系统概述

Qt元对象系统基于以下3个事实:

  • 基类QObject:任何需要使用元对象系统功能的类必须继承自QObjectQObject是Qt中最基本的类,是所有Qt对象的基类。
  • Q_OBJECT宏:Q_OBJECT宏必须出现在类的私有声明区,用于启动元对象的特性。
  • 元对象编译器(moc, Meta-Object Compiler):为QObject子类实现元对象特性提供必要的代码实现。(将Qt对C++的语法拓展编译还原为标准C++代码

构建项目时,MOC会读取C++源文件,当他发现类的定义中有Q_OBJECT宏时,他就会为这个类生成另一个包含元对象支持代码的C++源文件,这个生成的源文件连同类的实现文件一起被标准C++编译器编译和链接。

QObject类和QMetaObject类

QObject类是所有使用元对象系统的类的基类,也就是说,如果一个类的父类或上层父类是QObject,它就可以使用信号与槽、属性等特性。
在这里插入图片描述
元对象系统的特性是通过QObject的一些函数来实现的。
(1) 元对象(meta object)。每个QObject及其子类的实例都有一个元对象,这个元对象是自动创建的。静态变量staticMetaObject就是这个元对象,函数metaObject()返回这个元对象指针。
所以,获取一个对象的元对象有两种方式,示意代码如下:

QPushButton *btn= new QPushButton();
const QMetaObject *metaPtr = btn->metaObject();//获取元对象指针
const QMetaObject metaObj = btn->staticMetaObject;//获取元对象

(2) 类型信息。QObjectinherits()函数可以判断对象是不是从某个类继承的类的实例。

(3) 动态翻译。函数tr()用于返回一个字符串的翻译版本,在设计多语言界面的应用程序时需要用到tr()函数。

(4) 对象树(object tree)。对象树指的是表示对象间从属关系的树状结构。例如在一个窗口上,组件都有父容器,窗口是界面上所有组件的顶层容器。QObject类的parent()函数返回其父对象,children()函数返回其子对象,findChildren()函数可以返回某些子对象或所有子对象。
窗口和窗口上的组件就构成对象树,窗口可以访问任何一个界面组件。对象树中的某个对象被删除时,它的子对象会被自动删除,所以,一个窗口被删除时,它上面的所有界面组件也会被自动删除。

(5) 信号与槽。通过在一个类的定义中插入宏Q_OBJECT,我们就可以使用Qt扩展的C++语言特性编程,例如在一个类中定义属性、类信息、信号和槽函数。
(6) 属性系统。在类的定义代码中可以用宏Q_PROPERTY定义属性,QObjectsetProperty()函数会设置属性的值或定义动态属性;property()函数会返回属性的值。


每个QObject及其子类的实例都有一个自动创建的元对象,元对象是QMetaObject类型的实例。元对象存储了类的实例所属类的各种元数据,包括类信息元数据、方法元数据、属性元数据等,所以,元对象实质上是对类的描述。
在这里插入图片描述
通过QMetaObject类的这些函数,我们可以在运行时获取一个QObject对象的类信息和各种元数据。例如,函数className()可返回类的名称,函数superClass()可返回其父类的元对象,函数newInstance()可以创建元对象所描述类的一个新的实例。

类的元数据又分为多种类型,且有专门的类来描述。例如,函数property()返回属性的元数据,属性元数据用QMetaProporty类描述,它的接口函数描述了属性的各种特性,如函数name()返回属性名称,函数type()返回属性数据类型。

运行时类型信息

通过使用QObjectQMetaObject提供的以下一些接口函数,我们可以在运行时获得一个对象的类名称以及其父类的名称,判断其是否从某个类继承而来。

要实现这些功能,并不需要C++编译器的运行时类型信息(run-time type information,RTTI)支持,
而是通过Qt自身的元对象编译器(moc)在编译时生成的元数据来实现。

1) 获取类名
通过 QObject::metaObject() 获取对象关联的 QMetaObject 实例,然后调用其 className() 方法,可以在运行时获取对象的类名称。

QObject *obj = new QPushButton();
qDebug() << obj->metaObject()->className(); // 输出: "QPushButton"

2) 查询继承关系
QMetaObject 提供了方法来检查类的继承层次结构:

  • classInfo(int index):获取类的元信息(如版本、作者等自定义信息)。
  • superClass():返回该类的父类的 QMetaObject 指针,可用于遍历继承链。
const QMetaObject *meta = obj->metaObject();
while (meta) {
    qDebug() << "Class:" << meta->className();
    meta = meta->superClass(); // 向上追溯父类
}

3) 判断类型继承关系
使用 QObject::inherits(const char *className) 方法,可以判断一个对象是否从指定的类继承而来。

QPushButton button;
qDebug() << button.inherits("QWidget"); // true
qDebug() << button.inherits("QAbstractButton"); // true
qDebug() << button.inherits("QLineEdit"); // false

此方法比C++的 dynamic_cast 更高效,且不依赖原生RTTI。

4) 安全的类型转换
虽然Qt不依赖C++ RTTI,但它提供了类似于 dynamic_cast 的功能:

  • qobject_cast<T>():类型安全的向下转型,仅适用于继承自 QObject 且声明了 Q_OBJECT 宏的类。
QObject *obj = new QPushButton();
QPushButton *btn = qobject_cast<QPushButton*>(obj);
if (btn) {
    qDebug() << "Cast successful";
}

Qt的运行时类型信息完全由元对象系统提供,因此:

  • 不需要在编译时开启C++的RTTI(如 -frtti)。
  • 性能更高,因为 inherits()metaObject() 是基于字符串比较和指针查找,比 typeiddynamic_cast 更轻量。
  • 更加灵活,支持跨模块的类型查询。

元对象编译器(moc)的作用
所有这些功能的前提是类继承自 QObject 并声明了 Q_OBJECT 宏。moc 工具会解析这些宏,并生成包含元数据的C++代码,包括类名、信号/槽信息、属性、枚举等,这些数据在运行时通过 QMetaObject 暴露出来。

信号和槽

信号(signal)和槽(slot) 是Qt自行定义的一种对象间通信机制,实现对象之间的数据交互。

当用户或系统触发了一个动作,导致某个控件的状态发生了改变,该控件就会发射一个信号,即调用其类中一个特定的成员函数(信号),同时还能携带有必要的参数。

槽函数与普通成员函数没有太多区别,差别在于其功能。槽函数更多体现为对某种特定信号的处理,可以将槽和其他对象信号建立连接,这样当发射信号时,槽函数能将被触发和执行,进而来完成机体功能。

信号的定义

  class XX: public QObject{
      Q_OBJECT   //宏,moc链接工具,元对象编译器,处理QT语法扩展,还原成标准c++代码
  signals:
      void signal_func(..);  //信号函数,只需声明,不能写定义。QT语法扩展,普通C++无法编译
  }
  
  //发射信号
  emit signal_func(..);
  QObject::connect(A1, SIGNAL(sigfun1(int)), A2, SIGNAL(sigfun2(int)));

槽的定义

  class XX: public QObject{
      Q_OBJECT
  public slots:
      void slot_func(..){..} //槽函数可以连接到某个信号上,当信号被发射时,槽函数将被触发和执行。另外槽函数可以当作普通成员函数使用。
  }

信号和槽的链接

  QObject::connect(const QObject* sender,      //信号发送对象指针
                   const char* signal, 		//要发送的的信号函数,可以使用“SIGNAL(..)”宏进行类型转换
                   const QObject* receiver, 	//信号的接收对象指针
                   const char* method);		//要执行的槽函数,使用“SLOT(..)”宏进行类型转换
  										 //信号函数连同参数类型转换为const char*

connect()函数的第五个参数Qt::ConnectionType

  • Qt::AutoConnection(默认值):如果信号的接收者和发送者在同一个线程中,就使用Qt::DirectConnection方式,否则使用Qt::QueuedConnection方式。在信号发射时自动确定关联方式。
  • Qt::DirectConnection:信号被发射时槽函数立即执行,槽函数和信号在同一个线程中。
  • Qt::QueuedConnection:在事件循环回到接收者线程后运行槽函数,槽函数和信号在不同线程中。
  • Qt::BlockingQueuedConnection:与Qt::QueuedConnection相似,区别是信号线程会阻塞,直到槽函数运行完毕。当信号和槽函数在同一线程中时绝对不能使用这种方式,否则会造成死锁。

实例:

  • 按钮点击时发送信号:clicked()
  • 实现标签关闭功能:close()
  int main(int argc, char *argv[])
  {
      QApplication a(argc, argv);
  
      MainWindow w;
      w.resize(640, 640);
  
      //创建标签控件
      QLabel label("我是标签", &w);
      label.move(20, 40);
  
      //创建按钮空间,栈创建,系统自动销毁
      QPushButton btn("我是按钮", &w);
      btn.move(20, 100);
      QPushButton btn2("退出", &w);
      btn2.move(100, 100);
      w.show();
  
      //点击按钮关闭标签
      QObject::connect(&btn, SIGNAL(clicked(bool)), &label, SLOT(close()));
  
      //增加退出按钮,实现退出应用程序
      QObject::connect(&btn2, SIGNAL(clicked(bool)), 
                       &a, SLOT(quit()));
      			  //&a, SLOT(closeAllWindows())
      			  //&w, SLOT(close())
      return a.exec();
  }

信号和槽连接的语法要求与应用

  • 信号和槽参数要一致

    QObject::connect(A, SIGNAL(sigfun(int)), B, SLOT(slotfun(int)));
    QObject::connect(A, SIGNAL(sigfun(int)), B, SLOT(slotfun(int,int)));  //error
    
  • 可以带有缺省参数

    QObject::connect(A, SIGNAL(sigfun(int)), B, SLOT(slotfun(int,int=0)));
    
  • 信号函数参数可以多于槽函数,多于参数将被忽略

    QObject::connect(A, SIGNAL(sigfun(int,int)), B, SLOT(slotfun(int)));
    
  • 信号和槽函数(一对多)

    QObject::connect(A, SIGNAL(sigfun(int)), B1, SLOT(slotfun1(int)));
    QObject::connect(A, SIGNAL(sigfun(int)), B2, SLOT(slotfun2(int)));
    
  • 信号和槽函数(多对一)

    QObject::connect(A1, SIGNAL(sigfun1(int)), B, SLOT(slotfun(int)));
    QObject::connect(A2, SIGNAL(sigfun2(int)), B, SLOT(slotfun(int))); 
    
  • 两个信号直接连接,信号级联

    QObject::connect(A1, SIGNAL(sigfun1(int)), A2, SLOT(slotfun2(int)));
    

对象树

Qt的对象树系统(Object Tree System) 是其内存管理与对象生命周期控制的核心机制之一。它通过建立 QObject 派生对象之间的父子关系,形成一棵运行时的对象树,从而实现自动内存管理、资源组织和事件传播等功能。

  • 对象通过对象树的形式组织。
  • 对象树主要用来内存回收,对象树之间不一定是继承关系
  • Qt 对象间可以存在父子关系
    • 每一个对象都保存有它所有子对象的指针
    • 每一个对象都有一个指向其父对象的指针
    • 可以使用findChild()findChildren()查找对象的子对象。
  • 当指定 Qt 对象的父对象时,该对象将自动将自己添加到父对象的children()列表中
    • 其父对象会在子对象链表中加入该对象的指针
    • 该对象会保存指向其父对象的指针
  • 当Qt 对象被销毁时
    • 将自己从父对象的 Children List 移除
    • 将自己的 Children List 中的所有对象销毁

Qt 提供了运行时接口来遍历对象树:

QObject *parent = obj->parent();                    // 获取父对象
const QObjectList &children = parent->children();   // 获取所有子对象

for (QObject *child : children) {
    qDebug() << "Child:" << child->objectName();
}

也可通过 findChild<T>()findChildren<T>() 按名称或类型查找:

QPushButton *btn = parent->findChild<QPushButton*>("okButton");
QList<QLabel*> labels = parent->findChildren<QLabel*>(); // 所有 QLabel 子对象

与 GUI 组件的深度集成
在Qt Widgets中,对象树与可视化层级高度一致:

  • 父 QWidget 显示其子 QWidget。
  • 子控件随父控件移动、隐藏、显示、销毁。
  • 布局管理器(QLayout)也利用对象树管理控件。
QWidget *window = new QWidget;
QPushButton *btn = new QPushButton("OK", window);
window->show(); // btn 自动显示在 window 上

属性系统

Qt的属性系统(Property System) 是其元对象系统(Meta-Object System)的重要组成部分,它提供了一种在运行时动态访问和操作对象属性的机制。

与C++原生的成员变量不同,Qt属性是通过元对象编译器(moc)注册QMetaObject 中的,并可以在运行时进行查询、读取、写入、监听等操作。

以下是Qt属性系统的核心特性总结:

1) 定义Qt属性
Qt属性不是普通的类成员变量,而是通过 Q_PROPERTY() 宏在类中声明的。该宏必须位于继承自 QObject 的类中,并且类需包含 Q_OBJECT 宏。

class MyClass : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
    Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged DESIGNABLE true)

public:
    MyClass(QObject *parent = nullptr);

    QString name() const { return m_name; }
    void setName(const QString &name) {
        if (m_name != name) {
            m_name = name;
            emit nameChanged();
        }
    }

    int value() const { return m_value; }
    void setValue(int val) {
        if (m_value != val) {
            m_value = val;
            emit valueChanged();
        }
    }

signals:
    void nameChanged();
    void valueChanged();

private:
    QString m_name;
    int m_value;
};

2) Q_PROPERTY 宏参数详解

参数 说明
READ 指定读取属性值的函数(getter),必需
WRITE 指定设置属性值的函数(setter),可选
NOTIFY 属性变化时发出的信号,用于绑定和通知
DESIGNABLE 是否在设计工具(如Qt Designer)中可见
SCRIPTABLE 是否可在脚本中访问
STORED 是否“持久”存储(例如序列化)
USER 是否为用户主要操作的属性(如 QTextEdit 的 text)
CONSTANT 表示属性是常量(编译期已知,只读)
FINAL 表示该属性不能被子类重写

3) 运行时动态访问属性
通过 QObject 提供的接口,可以在运行时动态操作属性:

获取和设置属性值:

MyClass obj;
obj.setProperty("name", "Alice");         // 设置属性
QString name = obj.property("name").toString(); // 获取属性
qDebug() << name; // 输出: "Alice"

查询所有可用属性:

const QMetaObject *meta = obj.metaObject();
for (int i = 0; i < meta->propertyCount(); ++i) {
    QMetaProperty prop = meta->property(i);
    qDebug() << "Property:" << prop.name()
             << "Type:" << prop.typeName()
             << "Value:" << obj.property(prop.name());
}

4) 属性的类型安全与转换
属性值通过 QVariant 类型进行封装,支持Qt内建类型(如 int, QString, QColor 等)以及自定义类型(需用 Q_DECLARE_METATYPE 注册)。

支持自动类型转换:

obj.setProperty("value", "42"); // 字符串自动转为 int

5) 属性变更通知(NOTIFY)
当属性值改变时,可通过 NOTIFY 指定的信号通知外部系统,这在以下场景中非常有用:

  • QML 绑定:QML 可以监听属性变化并自动更新UI。
  • 模型视图编程:视图组件可响应数据模型的属性变化。
  • 动画系统:QPropertyAnimation 可对任意 NOTIFY 属性进行动画处理。
QPropertyAnimation *anim = new QPropertyAnimation(&obj, "value");
anim->setStartValue(0);
anim->setEndValue(100);
anim->start();

优点与使用场景

优点 说明
运行时反射 支持动态查询和修改对象属性
跨语言交互 C++ 与 QML/JS 无缝通信的基础
高度可扩展 支持自定义类型和复杂逻辑
设计工具支持 集成于 Qt Designer 等可视化工具
动画与绑定支持 是 QPropertyAnimation 和数据绑定的核心
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐