The Meta-Object System

Qt元对象系统(meta-object)为对象间通信提供信号和槽机制, 运行时类型信息及动态属性系统.

元对象系统基于以下三件事:

  1. QObject 作为利用元对象系统的对象的基类.
  2. 类声明私有 Q_OBJECT 宏, 启用元对象特性, 如动态属性, 信号和槽.
  3. 元对象编译器 Meta-Object Compiler (moc) 为每个 QObject 子类扩充必要的元对象特性代码.

moc 工具读取C++源文件. 如果它发现一个或多个包含 Q_OBJECT 宏的类声明, 它将生成另一个包含元对象代码的c++源文件. 这个源文件要么通过 #include方式嵌入到类的源文件, 要么编译并链接到类实现.

除了提供对象间通信的 信号和槽 机制外, 元对象还提供如下功能:

元对象系统也支持QObject子对象通过 qobject_cast() 动态转换. qobject_cast() 函数行为类似标准 C++ dynamic_cast(), 其优点是不需要RTTI支持, 且可以跨动态库边界工作. 它尝试将参数转换成尖括号中指定的指针类型, 若类型正确(运行时确定), 返回非零指针. 若类型不兼容, 返回nullptr.

例如, 假设 MyWidget 继承 QWidget , 并声明 Q_OBJECT 宏:


      QObject *obj = new MyWidget;

实际上, QObject *类型变量 obj 指向对象 MyWidget, 因此我们可以对其转换:


      QWidget *widget = qobject_cast<QWidget *>(obj);

QObjectQWidget 的转换是成功的, 因为obj对象其实是一个 MyWidget, 它是 QWidget 的子类. 由于我们知道 objMyWidget对象, 我们也可以将其转换为 MyWidget *:


      MyWidget *myWidget = qobject_cast<MyWidget *>(obj);

qobject_cast() 对于Qt内置类型和自定义类型没有区别, 因此obj可以转换为 MyWidget.


      QLabel *label = qobject_cast<QLabel *>(obj);
      // label is 0

另一方面, obj转换成 QLabel失败, 指针是0. 这使得软件可以在运行时根据对象类型不同进行不同的处理:


      if (QLabel *label = qobject_cast<QLabel *>(obj)) {
          label->setText(tr("Ping"));
      } else if (QPushButton *button = qobject_cast<QPushButton *>(obj)) {
          button->setText(tr("Pong!"));
      }

尽管在没有Q_OBJECT宏和元对象代码的情况下, 可以将 QObject对象作为基类. 但是如果不使用 Q_OBJECT 宏, 信号和槽及本文描述的其他功能无法使用. 从元对象系统看, 没有元对象代码的 QObject 子类近似于有元对象代码的最近祖先类. 例如, QMetaObject::className() 无法返回类名, 而是祖先类名.

因此, 我们强烈建议 QObject 的所有子类都使用 Q_OBJECT 宏, 而不管它们是否会使用信号, 槽和属性.

参见 QMetaObject, Qt's Property System, Signals and Slots.