在Qt中有一个元对象系统,该系统通过扩展C++,提供了信号与槽机制(事件处理)和属性系统。为了支持这些新特性,Qt引入了元对象编译器(Meta-Object Compiler,MOC),用来解析头文件并生成moc_className.cpp,将其与其他文件一起编译,实现元对象系统的基本功能。

MOC的工作原理

MOC会去读取C++的源文件,寻找qt特定宏(如Q_OBJECTQ_PROPERTYsignalsslots等),当它找到这些宏时,就会生成一个moc_className.cpp,其中包括了类的元信息,然后将这个文件以某种方式编译和链接到应用程序中。
MOC生成的代码主要有:

  • 元对象代码:指对象的信息,如类名、超类名、方法、属性、信号、槽。
  • 信号和槽的实现:实现了信号槽机制,允许对象之间进行低耦合通信。
  • 动态属性系统代码:允许在运行时内省和修改对象的属性。

案例

定义一个类XObject,为了让该类可以使用元对象系统,类定义中添加了Q_OBJECT宏。

  • XObject.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #ifndef XOBJECT_H
    #define XOBJECT_H

    #include <QObject>
    class XObject : public QObject
    {
    Q_OBJECT
    public:
    XObject();
    ~XObject();
    Q_PROPERTY(int count READ getCount WRITE setCount)
    int getCount() const;
    void setCount(int val);
    signals:
    void sig_countChanged(int val);
    public slots:
    void slot_countChanged(int val);
    private:
    int count;
    };

    #endif // XOBJECT_H
  • XObject.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #include "XObject.h"
    #include <QDebug>

    XObject::XObject()
    {
    connect(this, SIGNAL(sig_countChanged(int)), this, SLOT(slot_countChanged(int)));
    }

    XObject::~XObject()
    {

    }

    int XObject::getCount() const
    {
    return count;
    }

    void XObject::setCount(int val)
    {
    if ( val != count)
    {
    count = val;
    emit sig_countChanged(val);
    }
    }

    void XObject::slot_countChanged(int val)
    {
    qDebug() << "count is modified: " << val;
    }
  • moc_XObject.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    /****************************************************************************
    ** Meta object code from reading C++ file 'XObject.h'
    **
    ** Created by: The Qt Meta Object Compiler version 67 (Qt 5.14.2)
    **
    ** WARNING! All changes made in this file will be lost!
    *****************************************************************************/

    #include <memory>
    #include "../XObject.h"
    #include <QtCore/qbytearray.h>
    #include <QtCore/qmetatype.h>
    #if !defined(Q_MOC_OUTPUT_REVISION)
    #error "The header file 'XObject.h' doesn't include <QObject>."
    #elif Q_MOC_OUTPUT_REVISION != 67
    #error "This file was generated using the moc from 5.14.2. 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
    QT_WARNING_PUSH
    QT_WARNING_DISABLE_DEPRECATED
    struct qt_meta_stringdata_XObject_t {
    QByteArrayData data[6];
    char stringdata0[54];
    };
    #define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_XObject_t, stringdata0) + ofs \
    - idx * sizeof(QByteArrayData)) \
    )
    static const qt_meta_stringdata_XObject_t qt_meta_stringdata_XObject = {
    {
    QT_MOC_LITERAL(0, 0, 7), // "XObject"
    QT_MOC_LITERAL(1, 8, 16), // "sig_countChanged"
    QT_MOC_LITERAL(2, 25, 0), // ""
    QT_MOC_LITERAL(3, 26, 3), // "val"
    QT_MOC_LITERAL(4, 30, 17), // "slot_countChanged"
    QT_MOC_LITERAL(5, 48, 5) // "count"

    },
    "XObject\0sig_countChanged\0\0val\0"
    "slot_countChanged\0count"
    };
    #undef QT_MOC_LITERAL

    static const uint qt_meta_data_XObject[] = {

    // content:
    8, // revision
    0, // classname
    0, 0, // classinfo
    2, 14, // methods
    1, 30, // properties
    0, 0, // enums/sets
    0, 0, // constructors
    0, // flags
    1, // signalCount

    // signals: name, argc, parameters, tag, flags
    1, 1, 24, 2, 0x06 /* Public */,

    // slots: name, argc, parameters, tag, flags
    4, 1, 27, 2, 0x0a /* Public */,

    // signals: parameters
    QMetaType::Void, QMetaType::Int, 3,

    // slots: parameters
    QMetaType::Void, QMetaType::Int, 3,

    // properties: name, type, flags
    5, QMetaType::Int, 0x00095103,

    0 // eod
    };

    void XObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
    {
    if (_c == QMetaObject::InvokeMetaMethod) {
    auto *_t = static_cast<XObject *>(_o);
    Q_UNUSED(_t)
    switch (_id) {
    case 0: _t->sig_countChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
    case 1: _t->slot_countChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
    default: ;
    }
    } else if (_c == QMetaObject::IndexOfMethod) {
    int *result = reinterpret_cast<int *>(_a[0]);
    {
    using _t = void (XObject::*)(int );
    if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&XObject::sig_countChanged)) {
    *result = 0;
    return;
    }
    }
    }
    #ifndef QT_NO_PROPERTIES
    else if (_c == QMetaObject::ReadProperty) {
    auto *_t = static_cast<XObject *>(_o);
    Q_UNUSED(_t)
    void *_v = _a[0];
    switch (_id) {
    case 0: *reinterpret_cast< int*>(_v) = _t->getCount(); break;
    default: break;
    }
    } else if (_c == QMetaObject::WriteProperty) {
    auto *_t = static_cast<XObject *>(_o);
    Q_UNUSED(_t)
    void *_v = _a[0];
    switch (_id) {
    case 0: _t->setCount(*reinterpret_cast< int*>(_v)); break;
    default: break;
    }
    } else if (_c == QMetaObject::ResetProperty) {
    }
    #endif // QT_NO_PROPERTIES
    }

    QT_INIT_METAOBJECT const QMetaObject XObject::staticMetaObject = { {
    QMetaObject::SuperData::link<QObject::staticMetaObject>(),
    qt_meta_stringdata_XObject.data,
    qt_meta_data_XObject,
    qt_static_metacall,
    nullptr,
    nullptr
    } };


    const QMetaObject *XObject::metaObject() const
    {
    return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
    }

    void *XObject::qt_metacast(const char *_clname)
    {
    if (!_clname) return nullptr;
    if (!strcmp(_clname, qt_meta_stringdata_XObject.stringdata0))
    return static_cast<void*>(this);
    return QObject::qt_metacast(_clname);
    }

    int XObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
    {
    _id = QObject::qt_metacall(_c, _id, _a);
    if (_id < 0)
    return _id;
    if (_c == QMetaObject::InvokeMetaMethod) {
    if (_id < 2)
    qt_static_metacall(this, _c, _id, _a);
    _id -= 2;
    } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
    if (_id < 2)
    *reinterpret_cast<int*>(_a[0]) = -1;
    _id -= 2;
    }
    #ifndef QT_NO_PROPERTIES
    else if (_c == QMetaObject::ReadProperty || _c == QMetaObject::WriteProperty
    || _c == QMetaObject::ResetProperty || _c == QMetaObject::RegisterPropertyMetaType) {
    qt_static_metacall(this, _c, _id, _a);
    _id -= 1;
    } else if (_c == QMetaObject::QueryPropertyDesignable) {
    _id -= 1;
    } else if (_c == QMetaObject::QueryPropertyScriptable) {
    _id -= 1;
    } else if (_c == QMetaObject::QueryPropertyStored) {
    _id -= 1;
    } else if (_c == QMetaObject::QueryPropertyEditable) {
    _id -= 1;
    } else if (_c == QMetaObject::QueryPropertyUser) {
    _id -= 1;
    }
    #endif // QT_NO_PROPERTIES
    return _id;
    }

    // SIGNAL 0
    void XObject::sig_countChanged(int _t1)
    {
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
    }
    QT_WARNING_POP
    QT_END_MOC_NAMESPACE
  1. 文件头部

    1
    2
    3
    4
    5
    6
    7
    /****************************************************************************
    ** Meta object code from reading C++ file 'XObject.h'
    **
    ** Created by: The Qt Meta Object Compiler version 67 (Qt 5.14.2)
    **
    ** WARNING! All changes made in this file will be lost!
    *****************************************************************************/

    这部分说明了这个文件是从XObject.h读取并生成的元对象代码,生成是由特定版本的Qt元对象编译器完成的。该文件是自动生成的,用户不应手动编辑它。

  2. 包含的头文件

    1
    2
    3
    4
    #include <memory>
    #include "../XObject.h"
    #include <QtCore/qbytearray.h>
    #include <QtCore/qmetatype.h>

    这里包含了必要的库和头文件:
    memory用于智能指针。
    XObject.h是实际的类定义文件。
    qbytearray.h和qmetatype.h是Qt提供的用于处理字节数组和元类型的头文件。

  3. MOC版本检查

    1
    2
    3
    4
    5
    6
    7
    #if !defined(Q_MOC_OUTPUT_REVISION)
    #error "The header file 'XObject.h' doesn't include <QObject>."
    #elif Q_MOC_OUTPUT_REVISION != 67
    #error "This file was generated using the moc from 5.14.2. It"
    #error "cannot be used with the include files from this version of Qt."
    #error "(The moc has changed too much.)"
    #endif

