它是Qt提供的一个工具,在构建过程中用于为使用Qt元对象系统的类生成额外的C++代码(moc_class.cpp)。元对象系统使得Qt应用程序能够使用信号和槽、内省和动态属性处理等特性。
Moc在交给gcc进行标准编译之前先调用moc分析源文件,如果发现Q_OBJECT在源文件前加上moc_生成moc_a.cpp将这个新文件加入编译系统,最终被连接到二进制代码中。
/* qmake ignore Q_OBJECT */
#define Q_OBJECT \
public: \
QT_WARNING_PUSH \ //在编译过程中临时禁用警告信息
Q_OBJECT_NO_OVERRIDE_WARNING \ //禁用对未重写(override)虚函数的警告
static const QMetaObject staticMetaObject; \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
QT_TR_FUNCTIONS \ //自动生成翻译字符串的相关函数tr()、trUtf8()、trId()等
private: \
Q_OBJECT_NO_ATTRIBUTES_WARNING \ //禁用对属性(attributes)的警告。
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, "")
定义一个类test:
#ifndef TEST_H
#define TEST_H
#include
class Test : public QObject
{
Q_OBJECT
public:
explicit Test(QObject *parent = nullptr);
public slots:
void slotTest(int test);
signals:
void signalTest(int test);
};
#endif // TEST_H
由上面的头文件生成的moc_class.cpp文件中包含:
struct qt_meta_stringdata_Test_t {
QByteArrayData data[5];
char stringdata0[31];
};
存储类名、属性名、函数名、信号名…
包括两个变量,各个名称的数组和所有名称的拼接
static const uint qt_meta_data_Test[] = {
// content:
8, // revision
0, // classname
0, 0, // classinfo
2, 14, // methods
0, 0, // 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,
0 // eod
};
元对象索引信息表,存储了Stringdata数据的数量和索引、信息
QT_INIT_METAOBJECT const QMetaObject Test::staticMetaObject = { {
QMetaObject::SuperData::link<QObject::staticMetaObject>(),
qt_meta_stringdata_Test.data,
qt_meta_data_Test,
qt_static_metacall,
nullptr,
nullptr
} };
1.对元对象信息进行操作,偏移量、数量、索引、通过索引获取对象
2.接口,调用成员函数、信号激活、方法检测(check_…)
3.存储元对象信息
const QMetaObject *Test::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}
void *Test::qt_metacast(const char *_clname)
{
if (!_clname) return nullptr;
if (!strcmp(_clname, qt_meta_stringdata_Test.stringdata0))
return static_cast<void*>(this);
return QObject::qt_metacast(_clname);
}
void Test::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
auto *_t = static_cast<Test *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->signalTest((*reinterpret_cast< int(*)>(_a[1]))); break;//索引0调用信号
case 1: _t->slotTest((*reinterpret_cast< int(*)>(_a[1]))); break;//索引1调用槽函数
default: ;
}
//单独对信号的调用
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
{
using _t = void (Test::*)(int );// _t 定义为指向 Test 类的成员函数指针
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&Test::signalTest)) {
*result = 0;
return;
}
}
}
}
函数会根据调用类型和索引来判断执行的操作类型,比如调用方法、设置属性值等。通常用于 Qt 的信号与槽机制的底层实现。
1.QObject _o:元对象调用的实例
2.QMetaObject::Call _c:调用类型,表示要执行的操作类型。可以是 QMetaObject::InvokeMetaMethod (调用方法)、 QMetaObject::IndexOfMethod()、 QMetaObject::ReadProperty (读取属性值)、 QMetaObject::WriteProperty (设置属性值)等
3.int _id :索引,表示要调用的方法或属性的索引。索引是在元对象中分配的唯一标识符,用于标识方法和属性。
4. void argv* :指向参数列表的指针。参数列表是一个 void* 数组,用于存储方法调用时的参数值。每个参数值都是一个 void* 类型的指针,需要根据方法的参数类型进行适当的转换。
int Test::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;
}
return _id;
}
// SIGNAL 0
void Test::signalTest(int _t1)
{
//第一个元素是返回值void,之后依次是参数
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);//用于在信号发射函数中触发槽函数
}
当在代码中发出一个信号时,实际上是通过调用这个信号函数来触发信号的发射。信号函数会根据信号的参数和连接的槽函数的参数进行匹配,并生成相应的代码来调用槽函数。