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、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 宏,不管你是不是使用信号槽。