这部分检查是否包含了头文件(这是Qt类的基类),以及MOC的输出版本是否与当前的Qt版本匹配。这是为了保证生成的元对象代码与类定义的一致性。

  1. 元信息结构
    1
    2
    3
    4
    5
    6
    7
    QT_BEGIN_MOC_NAMESPACE
    QT_WARNING_PUSH
    QT_WARNING_DISABLE_DEPRECATED
    struct qt_meta_stringdata_XObject_t {
    QByteArrayData data[6];
    char stringdata0[54];
    };
    定义了一个结构体qt_meta_stringdata_XObject_t,用于存储与XObject类相关的字符串数据和元信息。
  2. 字符串数据初始化
    1
    2
    3
    4
    5
    #define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_XObject_t, stringdata0) + ofs \
    - idx * sizeof(QByteArrayData)) \
    )
    该宏用于初始化字符串数据,qt_meta_stringdata_XObject中包含了信号、槽和属性的名称。
  3. 元数据数组
    cpp
    static const uint qt_meta_data_XObject[] = {
    // content:
    8, // revision
    0, // classname
    0, 0, // classinfo
    2, 14, // methods
    1, 30, // properties
    0, 0, // enums/sets
    0, 0, // constructors
    0, // flags
    1, // signalCount

// signals: name, argc, parameters, tag, flags
1, 1, 24, 2, 0x06 /* Public /,
// slots: name, argc, parameters, tag, flags
4, 1, 27, 2, 0x0a /
Public */,
// signals: parameters
QMetaType::Void, QMetaType::Int, 3,
// slots: parameters
QMetaType::Void, QMetaType::Int, 3,
// properties: name, type, flags
5, QMetaType::Int, 0x00095103,
0 // eod
};
这里定义了qt_meta_data_XObject数组,包含了有关XObject类的元数据信息,包括:
版本号、类名、类信息、方法数量、属性数量、信号数量等。
信号和槽的名称、参数数量、参数类型等信息。
此外,还有属性的名称、类型和标志。
7. 静态元调用函数
cpp
void XObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
该函数用于处理信号和槽的调用。
根据调用类型(_c),它可以调用信号或槽,或者读取和写入属性。
8. metaObject和qt_metacast方法
cpp
const QMetaObject *XObject::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}

void *XObject::qt_metacast(const char *_clname)
{
if (!_clname) return nullptr;
if (!strcmp(_clname, qt_meta_stringdata_XObject.stringdata0))
return static_cast<void*>(this);
return QObject::qt_metacast(_clname);
}
metaObject返回该类的元对象信息。
qt_metacast用于支持动态类型转换。
9. qt_metacall方法
cpp
int XObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
if (_id < 2)
qt_static_metacall(this, _c, _id, _a);
_id -= 2;
}

return _id;
}
qt_metacall用于处理元对象调用,包括信号和槽的调用。
10. 信号的实现

1
2
3
4
5
6
// SIGNAL 0
void XObject::sig_countChanged(int _t1)
{
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

这个函数实现了sig_countChanged信号的激活,负责触发信号并传递参数。