2019建设什么网站好运营,个人博客网站设计模板,如何给网站做seo,wordpress 菜单添加图片引入
上篇讲到了信号与槽就是实现的观察者模式#xff0c;那具体如何生成映射表就是moc做的事情。
一、moc简介
1. moc的定义
moc 全称是 Meta-Object Compiler#xff0c;也就是“元对象编译器”#xff0c;它主要用于处理C源文件中的非标准C代码。Qt 程序在交由标准编…引入
上篇讲到了信号与槽就是实现的观察者模式那具体如何生成映射表就是moc做的事情。
一、moc简介
1. moc的定义
moc 全称是 Meta-Object Compiler也就是“元对象编译器”它主要用于处理C源文件中的非标准C代码。Qt 程序在交由标准编译器编译之前先要使用 moc 分析 C 源文件。如果它发现在一个头文件中包含了宏 Q_OBJECT则会生成另外一个 C 源文件。这个源文件中包含了 Q_OBJECT 宏的实现代码。这个新的文件名字将会是原文件名前面加上 moc_ 构成。这个新的文件同样将进入编译系统最终被链接到二进制代码中去。因此我们可以知道这个新的文件不是“替换”掉旧的文件而是与原文件一起参与编译。另外我们还可以看出一点moc 的执行是在预处理器之前。因为预处理器执行之后Q_OBJECT 宏就不存在了。 既然每个源文件都需要 moc 去处理那么我们在什么时候调用了它呢实际上如果你使用 qmake 的话这一步调用会在生成的 makefile 中展现出来。从本质上来说qmake 不过是一个 makefile 生成器因此最终执行还是通过 make 完成的。
2. moc的作用
Moc的主要功能如下
清理代码Moc会删除源文件中与C标准不符的代码例如某些未使用的变量、类型或函数。生成C类和函数Moc会将源文件中的预处理器指令和特殊C语句转换为C类和函数这些类和函数可以在其他Qt模块中使用。处理资源文件Moc可以将源文件中的资源文件如图像、字符串等转换为C类和函数使得这些资源可以在其他Qt模块中访问。提供扩展Moc可以处理模板代码从而使得代码可以根据特定的Qt模块或对象模型扩展。清理和清理Moc支持两次清理第一次清理通常用于移除不需要的C代码第二次清理用于移除第一次清理后仍然存在的未使用的代码。
二、moc详解
2.1 moc的工作原理
前面我们说过Qt 不是使用的“标准的” C 语言而是对其进行了一定程度的“扩展”。这里我们从Qt新增加的关键字就可以看出来signals、slots 或者 emit。所以有人会觉得 Qt 的程序编译速度慢这主要是因为在 Qt 将源代码交给标准 C 编译器如 gcc 之前需要事先将这些扩展的语法去除掉。完成这一操作的就是 moc。 Qt库中的mocMeta-Object Compiler是一个为特定Qt库生成元对象代码例如QObject、QMetaObject、QMetaObject::invokeMethod等的工具。moc的工作原理主要包括以下几个步骤
扩展原始代码moc首先读取源代码文件通常是C文件并根据该文件中的元对象代码生成相应的元对象代码。moc将元对象代码添加到源代码文件中以扩展原始代码。解析元对象moc接着解析源代码文件中的元对象代码。这些元对象代码包括 a. Q_OBJECT宏这是Qt库中最重要的元对象标识表示该类需要实现元对象系统如信号和槽机制。moc为所有定义了Q_OBJECT宏的类生成相应的元对象代码。 b. 信号和槽moc将读取和解析信号和槽的声明以生成相应的元对象代码。 c. 其他元对象属性moc解析并处理其他元对象属性例如元对象导出表、元对象属性表等。生成元对象代码moc根据解析到的元对象代码生成相应的元对象代码。这些元对象代码包括 QMetaObject生成的元对象代码包含了所有元对象的属性和方法。元对象导出表生成的元对象代码包含了类中所有元对象方法的符号这些符号用于其他库或代码通过Q_OBJECT宏识别和连接这些方法。元对象属性表生成的元对象代码包含了类中所有元对象属性的符号这些符号用于其他库或代码读取和设置这些属性。 输出元对象代码moc将生成的元对象代码写回原始代码文件。此时原始代码文件已经包含了额外的元对象代码用于实现元对象系统。
2.2 moc生成的文件结构
对于每一个 QObject 类的派生类qt 都会使用 moc 命令之生成附加的 moc_xxx.cpp 文件。在 moc_xxx.cpp 文件中包含了 QObject 派生类的附加信息。
我们创建一个qt对象生成出一个moc文件然后具体去分析这个文件各个函数的意义
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include QMainWindowclass MainWindow : public QMainWindow
{Q_OBJECT
public:MainWindow(QWidget *parent nullptr);~MainWindow();
};
#endif // MAINWINDOW_H/****************************************************************************
** Meta object code from reading C file mainwindow.h
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.12.10)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/#include mainwindow.h
#include QtCore/qbytearray.h
#include QtCore/qmetatype.h
#if !defined(Q_MOC_OUTPUT_REVISION)
#error The header file mainwindow.h doesnt include QObject.
#elif Q_MOC_OUTPUT_REVISION ! 67
#error This file was generated using the moc from 5.12.10. It
#error cannot be used with the include files from this version of Qt.
#error (The moc has changed too much.)
#endifQT_BEGIN_MOC_NAMESPACE
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
struct qt_meta_stringdata_MainWindow_t {QByteArrayData data[1];char stringdata0[11];
};
#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},MainWindow
};
#undef QT_MOC_LITERALstatic const uint qt_meta_data_MainWindow[] {// content:8, // revision0, // classname0, 0, // classinfo0, 0, // methods0, 0, // properties0, 0, // enums/sets0, 0, // constructors0, // flags0, // signalCount0 // eod
};//#因为这里没有信号和槽函数所以没有相应静态的元调用
void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{Q_UNUSED(_o);Q_UNUSED(_id);Q_UNUSED(_c);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_castvoid*(this);return QMainWindow::qt_metacast(_clname);
}int MainWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{_id QMainWindow::qt_metacall(_c, _id, _a);return _id;
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE在生成的 moc_sender.cpp 中 qt_meta_stringdata_Sender 是一个字面量表也是可称之为符号表。
它的存在是为了能够以 idx 就能获取对应的字符串。比如这里我要找到MainWindow这个字符 idx 0对应的字串在则为 (const char *)(qt_meta_stringdata_Sender.stringdata00) 此后可以用该对应的QByteArrayData的data()方法取得Hello字符串
2.3 Q_OBJECT展开记录
可以看到moc_test.cpp 里面为 Test 类增加了很多函数。然而我们并没有实际写出这些函数它是怎么加入类的呢他是通过Q_OBJECT宏定义出来的~ 他带来了 • QT_WARNING_PUSH宏 QT_WARNING_PUSH宏是用来将编译器的警告设置推入堆栈的宏。它会暂时关闭编译器的警告并将当前的警告设置保存在堆栈中。这样在QT_WARNING_POP宏被调用之后之前的警告设置会被恢复从而保证代码的其他部分不受到QT_WARNING_PUSH宏所设置的警告影响。 • Q_OBJECT_NO_OVERRIDE_WARNING宏 Q_OBJECT_NO_OVERRIDE_WARNING宏用于禁止编译器对未重写override父类中的虚函数发出的警告。在Qt的信号-槽机制中通常需要在自定义的类中重写父类的虚函数来实现信号和槽的连接。但是一些编译器可能会发出警告提示这些虚函数未被重写。使用Q_OBJECT_NO_OVERRIDE_WARNING宏可以禁止这些警告以避免在编译时产生大量的警告信息。 • 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 宏 QT_TR_FUNCTIONS宏是用来定义一组用于国际化i18n的翻译函数的宏。这些函数包括tr()、QObject::tr()、QT_TRANSLATE_NOOP()等用于在Qt应用程序中进行字符串的翻译和本地化。 这个宏的最关键的地方是是声明了一个只读的静态成员变量staticMetaObject以及3个public的成员函数通过moc工具就可以生成以下变量和函数的定义
三、实现信号与槽的实现为啥是moc
信号的触发
信号 就是普通的类成员函数信号只要声明(declare)不需要实现(implement)实现由moc(元对象编译器)自动生成。 信号的触发可以用emit也可以直接调用函数。
信号的实现是直接调用了QMetaObject::activate函数。其中0代表miao这个函数的索引号。 QMetaObject::activate函数的实现在Qt源码的QObject.cpp文件中略微复杂一些且不同版本的Qt实现差异都比较大 这里总结一下大致的实现 先找出与当前信号连接的所有对象-槽函数再逐个处理。 这里处理的方式分为三种
if((c-connectionType Qt::AutoConnection !receiverInSameThread)|| (c-connectionType Qt::QueuedConnection)) {// 队列处理
} else if (c-connectionType Qt::BlockingQueuedConnection) {// 阻塞处理// 如果同线程打印潜在死锁。
} else {//直接调用槽函数或回调函数
}receiverInSameThread表示当前线程id和接收信号的对象的所在线程id是否相等。
如果信号-槽连接方式为QueuedConnection不论是否在同一个线程按队列处理。如果信号-槽连接方式为Auto且不在同一个线程也按队列处理。如果信号-槽连接方式为阻塞队列BlockingQueuedConnection按阻塞处理。 (注意同一个线程就不要按阻塞队列调用了。因为同一个线程同时只能做一件事本身就是阻塞的直接调用就好了如果走阻塞队列则多了加锁的过程。如果槽中又发了同样的信号就会出现死锁加锁之后还未解锁又来申请加锁。)
队列处理就是把槽函数的调用转化成了QMetaCallEvent事件通过QCoreApplication::postEvent放进了事件循环。 等到下一次事件分发相应的线程才会去调用槽函数。
槽和moc生成
slot函数我们自己实现了moc不会做额外的处理所以自动生成的moc_Jerry.cpp文件中只有Q_OBJECT宏的展开 他的具体调用方式就是在qt_static_metacall函数中调用具体的函数不需要moc做额外处理
使用moc的注意事项
一个没有定义 Q_OBJECT 宏的类与它最接近的父类是同一类型的。也就是说如果 A 继承了 QObject 并且定义了 Q_OBJECTB 继承了 A 但没有定义 Q_OBJECTC 继承了 B则 C 的 QMetaObject::className() 函数将返回 A而不是本身的名字。因此为了避免这一问题所有继承了 QObject 的类都应该定义 Q_OBJECT 宏不管你是不是使用信号槽。