什么是元对象系统?
在计算机科学中,元对象是这样一个东西:它可以操纵,创建,描述,或执行其他对象。元对象描述的对象称为基对象。元对象可能存这样的信息:基础对象的类型,接口,类,方法,属性,变量,函数,控制结构等。
Qt元对象系统提供了对象间的通信机制(信号和槽)、运行时的类型信息和动态属性系统的支持,是标准C++的一个扩展,它使得Qt能更好的实现GUI图形用户界面编程。
QT中的元对象系统
QT中的元对象系统基于以下三种东西:
1. QObject
提到这个类,相信大家都不陌生。几乎所有在QT开发的类都继承于此类。QObject这个类为其他需要用到元对象系统的类提供了一个基类。
2.Q_OBJECT
放在类声明中的Q_OBJECT宏是用来为这个类开启元对象特性的,例如动态的属性(dynamic properties),信号(signals)以及槽(slots)。Q_OBJECT宏必须出新在类的私有声明区,以启动元对象的特性。
可这个Q_OBJECT宏到底是什么呢?如下所示,大家可以看到它是一些函数和一个静态的类成员。
Q_OBJECT宏内容如下:
#define Q_OBJECT \ public: \ Q_OBJECT_CHECK \ static const QMetaObject staticMetaObject; \ Q_OBJECT_GETSTATICMETAOBJECT \ virtual const QMetaObject *metaObject() const; \ virtual void *qt_metacast(const char *); \ QT_TR_FUNCTIONS \ virtual int qt_metacall(QMetaObject::Call, int, void **); \ private:
1.首先调用了Q_OBJECT_CHECK (插入了一个 qt_check_for_QOBJECT_macro的 template function),如下。
Q_OBJECT_CHECK宏的内容是:
#define Q_OBJECT_CHECK \ template <typename T> inline void qt_check_for_QOBJECT_macro(const T &_q_argument) const \ { int i = qYouForgotTheQ_OBJECT_Macro(this, &_q_argument); i = i; }
这里是检查类声明中是否遗漏Q_OBJECT宏,如果遗漏则在编译时给出错误提示。
2.然后是全局常量 QMetaObject对象,因此可以用 QClassname::staticMetaObject直接访问,另外提供了两个接口函数metaObject()用于不同的class返回自己的 staticMetaObject、qt_metacast()用于转换,我们在 moc产生的文件里面可以找到这两个接口的实现:
3.而QT_TR_FUNCTIONS宏的内容是:
# 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); }
一些字符串操作的算法,这几个方法实际上是隐藏了父类QObject中这个几个方法。我们在类定义中调用的tr或是trUtf8方法都是调用这里的方法。
4.最后是接口函数qt_metacall,它的作用是查表。
3.元对象编译器 MOC
MOC编译器为QObject子类提供了一些实现元对象特性所需的一些代码。就比如说信号,大家只是在类声明的时候声明了所需的信号。这个宏感觉有点奇怪,它要替换的是它自己。实际上它只是一个protected的关键字。moc编译器会用protected来替换它。但moc编译器做的远过于此,它会用protected来替换它,但同时还会为这个信号声明方法体。
当moc工具读取一个c++源文件时,如果它发现类的声明中有Q_OBJECT时,它会产生另外一个c++源文件,这个文件中就包含了这些类的元对象代码。这个新产生的文件可以以#include形式包含到这个类的源文件中,但更常用的做法是和这个类的定义一起编译连接。
以QT Creator为例,以下代码MainWindow类是我创建工程时选择Mobile Qt Application时自动创建的一个类,其中我自己增加了3个信号3槽,代码如下所示:
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QString> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); public slots: void slotTest1(int value); void slotTest2(const QString value); void slotTest3(bool value); signals: void signalTest1(int value); void signalTest2(const QString value); void signalTest3(bool value); public: void test(); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; } void MainWindow::slotTest1(int value) { } void MainWindow::slotTest2(const QString value) { } void MainWindow::slotTest3(bool value) { } void MainWindow::test() { connect(this, SIGNAL(signalTest1(int)), this, SLOT(slotTest1(int))); }
这个类很简单,但当moc读到mainwindow.h时发现Q_OBJECT时,它会在在这个工程目录下的moc目录下创建moc_mainwindow.cpp。让我们来看看这里面都有些什么:
/**************************************************************************** ** Meta object code from reading C++ file 'mainwindow.h' ** ** Created: Thu Sep 2 16:23:24 2010 ** by: The Qt Meta Object Compiler version 62 (Qt 4.6.3) ** ** WARNING! All changes made in this file will be lost! *****************************************************************************/ #include "../mainwindow.h" #if !defined(Q_MOC_OUTPUT_REVISION) #error "The header file 'mainwindow.h' doesn't include <QObject>." #elif Q_MOC_OUTPUT_REVISION != 62 #error "This file was generated using the moc from 4.6.3. It" #error "cannot be used with the include files from this version of Qt." #error "(The moc has changed too much.)" #endif QT_BEGIN_MOC_NAMESPACE static const uint qt_meta_data_MainWindow[] = { // content: 4, // revision 0, // classname 0, 0, // classinfo 6, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 3, // signalCount // signals: signature, parameters, type, tag, flags 18, 12, 11, 11, 0x05, 35, 12, 11, 11, 0x05, 56, 12, 11, 11, 0x05, // slots: signature, parameters, type, tag, flags 74, 12, 11, 11, 0x0a, 89, 12, 11, 11, 0x0a, 108, 12, 11, 11, 0x0a, 0 // eod }; static const char qt_meta_stringdata_MainWindow[] = { "MainWindow\0\0value\0signalTest1(int)\0" "signalTest2(QString)\0signalTest3(bool)\0" "slotTest1(int)\0slotTest2(QString)\0" "slotTest3(bool)\0" }; const QMetaObject MainWindow::staticMetaObject = { { &QMainWindow::staticMetaObject, qt_meta_stringdata_MainWindow, qt_meta_data_MainWindow, 0 } }; #ifdef Q_NO_DATA_RELOCATION const QMetaObject &MainWindow::getStaticMetaObject() { return staticMetaObject; } #endif //Q_NO_DATA_RELOCATION const QMetaObject *MainWindow::metaObject() const { return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; } void *MainWindow::qt_metacast(const char *_clname) { if (!_clname) return 0; if (!strcmp(_clname, qt_meta_stringdata_MainWindow)) return static_cast<void*>(const_cast< MainWindow*>(this)); return QMainWindow::qt_metacast(_clname); } int MainWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { _id = QMainWindow::qt_metacall(_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { switch (_id) { case 0: signalTest1((*reinterpret_cast< int(*)>(_a[1]))); break; case 1: signalTest2((*reinterpret_cast< const QString(*)>(_a[1]))); break; case 2: signalTest3((*reinterpret_cast< bool(*)>(_a[1]))); break; case 3: slotTest1((*reinterpret_cast< int(*)>(_a[1]))); break; case 4: slotTest2((*reinterpret_cast< const QString(*)>(_a[1]))); break; case 5: slotTest3((*reinterpret_cast< bool(*)>(_a[1]))); break; default: ; } _id -= 6; } return _id; } // SIGNAL 0 void MainWindow::signalTest1(int _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 0, _a); } // SIGNAL 1 void MainWindow::signalTest2(const QString _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 1, _a); } // SIGNAL 2 void MainWindow::signalTest3(bool _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 2, _a); } QT_END_MOC_NAMESPACE
看起来还真不少,没关系,让我们来一点一点分析。moc还真做的不少呢,不过很少有人意识到这点。让我们来看看它都做了些什么。
先看看的一些预处理,wow, 不少的if error:
#if !defined(Q_MOC_OUTPUT_REVISION) #error "The header file 'mainwindow.h' doesn't include <QObject>." #elif Q_MOC_OUTPUT_REVISION != 62 #error "This file was generated using the moc from 4.6.3. It" #error "cannot be used with the include files from this version of Qt." #error "(The moc has changed too much.)" #endif
接着是一个宏QT_BEGIN_MOC_NAMESPACE
在qglobal.h我们可以找到如下:
# define QT_BEGIN_MOC_NAMESPACE QT_USE_NAMESPACE # define QT_USE_NAMESPACE using namespace :: QT_NAMESPACE;
所以QT_BEGIN_MOC_NAMESPACE就是using namespace :: QT_NAMESPACE;现在看起来是不是很熟悉啊,哈哈
如下说完这个我们也一并把它的配对的一项QT_END_MOC_NAMESPACE也看一下:
在qglobal.h我们可以找到如下:
# define QT_END_MOC_NAMESPACE
所以它就是一个空的,什么也没有,仅仅是为了配对,看起来比较好看吧。
接着是一个静态全局常量qt_meta_data_MainWindow:
static const uint qt_meta_data_MainWindow[] = { // content: 4, // revision 0, // classname 0, 0, // classinfo 6, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 3, // signalCount // signals: signature, parameters, type, tag, flags 18, 12, 11, 11, 0x05, 35, 12, 11, 11, 0x05, 56, 12, 11, 11, 0x05, // slots: signature, parameters, type, tag, flags 74, 12, 11, 11, 0x0a, 89, 12, 11, 11, 0x0a, 108, 12, 11, 11, 0x0a, 0 // eod };
你写的每个类,只要是从QObject继承,并且在类的声明中包含Q_OBJECT宏,moc都会为了定义一个(qt_meta_data_+类名)格式的静态变量。
继续,又一个静态变量:
static const char qt_meta_stringdata_MainWindow[] = { "MainWindow\0\0value\0signalTest1(int)\0" "signalTest2(QString)\0signalTest3(bool)\0" "slotTest1(int)\0slotTest2(QString)\0" "slotTest3(bool)\0" };
很容易看出来这里放置的是类名和所有的信号槽。这个东西很重要,就是有了这个才可以通过索引到相应的信号和槽的,至于信号槽更深入的东西,信号槽是如何实现的,我们将在下面的内容中讲到。
继续,还没有完呢,又一个全局静态变量:
const QMetaObject MainWindow::staticMetaObject = { { &QMainWindow::staticMetaObject, qt_meta_stringdata_MainWindow, qt_meta_data_MainWindow, 0 } };
这个就是MainWindow的静态元对象了,看的出来是个结构体,包含了三项:
1. 它的父类的静态元对象。
2. 它的元字符串数据。
3. 它的元数据。
继续看,这下开始到方法了:
#ifdef Q_NO_DATA_RELOCATION const QMetaObject &MainWindow::getStaticMetaObject() { return staticMetaObject; } #endif //Q_NO_DATA_RELOCATION
这个方法很简单,就是返回它自己的静态元对象。
const QMetaObject *MainWindow::metaObject() const { return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; }
很简单,如果它最顶层父类QObject类的d_ptr->metaObject不为空则返回最顶层父类QObject类的d_ptr->metaObject,否则返回staticMetaObject的指针。
继续,
void *MainWindow::qt_metacast(const char *_clname) { if (!_clname) return 0; if (!strcmp(_clname, qt_meta_stringdata_MainWindow)) return static_cast<void*>(const_cast< MainWindow*>(this)); return QMainWindow::qt_metacast(_clname); }
看的出来这个方法就是控制类型转换的了,通过比较类的元字符串数据,如果相等则当前的this指针转换为空指针返回,否则继续向上找,如果最顶上的类的元字符串数据还是不相同则返回空指针。
继续,
int MainWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { _id = QMainWindow::qt_metacall(_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { switch (_id) { case 0: signalTest1((*reinterpret_cast< int(*)>(_a[1]))); break; case 1: signalTest2((*reinterpret_cast< const QString(*)>(_a[1]))); break; case 2: signalTest3((*reinterpret_cast< bool(*)>(_a[1]))); break; case 3: slotTest1((*reinterpret_cast< int(*)>(_a[1]))); break; case 4: slotTest2((*reinterpret_cast< const QString(*)>(_a[1]))); break; case 5: slotTest3((*reinterpret_cast< bool(*)>(_a[1]))); break; default: ; } _id -= 6; } return _id; }
继续,接着就是信号的实现了:
// SIGNAL 0 void MainWindow::signalTest1(int _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 0, _a); } // SIGNAL 1 void MainWindow::signalTest2(const QString _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 1, _a); } // SIGNAL 2 void MainWindow::signalTest3(bool _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 2, _a); }
这里就是moc为所有信号生成的方法了。