当前位置: 首页 > 工具软件 > MOC > 使用案例 >

Qt MOC元对象系统

农均
2023-12-01

引言

Qt 本身不是一种编程语言,它是C++编写的一个框架。通过使用一个叫做MOC (Meta-Object Compiler)的预处理器来扩展标准的C++语言,从而实现信号和槽等特性。Qt编写的程序可以使用Clang、GCC、ICC、MinGW和MSVC等多种标准C++编译器进行编译。元对象系统是一个基于标准C++的扩展,为Qt提供了1、信号与槽机制2、实时类型信息3、动态属性系统。

定义

moc 全称是 Meta-Object Compiler,也就是“元对象编译器”。Qt 程序在交由标准编译器编译之前,先要使用 moc 分析 C++ 源文件。如果它发现在一个头文件中包含了宏 Q_OBJECT,则会生成另外一个 C++ 源文件。这个源文件中包含了 Q_OBJECT 宏的实现代码。这个新的文件名字将会是原文件名前面加上 moc_ 构成。这个新的文件同样将进入编译系统,最终被链接到二进制代码中去。因此我们可以知道,这个新的文件不是“替换”掉旧的文件,而是与原文件一起参与编译。另外,我们还可以看出一点,moc 的执行是在预处理器之前。因为预处理器执行之后,Q_OBJECT 宏就不存在了。

作用

元对象系统这样工作:

  1. Q_OBJECT宏声明一些内省函数(metaObject(),TR(),qt_matacall()和少量其他的函数)。这些函数必须在所有的QObject的子类中被实现。
  2. Qt的moc工具负责执行被Q_OBJECT宏声明的函数,同时负责执行所有的信号函数。
  3. QObject的成员函数,例如connect()和disconnect(),使用内省函数来工作。

元对象系统基于以下三类:

  1、QOBJECT类;
  2、类声明中的私有段的Q_OBJECT宏
  3、元对象编译器。

Moc读取C++源文件。如果它发现其中包含一个或多个类的声明中含有Q_OBJECT宏,它就会给含有Q_OBJECT宏的类生成另一个含有元对象代码的C++源文件。这个生成的源文件可以被类的源文件包含(#include)到或者和这个类的实现一起编译和连接。
除了提供对象间通讯的信号和槽机制之外(这也是介绍这个系统的主要原因),QObject中的元对象代码也实现其它特征:

    1、className()函数在运行的时候以字符串返回类的名称,不需要C++编译器中的运行时刻类型识别(RTTI)的支持。
    2、inherits()函数返回这个对象是否是一个继承于QObject继承树中一个特定类的类的实例。
    3、tr()和trUtf8()两个函数是用于国际化的字符串翻译。
    4、setPorperty()和property()两个函数是用来通过名称动态设置和获得对象属性的。
    5、metaObject()函数返回这个类所关联的元对象。

虽然使用QObject作为一个基类而不使用Q_OBJECT宏和元对象代码是可以的,但是如果Q_OBJECT宏没有被使用,那么这里的信号和槽以及其它特征描述都不会被提供。根据元对象系统的观点,一个没有元代码的QObject的子类和它含有元对象代码的最近的祖先相同。举例来说就是,className()将不会返回你的类的实际名称,返回的是它的这个祖先的名称。我们强烈建议QObject的所有子类使用Q_OBJECT宏,而不管它们是否实际使用了信号、槽和属性。

例子

为了查看 moc 生成的文件,我们使用一个很简单的 cpp 来测试:

//mainwindow.cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_1_clicked();
    
private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

这是一个空白的类,什么都没有实现。在经过编译之后,我们会在输出文件夹中找到 moc_mainwindow.cpp:

// moc_mainwindow.cpp

/****************************************************************************
** Meta object code from reading C++ file 'mainwindow.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.13.2)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/

#include <memory>
#include "../../mainwindow.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'mainwindow.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.13.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_MainWindow_t {
    QByteArrayData data[8];
    char stringdata0[153];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_MainWindow_t, stringdata0) + ofs \
        - idx * sizeof(QByteArrayData)) \
    )
static const qt_meta_stringdata_MainWindow_t qt_meta_stringdata_MainWindow = {
    {
QT_MOC_LITERAL(0, 0, 10), // "MainWindow"
QT_MOC_LITERAL(1, 11, 23), // "on_pushButton_1_clicked"
    },
    "MainWindow\0on_pushButton_1_clicked\0\0"
};
#undef QT_MOC_LITERAL

static const uint qt_meta_data_MainWindow[] = {

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

 // slots: name, argc, parameters, tag, flags
       1,    0,   44,    2, 0x08 /* Private */,
       3,    0,   45,    2, 0x08 /* Private */,
       4,    0,   46,    2, 0x08 /* Private */,
       5,    0,   47,    2, 0x08 /* Private */,
       6,    0,   48,    2, 0x08 /* Private */,
       7,    0,   49,    2, 0x08 /* Private */,

 // slots: parameters
    QMetaType::Void,
    QMetaType::Void,
    QMetaType::Void,
    QMetaType::Void,
    QMetaType::Void,
    QMetaType::Void,

       0        // eod
};

void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        auto *_t = static_cast<MainWindow *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->on_pushButton_1_clicked(); break;
        default: ;
        }
    }
    Q_UNUSED(_a);
}

QT_INIT_METAOBJECT const QMetaObject MainWindow::staticMetaObject = { {
    &QMainWindow::staticMetaObject,
    qt_meta_stringdata_MainWindow.data,
    qt_meta_data_MainWindow,
    qt_static_metacall,
    nullptr,
    nullptr
} };


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

void *MainWindow::qt_metacast(const char *_clname)
{
    if (!_clname) return nullptr;
    if (!strcmp(_clname, qt_meta_stringdata_MainWindow.stringdata0))
        return static_cast<void*>(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) {
        if (_id < 6)
            qt_static_metacall(this, _c, _id, _a);
        _id -= 6;
    } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
        if (_id < 6)
            *reinterpret_cast<int*>(_a[0]) = -1;
        _id -= 6;
    }
    return _id;
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE

正是对 Q_OBJECT 宏的展开,使我们的 MainWindow类拥有了这些多出来的属性和函数。注意,QT_TR_FUNCTIONS 这个宏也是在这里定义的。也就是说,如果你要使用 tr() 国际化,就必须使用 Q_OBJECT 宏,否则是没有 tr() 函数的。这期间最重要的就是 virtual const QMetaObject *metaObject() const; 函数。这个函数返回 QMetaObject 元对象类的实例,通过它,你就获得了 Qt 类的反射的能力:获取本对象的类型之类,而这一切,都不需要 C++ 编译器的 RTTI 支持。Qt 也提供了一个类似 C++ 的 dynamic_cast() 的函数 qobject_case(),而这一函数的实现也不需要 RTTI。另外,一个没有定义 Q_OBJECT 宏的类与它最接近的父类是同一类型的。也就是说,如果 A 继承了 QObject 并且定义了 Q_OBJECT,B 继承了 A 但没有定义 Q_OBJECT,C 继承了 B,则 C 的 QMetaObject::className() 函数将返回 A,而不是本身的名字。因此,为了避免这一问题,所有继承了 QObject 的类都应该定义 Q_OBJECT 宏,不管你是不是使用信号槽。

 类似资料: