Qt中的元对象系统

Qt中的元对象系统

moc

什么是元对象?
In computer science, a metaobject or meta-object is any entity that manipulates, creates, describes, or implements other objects. The object that the metaobject is about is called the base object. Some information that a metaobject might store is the base object's type, interface, class, methods, attributes, variables, functions, control structures, etc.
在计算机科学中,元对象是这样一个东西:它可以操纵,创建,描述,或执行其他对象。元对象描述的对象称为基对象。元对象可能存这样的信息:基础对象的类型,接口,类,方法,属性,变量,函数,控制结构等。

QT
中的元对象系统

QT
中的元对象系统基于以下三种东西:
1. QObject
提到这个类,相信大家都不陌生。几乎所有在QT开发的类都继承于此类。
QObject
这个类为其他需要用到元对象系统的类提供了一个基类。
2.Q_OBJECT
放在类声明中的Q_OBJECT宏是用来为这个类开启元对象特性的,例如动态的属性(dynamic properties),信号(signals)以及槽(slots)
可这个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:

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宏,如果遗漏则在编译时给出错误提示。

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方法都是调用这里的方法。
3 MOC
what does MOC do?
MOC
编译器为QObject子类提供了一些实现元对象特性所需的一些代码。就比如说信号,大家只是在类声明的时候声明了所需的信号,就像下面这样:

样当我们把鼠标放到Q_SINGNALS时可以看到下面

这个宏感觉有点奇怪,它要替换的是它自己。实际上它只是一个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
看起来还真不少,没关系,让我们来一点一点分析。说到这里我先说一点废话,最近大家都在学习QT,但我发现大家掌握的都不够深入。如果你仅仅是学习一些类的使用时不够的,这对你技术的提高很不好,这是一个习惯,而且是个好习惯,如果你养成了这种习惯,你会发现你学东西会很快,技术当然也是突飞猛进。也会有人会说我也学了一些深层次的东西,就比如说我也学会了信号槽原理啦,我知道怎么用了。作为技术人员,如果你把你的技术高度定位在会用,你会发现你就在那个高度了。闲话少说,我们回到刚才那个话题上来。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_+类名)格式的静态变量。

继续,wow,又一个静态变量:
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"
};
很容易看出来这里放置的是类名和所有的信号槽。这个东西很重要,就是有了这个才可以通过索引到相应的信号和槽的,至于信号槽更深入的东西,信号槽是如何实现的,我们将在下面的内容中讲到。

继续,wow,还没有完呢,又一个全局静态变量:
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;
}
现在到了QT里的元调用了,这时是        QT信号槽原理实现很重要的一步,这里_id就是信号槽对应的索引,可每个信号或是槽对应哪个索引呢,看看它的元字符串数据qt_meta_stringdata_MainWindow,我想你应该知道了。

继续,接着就是信号的实现了:
// 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为所有信号生成的方法了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值