Qt 核心机制解析(一、MOC机制)

C++扩展语法

Qt对C++进行了一些扩展,主要是三个方面:

  • 元对象系统,包含Q_OBJECT宏的文件,需要 moc 预处理。moc 把qt扩展的C++语法翻译为标准C++代码
  • 资源系统,.qrc文件需要 rcc 进行预处理
  • 界面系统,.ui文件需要 uic 进行预处理

要使用这些扩展机制,比较常用的方法是通过 qmake 或 cmake 生成 makefile,然后再生成可执行文件。在使用 cmake 构建项目时,需要加上以下语句来表明需要使用Qt的扩展机制:

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

我们主要研究的是MOC机制,Qt的核心机制也是通过 MOC 来实现的,Qt 相对于标准 C++增添的特性主要有以下体现:

  • 支持对象间通信信号与槽机制
  • 支持可查询和可设计的动态对象属性机制
  • 事件和事件过滤器
  • 国际化支持
  • 支持多任务的定时器
  • 支持按层检索的对象树
  • 受保护指针
  • 动态类型转换

MOC

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

接下来,以一个简单的Q_OBJECT为例,我们来分析元对象编译器生成了什么

// mywidget.cpp
#include <QObject>

class MyWidget : public QObject
{
    Q_OBJECT
public:
    explicit MyWidget(QObject *parent = nullptr);

signals:
    void signal_test(int index);

public slots:
    void slot_test(int index) {};
};

我们查看Q_OBJECT的定义,它会被展开成以下内容:

public: 
    QT_WARNING_PUSH 
    Q_OBJECT_NO_OVERRIDE_WARNING 
    // 定义了一个静态的 QMetaObject 对象。这个对象包含了元对象的描述信息,用于支持信号和槽机制、反射等功能
    static const QMetaObject staticMetaObject; 
    // 返回当前的 QMetaObject
    virtual const QMetaObject *metaObject() const; 
    // 进行元对象类型的转换
    virtual void *qt_metacast(const char *); 
    // 通过该函数调用槽函数,实际是调用了 qt_static_metacall
    virtual int qt_metacall(QMetaObject::Call, int, void **); 
    QT_TR_FUNCTIONS 
private: 
    Q_OBJECT_NO_ATTRIBUTES_WARNING 
    // 根据函数索引调用槽函数
    Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); 
    QT_WARNING_POP 
    struct QPrivateSignal {}; 
    QT_ANNOTATE_CLASS(qt_qobject, "")

我们可以看到Q_OBJECT扩展后带来的一些变量和函数的定义,而这些定义都已经被写入到了moc_mywidget.cpp文件中,因此,在编译时mywidget.cpp会与moc_mywidget.cpp一起编译,否则这个类是不完整的。

在后续,我们会结合生成的moc_mywidget.cpp的内容,讨论由MOC生成的函数的作用