Qt“灵魂”之Meta-Object系统

描述

一、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宏。

  审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分