使用 meta object system
- 继承自 QOject
- 类定义中添加 Q_OBJECT 宏
- 使用 moc 程序对包含该宏的文件进行处理
采用 qmake 进行处理时,如果头文件xxx.h内包含 Q_OBJECT 宏,将生成 moc_xxx.cpp 文件。如果xxx.cpp文件内包含宏,将生成 xxx.moc 文件(这时,我们需要在xxx.cpp文件内添加 #include"xxx.moc")
Q_OBJECT宏
包括两个方面,
- 该宏在C++中的展开,有编译预处理器完成
- moc 程序对该宏的处理
宏定义
#define Q_OBJECT \ public: \ Q_OBJECT_CHECK \ static const QMetaObject staticMetaObject; \ virtual const QMetaObject *metaObject() const; \ virtual void *qt_metacast(const char *); \ QT_TR_FUNCTIONS \ virtual int qt_metacall(QMetaObject::Call, int, void **); \ private:
而宏 QT_TR_FUNCTIONS 将展开为我们使程序国际化经常使用的 tr 与 trUtf8函数
# define QT_TR_FUNCTIONS \ static inline QString tr(const char *s, const char *c = 0) \ { return staticMetaObject.tr(s, c); } \ static inline QString trUtf8(const char *s, const char *c = 0) \ { return staticMetaObject.trUtf8(s, c); } \ static inline QString tr(const char *s, const char *c, int n) \ { return staticMetaObject.tr(s, c, n); } \ static inline QString trUtf8(const char *s, const char *c, int n) \ { return staticMetaObject.trUtf8(s, c, n); }
moc 处理
Q_OBJECT 为我们添加了这么多成员函数,而这些函数我们有没有自己去实现,那么其定义在哪儿呢? 这就是 moc 为我们做的,自动生成这些成员函数的定义,放于生成的 xxx.moc 或 moc_xxx.cpp 文件内
注意:两个文件的区别(如果你用cmake或其他工具的话,这点可能很重要)
- 生成的 moc_xxx.cpp 中会自动包含 xxx.h 头文件,所以它可以被独立编译成目标文件(.o 或 .obj)
- 生成的 xxx.moc 是不会包含 xxx.cpp 的(要是包含就出问题了,能发现吧?),因此 xxx.moc 中没有类定义,它无法被独立编译,只能被 include 到 xxx.cpp 中。
QMetaObject
既然 Q_OBJECT 展开成与 QMetaObject 有关的成员函数,看一下QMetaObject 都提供哪些常用功能
- className() 返回类的名字
- superClass() 返回父类的 QMetaObject 对象
- method()与methodCount() 提供meta函数信息(包括signals, slots 与 invokable函数)
- enumerator()与 enumeratorCount() 提供enum类型信息
- propertyCount()与 property() 提供属性信息
- constructor()与 constructorCount() 提供 meta-constructors 信息
既然meta object能为我们的类提供这么多信息,那么这些信息存放哪儿了(大家肯定都能猜到秘密在moc生成的文件内,但为清楚起见,我们看一下QMetaObject的定义)。
这是用 struct 定义的一个类,我们略过其它,只看其数据成员
struct QMetaObject { ... ... private: struct { const QMetaObject *superdata; const char *stringdata; const uint *data; const void *extradata; } d; }
其中呢,
moc生成的文件
随便找个 moc 文件出来看看
static const uint qt_meta_data_HPixmapScene[] = {...}; static const char qt_meta_stringdata_HPixmapScene[] = {...}; const QMetaObject HPixmapScene::staticMetaObject = { { &QGraphicsScene::staticMetaObject, qt_meta_stringdata_HPixmapScene, qt_meta_data_HPixmapScene, 0 } };
这是一个QGraphicsScene的子类。对比前面QMetaObject的数据成员看看,是不是很简单:
uint数组
接下来我们可以看看 uint 数组,这个数组中存放的是一些索引值,来指导我们从char字符串中提取信息
static const uint qt_meta_data_HPixmapScene[] = {
4, 0, 0, 0, 2, 14, 0, 0, 0, 0, 0, 0, 0, 0,
18, 14, 13, 13, 0x0a, 41, 37, 13, 13, 0x0a,
0 };
对比QMetaObject用到的数据结构 QMetaObjectPrivate,看看,是不是豁然开朗了:uint 数组中的第一段 就对应这个结构体
struct QMetaObjectPrivate { int revision; int className; int classInfoCount, classInfoData; int methodCount, methodData; int propertyCount, propertyData; int enumeratorCount, enumeratorData; int constructorCount, constructorData; int flags; int signalCount; }
char 数组
配合上面的索引数组,继续看一下:
static const char qt_meta_stringdata_HPixmapScene[] = { "HPixmapScene\0\0pix\0setPixmap(QPixmap)\0" "img\0setImage(QImage)\0" };
索引数组中 className 为0,我们看字符数组中从0开始是什么?恩,就是我们类的名字。
我们类的类中还定义的两个slot函数
public slots: void setPixmap(const QPixmap& pix); void setImage(const QImage& img);
我们看看是怎么对应的,
- 首先看索引数组 methodCount=2, methodData=14,告诉我们有两个method,数据从索引数组的14开始
-
然后,我们从索引数组的14开始看,{18, 14, 13, 13, 0x0a
- 这儿,18和14对应到我们的char数组了,看一下,分别是setPixmap(QPixmap) 和 pix。怎么样,和我们定义的函数对应上了吧。
有什么不对
- 其实上面有一处不太对,从开始就不太对。什么不对呢?
Qt manual 对此只提了一句,在QObject的manual中,除此之外,似乎再没出现过:要用 meta object system,我们不一定要继承QObject,普通的C++的类也可以,与此对应,我们不用Q_OBJECT宏,而用Q_GADGET宏,当然,这个只提供部分meta object system 的功能,比如 Qt 元对象系统之 "enum自省"
另外 moc 生成的文件内有一个 Q_NO_DATA_RELOCATION 宏,无论用 Q_OBJECT 还是 Q_GADGET,我不清楚该宏起什么作用的。
整理思路太累了,写完这个,还不清楚什么时候再写(二)
|