目录
1.元对象系统概述
元对象系统引用反射的基本思想。所谓反射,就是指对象成员的自我检查。使用反射编程(reflection programming),就可以编写出通用的操作,可对有各种不同结构的类进行操作。使用通用的值存储器QVariant,就可以按照一种统一的方式来对基本类型和其它的普通类型进行操作。 简而言之,所谓的反射模式,就是通过自己来查看所有的属性,通过自己的元对象属性来访问自己的全部属性,为什么不直接使用成员函数呢?使用成员函数过于繁琐,通过元对象对应的方法可以快速访问对象的属性 反射机制无疑会增加额外的存储空间,在效率上有所牺牲,而且在普通程序中没有用武之地,为了保持几乎与C同等的效率,C++不提供反射机制也有一定的道理。
但对于大型框架或类库来说,反射机制有时很有必要,它会增加程序的灵活性和动态性,例如动态加载类、解耦等。最明显的一个例子是编译器的智能提示,当输入完对象的名称,再输入.或->,就会提示该对象拥有的变量和函数,这是反射机制的典型应用。
元对象系统是一个基于标准C++的扩展,为Qt提供了1、信号与槽机制2、实时类型信息3、动态属性系统 。
元对象系统的三个必要条件:
- QObject的子类
- 宏Q_OBJECT
- Meta Object Compiler
Qt官方建议对自定义的QObject子类都要加这个宏,但要注意:某些类不是继承自QObject,这些类里加Q_OBJECT就会出错,例如QEvent,QGraphicsItem,QRunnable.
MOC的实现是一个预处理器,使用MOC的方式,所有平台上的标准的C++编译器都能支持Qt。从而不需要实现一个新的跨平台的Qt编译器。moc是为了解决反射的问题,但是一些动态的编程语言(如Python,Ruby等)中,语言本身自带反射功能。Qt程序之所以编译速度慢,主要是因为在 Qt 将源代码交给标准C++编译器,需要事先将这些扩展的语法去除掉。完成这一操作的就是 moc。
如果工程是用qmake生成Makefile进行编译,那么其中就包含了调用moc的规则,我们不必直接使用moc.exe。moc.exe会读取头文件,查看是否有Q_OBJECT宏定义,如果有则根据这个头文件生成相应的moc_.cpp,该文件同样将进入编译系统,最终被链接到二进制代码中去,QtCreator生成的代码就是通过编译链接时,把moc_widget.o与其他目标文件链接到一起,这种方式不用改源代码,相对而言比较顺眼。
moc_.cpp主要实现了头文件中的Q_OBJECT宏和SIGNAL,也就是将Qt扩展的语法去掉,再交给C++编译器。
2 宏QOBJECT slots emit
2.1 QOBJECT
#define Q_OBJECT \
public: \
QT_WARNING_PUSH \ //这个宏实际作用就是告诉编译器GCC diagnostic push 编译相关
Q_OBJECT_NO_OVERRIDE_WARNING \ //实际是一个空宏,增强代码可读性
static const QMetaObject staticMetaObject; \ //静态元对象,这个对象会保存该类的元对象系统信息
virtual const QMetaObject *metaObject() const; \
//获取当前类对象里面内部元对象的公开接口,通常情况下都会返回类的静态元对象 staticMetaObject,如果当前类的对象内部使用了动态元对象(仅 QML 程序才有),才会出现返回非静态元对象
virtual void *qt_metacast(const char *); \
//qt_metacast 是程序运行时的对象指针转换,它可以将派生类对象的指针安全地转为基类对象指针,这是 Qt 不依赖编译器特性,自己实现的运行时类型转换。qt_metacast 参数是基类名称字符串,返回值是转换后的基类对象指针,如果转换不成功,返回 NULL
virtual int qt_metacall(QMetaObject::Call, int, void **); \
//qt_metacall 是非常重要的虚函数,在信号到槽的执行过程中,qt_metacall 就是负责槽函数的调用,属性系统的读写等也是靠 qt_metacall 实现
QT_WARNING_POP \ //类似
QT_TR_FUNCTIONS \ //宏用于国际化功能,实际是两个内联函数tr和trUtf8
private: \
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
//Q_DECL_HIDDEN_STATIC_METACALL 是个空宏 提醒隐蔽的私有静态函数,前面的qt_metacall调用该私有静态函数实现槽函数调用,真正调用槽函数的就是 qt_static_metacall
struct QPrivateSignal {};
//QPrivateSignal 是一个私有的空结构体,对函数功能来说没啥用,就是在信号被触发时,挂在参数里提醒程序员这是一个私有信号的触发。
2.2 信号槽相关宏
# define slots //slots 实际是空 可以不写
# define signals public // 实际是public 可以在public下写信号函数# define emit //实际是空 调用emit signal实际就是调用public的signals emit给程序员看
# define Q_INVOKABLE//又是空宏 使用Q_INVOKABLE来修饰成员函数,目的在于被修饰的成员函数能够被元对象系统所唤起,QMetaObject::invokeMethod 调用方法。
3 信号和槽
在GUI编程中,我们总希望一个对象可以和其他对象进行通信;一些工具包采用回调机制,Qt采用信号槽机制;所谓回调即是将一个指向函数的指针传递给处理函数,处理函数可以再适当的地方进行调用;两个缺陷:不是类型安全的,不可以保证在调用函数时使用正确的参数;强耦合,处理函数必须知道调用哪个回调函数。
3.1 信号和槽的链接使用
信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)。
关于链接类型
- 自动连接(AutoConnection),默认的连接方式。如果信号与槽,也就是发送者与接受者在同一线程,等同于直接连接;如果发送者与接受者处在不同线程,等同于队列连接。
- 直接连接(DirectConnection)当信号发射时,槽函数立即直接调用。无论槽函数所属对象在哪个线程,槽函数总在发送者所在线程执行。
- 队列连接(QueuedCo