简述

元对象系统(Meta-Object System)提供了信号与槽机制,可用于对象间通信、运行时类别信息和动态属性系统。

元对象系统基于三个方面:

  1. QObject类:为objects提供了一个可以利用元对象系统的基类。
  2. Q_OBJECT宏: 在类的私有部分声明这个宏可以启用元对象特性,例如:动态属性、信号与槽。
  3. Meta-Object编译器(moc): 为每个QObject子类生成必要的代码来实现元对象特性。

moc工具会读取C++源文件,如果发现有包含Q_OBJECT宏的类声明,就生成另外一个包含这些类的元对象代码的C++源文件。生成的源文件要么在类源文件里用#include包含,或者(更常见)与类的实现代码直接进行编译连接。

moc工具会读取C++源文件,如果发现一个或者多个声明了Q_OBJECT宏的类,就生成另外一个包含这些类的元对象代码的C++源文件。这些编译生成的源文件要么被包含到类的源文件中,或者更常见,被编译和链接到类的实现中。

详细描述

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

  • QObject::metaObject()返回类关联的meta-object对象。
  • QMetaObject::className()在运行时以字符串的形式返回类名,无需C++编译器提供运行时类别信息(RTTI)的支持。
  • QObject::inherits()返回一个对象是否是QObject继承树上一个类的实例。
  • QObject::tr()和QObject::trUtf8()提供国际化支持,将字符串翻译成指定的语言。
  • QObject::setProperty()和QObject::property()通过名称动态设置和获取属性。
  • QMetaObject::newInstance()构造类的一个新实例。

除此之外,还可以用qobject_cast()动态转换QObject类的类型。qobject_cast()函数和标准C++的dynamic_cast()功能类似,它的优点在于:不需要RTTI的支持,而且可以跨越动态连接库的转换。它尝试将它的参数转换成尖括号内的指针类型,如果对象是正确的类型(在运行时检查),则返回非零指针;否则,返回0,说明对象类型不兼容。

例如,假设MyWidget继承自QWidget,同时也声明了Q_OBJECT宏。

QObject *obj = new MyWidget;

QObject *类型的变量obj实际上指向一个MyWidget对象,因此,我们可以适当地进行类型转换:

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

因为obj实际上是一个MyWidget,而MyWidget是QWidget的子类,所以,从QObject转换为QWidget成功了。既然知道了obj是MyWidget类型的,那么我们也可以将其转换为MyWidget *:

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

到MyWidget类型的转换也是成功的,因为qobject_cast()并不区分内建的Qt类型和自定义类型。

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

可是,转换到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作为基类,但是像信号和槽以及上面所说的其它特性都将无法使用。从元对象系统的角度来看,一个没有元对象代码的QObject子类等同于它最接近的有元对象代码的祖先。这意味着,QMetaObject::className()将不会返回你的类的真实名称,而是返回其祖先的名字。

因此,强烈建议所有QObject的子类都使用Q_OBJECT宏,不管实际上是否使用信号和槽,以及属性


深入理解QT

深入了解Qt主要内容来源于Inside Qt系列,本文做了部分删改,以便于理解。在此向原作者表示感谢!

QObject这个 class 是 QT 对象模型的核心,关于对象模型可以阅读C++对象模型详解,绝大部分的 QT 类都是从这个类继承而来。这个模型的中心特征就是一个叫做信号和槽(signaland slot)的机制来实现对象间的通讯,你可以把一个信号和另一个槽通过 connect(…) 方法连接起来,并可以使用disconnect(…) 方法来断开这种连接,你还可以通过调用blockSignal(…) 这个方法来临时的阻塞信号。对于信号和槽可以阅读Qt 信号和槽函数。

QObject 的对象树机制

当你创建一个 QObject 并使用其它对象作为父对象时,这个对象会自动添加到父对象的children() list 中。父对象拥有这个对象,比如,它将在它的析构函数中自动删除它所有的 child对象。你可以通过 findChild() 或者findChildren()函数来查找一个对象。每个对象都有一个对象名称(objectName())和类名称(class name), 他们都可以通过相应的 metaObject 对象来获得。你还可以通过 inherits() 方法来判断一个对象的类是不是从另一个类继承而来。当对象被删除时,它发出destroyed()信号。你可以捕获这个信号来避免对QObject的无效引用。QObject可以通过event()接收事件并且过滤其它对象的事件。详细情况请参考installEventFilter()和eventFilter()。对于每一个实现了信号、槽和属性的对象来说,Q_OBJECT 宏都是必须要加上的。

QObject 类的实现文件一共有四个:

  1. qobject.h,QObject class 的基本定义,也是我们一般定义一个类的头文件。
  2. qobject.cpp,QObject class 的实现代码基本上都在这个文件。
  3. qobjectdefs.h,这个文件中最重要的东西就是定义了 QMetaObject class,这个class是为了实现 signal、slot、properties,的核心部分。
  4. qobject_p.h,这个文件中的 code 是辅助实现QObject class 的,这里面最重要的东西是定义了一个 QObjectPrivate 类来存储 QOjbect 对象的成员数据。

理解这个 QObjectPrivate class 又是我们理解 QT kernel source code 的基础,这个对象包含了每一个 QT 对象中的数据成员,好了,让我们首先从理解 QObject 的数据存储代码开始我么的 QT Kernel Source Code 之旅。

我们知道,在C++中,几乎每一个类(class)中都需要有一些类的成员变量(class member variable),在通常情况下的做法如下:

class Person
{
private:string mszName; // 姓名bool mbSex;    // 性别int mnAge;     // 年龄
};

在QT中,却几乎都不是这样做的,那么,QT是怎么做的呢?

几乎每一个C++的类中都会保存许多的数据,要想读懂别人写的C++代码,就一定需要知道每一个类的的数据是如何存储的,是什么含义,否则,我们不可能读懂别人的C++代码。在这里也就是说,要想读懂QT的代码,第一步就必须先搞清楚QT的类成员数据是如何保存的。

为了更容易理解QT是如何定义类成员变量的,我们先说一下QT 4.x 版本中的类成员变量定义方法,因为在 4.x 中的方法非常容易理解。然后在介绍 QT 4.4 中的类成员变量定义方法。

QT 4.x 中的方法

在定义class的时候(在.h文件中),只包含有一个,只是定义一个成员数据指针,然后由这个指针指向一个数据成员对象,这个数据成员对象包含所有这个class的成员数据,然后在class的实现文件(.cpp文件)中,定义这个私有数据成员对象。示例代码如下:

//person.h
struct PersonalDataPrivate; // 声明私有数据成员类型
class Person
{
public:Person ();   // constructorvirtual ~Person ();  // destructorvoid setAge(const int);int getAge();
private:PersonalDataPrivate* d;
};//---------------------------------------------------------------------
//person.cpp
struct PersonalDataPrivate  // 定义私有数据成员类型
{string mszName; // 姓名bool mbSex;    // 性别int mnAge;     // 年龄
};// constructor
Person::Person ()
{d = new PersonalDataPrivate;
};// destructor
Person::~Person ()
{delete d;
};void Person::setAge(const int age)
{if (age != d->mnAge)d->mnAge = age;
}int Person::getAge()
{return d->mnAge;
}

在最初学习QT的时候,我也觉得这种方法很麻烦,但是随着使用的增多,我开始很喜欢这个方法了,而且,现在我写的代码,基本上都会用这种方法。具体说来,它有如下优点:

减少头文件的依赖性
把具体的数据成员都放到cpp文件中去,这样,在需要修改数据成员的时候,只需要改cpp文件而不需要头文件,这样就可以避免一次因为头文件的修改而导致所有包含了这个文件的文件全部重新编译一次,尤其是当这个头文件是非常底层的头文件和项目非常庞大的时候,优势明显。
同时,也减少了这个头文件对其它头文件的依赖性。可以把只在数据成员中需要用到的在cpp文件中include一次就可以,在头文件中就可以尽可能的减少include语句
增强类的封装性
这种方法增强了类的封装性,无法再直接存取类成员变量,而必须写相应的 get/set 成员函数来做这些事情。
关于这个问题,仁者见仁,智者见智,每个人都有不同的观点。有些人就是喜欢把类成员变量都定义成public的,在使用的时候方便。只是我个人不喜欢这种方法,当项目变得很大的时候,有非常多的人一起在做这个项目的时候,自己所写的代码处于底层有非常多的人需要使用(#include)的时候,这个方法的弊端就充分的体现出来了。

还有,我不喜欢 QT 4.x 中把数据成员的变量名都定义成只有一个字母d,看起来很不直观,尤其是在search的时候,很不方便。但是,QT kernel 中的确就是这么干的。

QT 4.4.x 中的方法

在 QT 4.4 中,类成员变量定义方法的出发点没有变化,只是在具体的实现手段上发生了非常大的变化,在 QT 4.4 中,使用了非常多的宏来做事,这凭空的增加了理解 QT source code 的难度,不知道他们是不是从MFC学来的。就连在定义类成员数据变量这件事情上,也大量的使用了宏。

在这个版本中,类成员变量不再是给每一个class都定义一个私有的成员,而是把这一项common的工作放到了最基础的基类 QObject 中,然后定义了一些相关的方法来存取,好了,让我们进入具体的代码吧。

//qobject.hclass QObjectData
{
public:virtual ~QObjectData() = 0;// 省略
};class QObject
{Q_DECLARE_PRIVATE(QObject)
public:QObject(QObject *parent=0);protected: QObject(QObjectPrivate &dd, QObject *parent = 0);QObjectData *d_ptr;
}

这些代码就是在 qobject.h 这个头文件中的。在 QObject class 的定义中,我们看到,数据员的定义为:QObjectData*d_ptr; 定义成 protected 类型的就是要让所有的派生类都可以存取这个变量,而在外部却不可以直接存取这个变量。而 QObjectData 的定义却放在了这个头文件中,其目的就是为了要所有从QObject继承出来的类的成员变量也都相应的要在QObjectData这个class继承出 来。而纯虚的析构函数又决定了两件事:

  1. 这个class不能直接被实例化。换句话说就是,如果你写了这么一行代码,new QObjectData, 这行代码一定会出错,compile的时候是无法过关的。
  2. 当 delete 这个指针变量的时候,这个指针变量是指向的任意从QObjectData继承出来的对象的时候,这个对象都能被正确delete,而不会产生错误,诸如,内存泄漏之类的。

Qt总结之二十二:Qt控件QObject相关推荐

  1. VS2010/MFC编程入门之二十(常用控件:静态文本框)

    上一节鸡啄米讲了颜色对话框之后,关于对话框的使用和各种通用对话框的介绍就到此为止了.从本节开始鸡啄米将讲解各种常用控件的用法.常用控件主要包括:静态文本框.编辑框.单选按钮.复选框.分组框.列表框.组 ...

  2. MFC编程入门之二十(常用控件:静态文本框)

    上一节讲了颜色对话框之后,关于对话框的使用和各种通用对话框的介绍就到此为止了.从本节开始将讲解各种常用控件的用法.常用控件主要包括:静态文本框.编辑框.单选按钮.复选框.分组框.列表框.组合框.图片控 ...

  3. OpenCV学习笔记(二十一)——绘图函数core OpenCV学习笔记(二十二)——粒子滤波跟踪方法 OpenCV学习笔记(二十三)——OpenCV的GUI之凤凰涅槃Qt OpenCV学习笔记(二十

    OpenCV学习笔记(二十一)--绘图函数core 在图像中,我们经常想要在图像中做一些标识记号,这就需要绘图函数.OpenCV虽然没有太优秀的GUI,但在绘图方面还是做得很完整的.这里就介绍一下相关 ...

  4. QT学习笔记(十二):透明窗体设置

    QT学习笔记(十二):透明窗体设置 创建 My_Widget 类 基类为QWidget , My_Widget.cpp 源文件中添加代码 #include "widget.h" # ...

  5. Qt OpenGL(二十)——Qt OpenGL 核心模式版本

    Qt OpenGL(二十)--Qt OpenGL 核心模式版本 一.写在前面 在之前的OpenGL教程(1~19)中,采用的方式都是固定渲染管线,也就是OpenGL3.2版本之前的写法,但是OpenG ...

  6. QT开发(五十)——QT串口编程基础

    QT开发(五十)--QT串口编程基础 一.QtSerialPort简介 1.串口通信基础 目前使用最广泛的串口为DB9接口,适用于较近距离的通信.一般小于10米.DB9接口有9个针脚. 串口通信的主要 ...

  7. iOS 11开发教程(二十二)iOS11应用视图实现按钮的响应(2)

    iOS 11开发教程(二十二)iOS11应用视图实现按钮的响应(2) 此时,当用户轻拍按钮后,一个叫tapButton()的方法就会被触发. 注意:以上这一种方式是动作声明和关联一起进行的,还有一种先 ...

  8. qt中QHBoxLayout或QVBoxLayout布局内控件的动态生成与显示

    ---恢复内容开始--- qt中QHBoxLayout或QVBoxLayout布局内控件的动态生成与显示 打个比方,我现在写个小例子,这个小例子是这样的,整个界面分为俩个部分,分为上半部分和下半部分, ...

  9. Android开发笔记(一百二十二)循环器视图RecyclerView

    RecyclerView RecyclerView是Android在support-v7库中新推出控件,中文别名为循环器视图,它的功能非常强大,可分别实现ListView.GridView,以及瀑布流 ...

  10. GUI的演化和python编程——Python学习笔记之二十二

    GUI的演化和python编程--Python学习笔记之二十二 写完了有关长寿的两篇博文,本该去完成哥德尔那个命题六的.对计算机图形界面的好奇,让我把注意力暂时离开那个高度抽象难读的哥德尔,给转到计算 ...

最新文章

  1. setInterval 与 clearInterval详解
  2. 微软肿么了?版本更新大提速。Visual Studio 2012再次更新
  3. OleDbHelper类
  4. Springboot本地缓存和redis缓存
  5. mysql登陆salt_salt把返回写入到mysql
  6. linux内核5.4,Linux Kernel 5.4 RC8 发布,一切都很正常
  7. c# 从一组数中随机抽取一定个数_Python随机模块22个函数详解
  8. idea 父文件_在ideal创建新的模块(子项目,同时依赖父模块)
  9. Unity2017.3来了!洪流学堂第一时间送上更新日志简报
  10. Linux多进程编程(2)
  11. OpenStack Networking – FlatManager and FlatDHCPManager
  12. EZEMC测试软件_四种常见的EMC仿真软件介绍
  13. cocos2dx[3.x](11)——拖尾渐隐效果MotionStreak
  14. 高精度NTC测温的硬件电路以及软件设计
  15. Beyond Short Snippets: Deep Networks for Video Classification
  16. 深入理解PHP原理之PHP与WEB服务器交互
  17. 【Linux】gcc编译工具,断点的设置,gdb调试
  18. 计算机详细配置快捷键,电脑输入法怎么设置快捷键
  19. 解决js获取当前时间精确到秒并格式划成数字(20200712120610)
  20. 关于python循环结构错误的是_以下关于Python的循环结构说法错误的是(_____)。...

热门文章

  1. 【李宏毅2020 ML/DL】P4 Basic Concept
  2. 线程同步,通信与虚方法
  3. 为什么基类的析构函数是虚函数?
  4. jQuery Event.delegateTarget 属性详解
  5. oracle机票,全球机票分销系统
  6. java程序利用HttpSessionListener实现统计在线人数(示例代码)
  7. mysql配置好后_安装完 MySQL 后必须调整的 10 项配置
  8. vsftp虚拟帐户配置
  9. vue 如何处理两个组件异步问题_Vue异步组件处理路由组件加载状态的解决方案...
  10. Lock-Free / Lockless 相关术语