QT对C++进行了扩展,提供了三个主要的功能:信号槽、运行时类型信息和动态属性,这三个扩展功能都是由“元对象系统”提供的。

元对象系统基于三个支撑点:

1 OObject为需要使用元对象系统有点的类提供了基类。

2 Q_OBJECT宏声明在类的私有段中,可用来启用元对象特征,如动态属性,信号槽。

3 元对象编译器(moc)为每一个QObject子类提供了实现元对象特征的必要代码。

MOC工具读取C++源代码。如果它发现一个或者多个类的声明包括了宏Q_OBJECT,它产生另一个C++源代码文件,这个文件中包含了含有宏Q_OBJCET类的元对象代码。这个新产生的源文件或者被包含值类的源文件中或者,或者更通常的是被编译和链接到类的的实现中。

元对象系统除了提供信号和槽机制(介绍元对象系统的主要原因),还提供如下特征:

1 QObject::metaObject()返回了类关联的元对象;

2 QMetaObjcet::className()在运行时返回字符串形式的类名称,不需要通过C++编译器的原始运行时类型信息支持。

3 QObject::inherits()方法返回一个对象是否是QObject类或者QObject子类的实例。

4 QObject::tr()和Qobject::trUtf8()用来完成国际化;

5 QObject::SetProperty()和QObject::property()通过名称动态的设置和获取属性;

6 QMetaObject::newInstance()构造类的新实例。

对QObject类也可以使用动态转换qobject_cast(),qobject_cast()函数与标准C++dynamic_cast()的行为很像,优点是不需要RTTI支持,并且它可以跨动态库边界。qobject_cast()尝试将它的参数转换到特定的指针类型,如果对象是正确的类型(在运行时判断)返回非0指针,如果不兼容则返回0。

看下面的例子。我们假设MyWidget继承了Qwidget并且声明了宏Q_OBJECT:

QObject *obj = new MyWidget;

变量Obj是QObject类型,实际引用到一个MyWidget对象,所以我们可以转换:

QWidget *widget = qobject_cast(obj);

从QObject到QWidget的转换是成功的,因为obj实际上就是一个MyWidget,是Qwidget的子类。现在我们知道obj是一个MyWidget对象,我们可以转换到MyWidget *:

MyWidget *myWidget = qobject_cast(obj);

到MyWidget的转换也是成功的,因为qobject_cast()对待QT内建类型和自定义类型之间没有区别的。

下面的转换则是失败的:

QLabel *label = qobject_cast(obj);

obj到Qlabel的转换是失败的。label也被设置为0。

这种运行时类型信息机制可以在运行时处理不同类型的对象,比如:

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

当然也可以使用QObject做为基类却不使用Q_OBJECT宏,这样的类就没有了元对象代码,前文提到的信号槽和其他特征也就都失效了。从元对象系统的观点来看,一个不使用元代码QObject子类等效于它最近的使用元对象代码的祖先。这就意味着,QMetaObject::className() 将会返回祖先的类名而不是实际类的名字。

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