一、Meta-Object简介
Meta-Object即是Qt的元对象系统,下文都以元对象系统进行描述。在Qt中,具有标志性特征的则是信号和槽函数机制,该机制的背后实现本质上则是元对象系统。编写Qt代码的时候,在定义类的时候,需要放置一个Q_OBJECT,为什么呢?后文会描述到,例如如下代码:
image-20230207220020441
Q_OBJECT本质上是一个宏定义,在进行Qt开发时,所有QObject的派生类都推荐在头文件中放置Q_OBJECT宏定义,该宏定义如下(出自qobjectdefs.h文件):
#define Q_OBJECT public: QT_WARNING_PUSH Q_OBJECT_NO_OVERRIDE_WARNING static const QMetaObject staticMetaObject; virtual const QMetaObject *metaObject() const; virtual void *qt_metacast(const char *); virtual int qt_metacall(QMetaObject::Call, int, void **); QT_TR_FUNCTIONS private: Q_OBJECT_NO_ATTRIBUTES_WARNING Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); QT_WARNING_POP struct QPrivateSignal {}; QT_ANNOTATE_CLASS(qt_qobject, "")
Qt中,元对象系统包含了支持元对象系统的程序,宏定义,基类,接口函数。这些东西共同构成了Qt的元对象系统:
(1)QObject为想要使用元对象系统的对象提供了基类。
(2)Q_OBJECT宏用于启动元对象特性,例如:动态属性、信号和槽函数机制等。
(3)元对象编译器(moc)为每个QObject子类生成实现元对象特性所需要的代码。
在实际Qt程序设计中,在派生自QObject的类定义中加上Q_OBJECT后,则可以使用元对象系统所支持的特性了。
二、元对象系统背后机制
如果在派生自QObject的类定义中加上了Q_OBJECT后,在编译构建过程中,元对象系统的moc工具(本文以Windows平台为例,该工具则位于具体Qt版本目录下的bin目录中)
在Windows命令行下运行,可获知如下信息:
在QtCreator集成开发环境中,当点击构建按钮后,QtCreator会自动调用moc工具,该工具会读取一个C++源文件,如果它发现一个或多个包含Q_OBJECT宏的类声明,那么则会生成另外一个C++源文件,源文件中包含每个类的元对象代码。接着,生成的源文件要么被#include包含到类的源文件中,要么被编译并链接到类的实现中。例如下列一个简单的项目工程,源码结构如下:
从上图可知,工程中包含了一个main.cpp、一个主窗口描述文件mainwindow.cpp/.h、一个stylesheeteditor.cpp/.h文件,由于mainwindow.cpp/.h、stylesheeteditor.cpp/.h支持Qt的元对象系统,在编译构建过程中,则会生成支持元对象系统的中间文件,如下图所示:
从上图可知,这些文件都以moc_xxx方式进行命名,最后结合其他的文件生成了程序可执行体(stylesheet.exe),整个过程可如下图所示(Windows平台):
三、再谈元对象系统
除了提供对象之间通信的信号和槽函数机制(这是引入该系统的主要原因),元对象系统还提供以下的功能:
(1)Object::metaObject():返回类的关联元对象。
(2)QMetaObject::className():在运行时以字符串的形式返回类名,而不需要通过C++编译器提供本地运行时类型信息(RTTI)支持。
(3)QObject::inherits():函数返回一个对象是否是在QObject继承树中继承指定类实例。
(4)QObject::tr()和QObject::trUtf8()为国际化翻译字符串。
(5)QObject::setProperty()和QObject::property()根据名称动态设置和获取属性。
(6)QMetaObject::newInstance():构造类的新实例。
除了上述所列的功能,还可以使用qobject_cast()对QObject类执行动态强制类型转换,qobject_cast()函数的行为类似于标准C++ 的dynamic_cast(),它的优点是不需要RTTI支持,并且可以跨动态库工作。该函数尝试将其参数转换为尖括号中指定的指针类型,如果对象的类型正确(在运行时确定),则返回非零指针;如果对象的类型不兼容,则返回nullptr。
例如,假设有一个MyWidget继承自QWidget,并使用了Q_OBJECT宏声明,然后使用new创建该实例:
QObject *obj = new MyWidget;
类型为QObject *的obj变量实际上引用了一个MyWidget对象,所以我们可以对它进行适当的类型转换,如下代码:
QWidget *widget = qobject_cast(obj);
从QObject到QWidget的转换是成功的,因为该对象实际上是一个MyWidget,它是QWidget的一个子类。既然知道obj是一个MyWidget,我们也可以将它cast到MyWidget *,如下代码:
MyWidget *myWidget = qobject_cast(obj);
上述代码对MyWidget的转换是也成功的,因为qobject_cast()在内置Qt类型和自定义类型之间没有区别。
然而对于下列代码:
QLabel *label = qobject_cast(obj);
对QLabel的强制转换将失败,会将指针设置为0。因此可以在运行时处理不同类型的对象,例如:
if (QLabel *label = qobject_cast(obj)) { label->setText(tr("iriczhao")); } else if (QPushButton *button = qobject_cast (obj)) { button->setText(tr("嵌入式小生")); }
上述代码使用qobject_case()对obj进行了向QLabel和QPushButton的强制转换,如果转换成功,则设置对应的显示文本。
四、小生总结
在实际Qt开发过程中,虽然可以在没有Q_OBJECT宏和元对象代码的情况下将QObject作为基类,但如果没有使用Q_OBJECT宏,则信号和槽函数机制或在本文中描述的其他特性都不能使用。从元对象系统的角度来看,一个没有元代码的QObject子类等价于它最近的有元对象代码的祖先。这意味着,例如,QMetaObject::className()将不会返回自己类的实际名称,而是这个祖先的类名称。
因此,在实际Qt开发过程中,无论实际上是否使用了信号和槽函数机制,都强烈建议QObject的所有子类都使用Q_OBJECT宏。
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !