元对象系统

Qt 的元对象系统提供了对象间通信的信号槽机制、运行时类型信息,以及动态属性系统。

元对象系统基于以下三者:

  1. QObject 类,提供了便于利用元对象系统的基类;
  2. Q_OBJECT 宏,放置于类声明的私有域,用于激活元对象系统特性,例如动态属性和信号槽;
  3. 元对象编译器 (moc),为每个 QObject 的子类提供实现元对象特性的代码生成。

moc 工具读取 C++ 源文件,若在其中找到包含 Q_OBJECT 宏的类声明,则会创建另一个 C++ 源文件,并在其中填充用于实现元对象得的代码。该生成的源文件需要通过 #include‘ 包含至对应类的源文件,或者更常见的是将其加入编译列表,并于对应类的实现一同链接。

除了为跨对象通信提供了 信号槽机制 (引入此系统的主要原因), 元对象系统还提供了下列额外特性:

我们还可以对 QObject 进行 qobject_cast() 操作,该函数与标准 C++ 的 dynamic_cast() 表现类似,但优点时不需要 RTTI 支持,并且可以跨越动态库边界运作。它会尝试将输入指针转换为尖括号中的指针类型,若类型正确则返回非空指针(在运行时作出判断),若对象类型不兼容则返回 nullptr

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

  1. QObject *obj = new MyWidget;

QObject * 类型的变量 obj 实际指向一个 MyWidget 对象,于是我们可以进行如下转换:

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

QObjectQWidget 的转换成功进行,因为该对象实际是 QWidget 的子类 MyWidget。由于我们知道 objMyWidget 类型,我们可以将其转换为 MyWidget *

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

转换至 MyWidget 的操作可以成功进行,因为 qobject_cast() 并不会将 Qt 内置类型和自定义类型区别对待。(译者注:Qt 是在运行时通过读取元对象信息进行动态转换,开发者可通过 Q_OBJECT 宏让自定义类型支持被 qobject_cast() 进行转换)

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

另一个例子,转换为 QLabel 的操作失败了,该指针会被置零。但此机制也让运行时基于转换结果来区别处理不同类型成为可能:

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

虽然可以使用 QObject 作为基类,但不定义 Q_OBJECT 宏,也不生成元对象代码,但这也意味着信号槽以及本文提到所有的其它机制无法使用。从元对象系统的视角来看,没有元对象代码的 QObject 的子类(译者注:即未使用 Q_OBJECT 宏)等价于它最近的一个包含元对象代码的父类,这也意味着,例如,QMetaObject::className() 不会返回该类的类名,而是会返回父类的类名。

因此,我们强烈建议在所有 QObject 的子类中都使用 Q_OBJECT 宏,无论它们是否用到了信号槽和动态属性。

另请参阅:QMetaObjectQt 的属性系统 以及 信号与槽