zl程序教程

您现在的位置是:首页 >  其它

当前栏目

Q_OBJECT的作用

作用 object
2023-09-11 14:16:44 时间

只有继承了QObject类的类,才具有信号槽的能力。所以,为了使用信号槽,必须继承QObject。凡是QObject类(不管是直接子类还是间接子类),都应该在第一行代码写上Q_OBJECT。不管是不是使用信号槽,都应该添加这个宏。这个宏的展开将为我们的类提供信号槽机制、国际化机制以及 Qt 提供的不基于 C++ RTTI 的反射能力。因此,如果你觉得你的类不需要使用信号槽,就不添加这个宏,就是错误的。其它很多操作都会依赖于这个宏。

注意,由于 moc 只处理头文件中的标记了Q_OBJECT的类声明,不会处理 cpp 文件中的类似声明。因此,如果我们的类位于 main.cpp 中,是无法得到 moc 的处理的。解决方法是,我们手动调用 moc 工具处理 main.cpp,并且将 main.cpp 中的#include “xxx.h”改为#include “moc_xxx.h”就可以了。不过,这是相当繁琐的步骤,为了避免这样修改,我们还是将其放在头文件中。许多初学者会遇到莫名其妙的错误,一加上Q_OBJECT就出错,很大一部分是因为没有注意到这个宏应该放在头文件中。


只有加入了Q_OBJECT,你才能使用QT中的signal和slot机制。
比如编写事件接口等程序时,有时会出现如下问题:
在PC端成功编译,但在开发板端却显示错误:
    QObject::connect: No such slot QWidget::*******()                            
    QObject::connect:  (sender name:   'unnamed')                                  
    QObject::connect:  (receiver name: 'unnamed') 

这时候,就必须在头文件派生类的时候,首先像下面那样引入Q_OBJECT宏:
    class MyMainWindow : public QWidget
    {
        Q_OBJECT
    ......
    }

Qt QObject 宏

成员变量

【const QMetaObject QObject::staticMetaObject】这个变量存储类的元对象。

  • 元对象包含关于继承QObject的类的信息。
    • 例如类名、超类名、属性、信号和插槽。
  • 每个包含 Q_OBJECT 宏的类也将有一个元对象。
  • 元对象信息是信号/槽连接机制和属性系统所需要的。inherits()函数也使用了元对象。
  • 如果你有一个指向对象的指针,你可以使用 metaObject()来检索与该对象关联的元对象。
  • 例子:
 
 
  1. QPushButton::staticMetaObject.className(); // returns "QPushButton"

  2. QObject *obj = new QPushButton;

  3. obj->metaObject()->className(); // returns "QPushButton"

相关的非成员变量

 
 
  1. template <typename T> T qobject_cast(QObject *object)

  2. template <typename T> T qobject_cast(const QObject *object)

  • 如果对象是T类型(或其子类),则返回转换为T类型的给定对象;否则返回nullptr。
  • 如果object是nullptr,那么它也将返回nullptr。
  • 类T必须(直接或间接)继承QObject,并使用Q_OBJECT 宏声明。
  • 一个类被认为是继承了它自己。
 
 
  1. QObject *obj = new QTimer; // QTimer inherits QObject

  2. QTimer *timer = qobject_cast<QTimer *>(obj);

  3. // timer == (QObject *)obj

  4. QAbstractButton *button = qobject_cast<QAbstractButton *>(obj);

  5. // button == nullptr

  • object_cast()函数的行为类似于标准c++ dynamic_cast(),其优点是不需要RTTI支持,并且可以跨动态库边界工作。
  • qobject_cast()也可以与接口一起使用; 详情请参阅 Plug & Paint 示例。
  • 警告:如果T没有被 Q_OBJECT宏声明,这个函数的返回值是未定义的。
  • 参考:QObject::inherits().

【typedef QObjectList】

template <typename T> QList<T> qFindChildren(const QObject *obj, const QRegExp &regExp)
  • 这个函数重载qFindChildren()。
  • 个函数等价于obj->findChildren<T>(regExp)。
  • 注意:这个函数是为msvc6提供的一个解决方案,它不支持成员模板函数。建议在新代码中使用其他形式。
  • 参考:QObject::findChildren().

宏文档

【QT_NO_NARROWING_CONVERSIONS_IN_CONNECT】

  • 当信号和槽使用基于PMF的语法连接时,定义这个宏将禁用信号携带的参数和槽接受的参数之间的收缩和浮点到整数的转换。
  • 该函数是在Qt 5.8中引入的。
  • 参考:QObject::connect.

【Q_CLASSINFO(NameValue)】

  • 这个宏将额外的信息关联到类,这可以通过QObject::metaObject()得到。
  • Qt只在 Active QtQt D-Bus 和Qt QML中有限地使用该特性。
  • 额外的信息采用Name字符串和Value字面值字符串的形式。
  • 示例:
 
 
  1. class MyClass : public QObject

  2. {

  3. Q_OBJECT

  4. Q_CLASSINFO("Author", "Pierre Gendron")

  5. Q_CLASSINFO("URL", "http://www.my-organization.qc.ca")

  6. public:

  7. ...

  8. };

【Q_DISABLE_COPY(Class)】

  • 禁用给定类的复制构造函数和赋值操作符。
  • QObject的子类的实例不应该被认为是可以复制或赋值的值,而是作为唯一标识。
    • 这意味着当您创建自己的QObject子类(director或indirect)时,不应该给它一个复制构造函数或赋值操作符。
    • 但是,在类中简单地省略它们可能还不够,因为如果您错误地编写了一些需要复制构造函数或赋值操作符的代码(这很容易做到),编译器将精心地为您创建它。你必须做得更多。
  • 好奇的用户会发现,从QObject派生出来的Qt类通常会把这个宏包含在私有部分中:
 
 
  1. class MyClass : public QObject

  2. {

  3. private:

  4. Q_DISABLE_COPY(MyClass)

  5. };

  • 它在private区段中声明了复制构造函数和赋值操作符,因此如果您错误地使用它们,编译器将报告错误。
 
 
  1. class MyClass : public QObject

  2. {

  3. private:

  4. MyClass(const MyClass &) = delete;

  5. MyClass &operator=(const MyClass &) = delete;

  6. };

  • 但即便如此,也未必能完全抓住每一个病例。你可能会想这样做:
QWidget w = QWidget();
  • 首先,不要那样做。大多数编译器将生成使用复制构造函数的代码,因此将报告违反隐私的错误,但您的c++编译器不需要以特定的方式为该语句生成代码。
  • 它既不能使用复制构造函数,也不能使用私有的赋值操作符生成代码。在这种情况下,不会报告任何错误,但是当您调用w的成员函数时,应用程序可能会崩溃。
  • 参考:Q_DISABLE_COPY_MOVE 和Q_DISABLE_MOVE.

【Q_DISABLE_COPY_MOVE(Class)】

  • 它禁止对给定类使用复制构造函数、赋值操作符、移动构造函数和移动赋值操作符,结合了Q_DISABLE_COPY 和Q_DISABLE_MOVE
  • 该函数是在Qt 5.13中引入的。

【Q_DISABLE_MOVE(Class)】

【Q_EMIT】

  • 使用此宏替换发出信号的emit关键字,当你想使用Qt信号和插槽 通过 3rd party signal/slot mechanism
  • 该宏通常在.pro文件中使用CONFIG变量指定no_keywords时使用,但即使没有指定no_keywords时也可以使用。

【Q_ENUM(...)】

  • 这个宏在元对象系统中注册了enum类型。
  • 它必须放在具有Q_OBJECT或 Q_GADGET宏的类的枚举声明之后。
  • 对于名称空间,可以使用 Q_ENUM_NS()。
  • 示例:
 
 
  1. class MyClass : public QObject

  2. {

  3. Q_OBJECT

  4. public:

  5. MyClass(QObject *parent = nullptr);

  6. ~MyClass();

  7. enum Priority { High, Low, VeryHigh, VeryLow };

  8. Q_ENUM(Priority)

  9. void setPriority(Priority priority);

  10. Priority priority() const;

  11. };

  • 用Q_ENUM声明的枚举有 QMetaEnum在封闭的 QMetaObject 中注册。你也可以使用QMetaEnum::fromType()来获取QMetaEnum
  • 已注册枚举也自动注册到Qt元类型系统,使 QMetaType 知道它们,而不需要使用 Q_DECLARE_METATYPE()。
    • 这将启用有用的功能;  例如,如果在QVariant中使用,您可以将它们转换为字符串。同样,将它们传递给QDebug将打印出它们的名称。
  • 注意:在元对象系统中,枚举值是以signed int的形式存储的。
    • 用int的有效值范围之外的值注册枚举将导致溢出,并在通过元对象系统访问它们时可能出现未定义的行为。
    • 例如,QML通过元对象系统访问注册枚举。
  • 该函数是在Qt 5.5中引入的。
  • 参考: Qt's Property System.

【Q_ENUM_NS(...)】

  • 这个宏在元对象系统中注册了enum类型。
    • 它必须放在具有Q_NAMESPACE宏的命名空间的枚举声明之后。它与 Q_ENUM 相同,只是在命名空间中。
  • 用Q_ENUM_NS声明的枚举在封闭的 QMetaObject中注册了QMetaEnum 。你也可以使用QMetaEnum::fromType()来获取QMetaEnum
  • 已注册枚举也自动注册到Qt元类型系统,使QMetaType知道它们,而不需要使用 Q_DECLARE_METATYPE()。
    • 这将启用有用的功能;例如,如果在QVariant中使用,您可以将它们转换为字符串。同样,将它们传递给QDebug将打印出它们的名称。
  • 注意:在元对象系统中,枚举值是以signed int的形式存储的。
    • 用int的有效值范围之外的值注册枚举将导致溢出,并在通过元对象系统访问它们时可能出现未定义的行为。
    • 例如,QML通过元对象系统访问注册枚举。
  • 该函数是在Qt 5.8中引入的。
  • 参考: Qt's Property System.

【Q_FLAG(...)】

  • 这个宏向元对象系统注册一个单一的 flags type。它通常用于类定义中,声明给定枚举的值可以用作标志,并使用按位或运算符组合。对于名称空间,可以使用Q_FLAG_NS() 。
  • 宏必须放在enum声明之后。flags类型的声明是使用 Q_DECLARE_FLAGS() 宏完成的。
  • 例如,在QItemSelectionModel中, SelectionFlags标志以如下方式声明:
 
 
  1. class QItemSelectionModel : public QObject

  2. {

  3. Q_OBJECT

  4. public:

  5. ...

  6. enum SelectionFlag {

  7. NoUpdate = 0x0000,

  8. Clear = 0x0001,

  9. Select = 0x0002,

  10. Deselect = 0x0004,

  11. Toggle = 0x0008,

  12. Current = 0x0010,

  13. Rows = 0x0020,

  14. Columns = 0x0040,

  15. SelectCurrent = Select | Current,

  16. ToggleCurrent = Toggle | Current,

  17. ClearAndSelect = Clear | Select

  18. };

  19. Q_DECLARE_FLAGS(SelectionFlags, SelectionFlag)

  20. Q_FLAG(SelectionFlags)

  21. ...

  22. }

  • 注意: Q_FLAG宏负责在元对象系统中注册单个标志值,所以除了这个宏之外,没有必要使用 Q_ENUM()。
  • 该函数是在Qt 5.5中引入的。
  • 参考: Qt's Property System.

【Q_FLAG_NS(...)】

  • 这个宏向元对象系统注册一个单一的 flags type 。
    • 在具有 Q_NAMESPACE宏的名称空间中使用它来声明给定枚举的值可以用作标志,并使用按位或运算符组合。
    • 它与s Q_FLAG相同,但是在名称空间中。
  • 宏必须放在enum声明之后。
  • 注意 : Q_FLAG_NS宏负责在元对象系统中注册单个标志值,所以除了这个宏之外,没有必要使用Q_ENUM_NS()。
  • 参考:该函数是在Qt 5.8中引入的。
  • 参考: Qt's Property System.

【Q_GADGET】

  • Q_GADGET宏是Q_OBJECT宏的轻版本,适用于那些不继承自QObject但仍然希望使用QMetaObject.提供的一些反射功能的类。
    • 就像Q_OBJECT宏一样,它必须出现在类定义的私有部分中
  • Q_GADGET可以有 Q_ENUMQ_PROPERTY 和Q_INVOKABLE,,但是它们不能有信号或槽
  • Q_GADGET使类成员staticMetaObject可用。staticMetaObject的类型是QMetaObject ,它提供对使用 Q_ENUMS声明的枚举的访问。

【Q_INTERFACES(...)】

  • 这个宏告诉Qt类实现了哪些接口。这在实现插件时使用。
  • 示例:
 
 
  1. class BasicToolsPlugin : public QObject,

  2. public BrushInterface,

  3. public ShapeInterface,

  4. public FilterInterface

  5. {

  6. Q_OBJECT

  7. Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.PlugAndPaint.BrushInterface" FILE "basictools.json")

  8. Q_INTERFACES(BrushInterface ShapeInterface FilterInterface)

  9. public:

  10. ...

  11. };

【Q_INVOKABLE】

  • 将此宏应用于成员函数的声明,以允许它们通过元对象系统被调用。宏在返回类型之前写入,如下例所示:
 
 
  1. class Window : public QWidget

  2. {

  3. Q_OBJECT

  4. public:

  5. Window();

  6. void normalMethod();

  7. Q_INVOKABLE void invokableMethod();

  8. };

  • invokableMethod()函数使用Q_INVOKABLE标记,使得它被注册到元对象系统中,并允许它被 QMetaObject::invokeMethod()调用。
  • 因为normalMethod()函数没有以这种方式注册,所以不能使用 QMetaObject::invokeMethod()调用它。
  • 如果一个可调用的成员函数返回一个指向QObject或QObject子类的指针,并且它是从QML调用的,则应用特殊的所有权规则。

【Q_NAMESPACE】

 
 
  1. namespace test {

  2. Q_NAMESPACE

  3. ...

【Q_NAMESPACE_EXPORT(EXPORT_MACRO)】

  • Q_NAMESPACE_EXPORT宏可用于向名称空间添加QMetaObject功能。
  • 它的工作原理与Q_NAMESPACE宏完全一样。但是,在名称空间中定义的外部staticMetaObject变量是用提供的EXPORT_MACRO限定符声明的。
    • 如果需要从动态库导出对象,这很有用。
  • 例如:
 
 
  1. namespace test {

  2. Q_NAMESPACE_EXPORT(EXPORT_MACRO)

  3. ...

【Q_OBJECT】

  • Q_OBJECT宏必须出现在类定义的私有部分,该私有部分声明自己的信号和槽,或者使用Qt的元对象系统提供的其他服务。
  • 例如:
 
 
  1. #include <QObject>

  2. class Counter : public QObject

  3. {

  4. Q_OBJECT

  5. public:

  6. Counter() { m_value = 0; }

  7. int value() const { return m_value; }

  8. public slots:

  9. void setValue(int value);

  10. signals:

  11. void valueChanged(int newValue);

  12. private:

  13. int m_value;

  14. };

【Q_PROPERTY(...)】

  • 这个宏用于在继承QObject的类中声明属性。属性的行为类似于类数据成员,但它们具有通过元对象系统( Meta-Object System)访问的附加特性。
 
 
  1. Q_PROPERTY(type name

  2. (READ getFunction [WRITE setFunction] |

  3. MEMBER memberName [(READ getFunction | WRITE setFunction)])

  4. [RESET resetFunction]

  5. [NOTIFY notifySignal]

  6. [REVISION int]

  7. [DESIGNABLE bool]

  8. [SCRIPTABLE bool]

  9. [STORED bool]

  10. [USER bool]

  11. [CONSTANT]

  12. [FINAL]

  13. [REQUIRED])

  • 属性名、类型和READ函数是必需的
    • 该类型可以是QVariant支持的任何类型,也可以是用户定义的类型。
    • 其他项是可选的,但WRITE函数是常见的。属性默认为true, USER默认为false。
  • 示例:
Q_PROPERTY(QString title READ title WRITE setTitle USER true)
  • 或者更多关于如何使用这个宏的细节,以及更详细的使用示例,请参阅Qt的属性系统( Qt's Property System)的讨论。

【Q_REVISION】

  • 将此宏应用于成员函数的声明,以便在元对象系统中用修订号标记它们。宏在返回类型之前写入,如下例所示:
 
 
  1. lass Window : public QWidget

  2. {

  3. Q_OBJECT

  4. Q_PROPERTY(int normalProperty READ normalProperty)

  5. Q_PROPERTY(int newProperty READ newProperty REVISION 1)

  6. public:

  7. Window();

  8. int normalProperty();

  9. int newProperty();

  10. public slots:

  11. void normalMethod();

  12. Q_REVISION(1) void newMethod();

  13. };

  • 当使用元对象系统向另一个API动态公开对象时,这很有用,因为您可以匹配其他API的多个版本所期望的版本。考虑以下简化的示例:
 
 
  1. Window window;

  2. int expectedRevision = 0;

  3. const QMetaObject *windowMetaObject = window.metaObject();

  4. for (int i=0; i < windowMetaObject->methodCount(); i++)

  5. if (windowMetaObject->method(i).revision() <= expectedRevision)

  6. exposeMethod(windowMetaObject->method(i));

  7. for (int i=0; i < windowMetaObject->propertyCount(); i++)

  8. if (windowMetaObject->property(i).revision() <= expectedRevision)

  9. exposeProperty(windowMetaObject->property(i));

  • 使用与前面示例相同的Window类,newProperty和newMethod只会在预期版本为1或更大时在此代码中公开。
  • 因为如果没有标记,所有的方法都被认为是在修订0中,所以Q_REVISION(0)的标记是无效的并被忽略。
  • 元对象系统本身不使用这个标记。目前,这只被QtQml模块使用。
  • 有关更通用的字符串标记,请参见QMetaMethod::tag()。
  • 参考: QMetaMethod::revision().

【Q_SET_OBJECT_NAME(Object)】

  • 这个宏为Object指定objectName为“Object”。
  • Object是否是一个指针并不重要,宏会自己计算出来。
  • 该函数是在Qt 5.0中引入的。
  • 参考:QObject::objectName()。

【Q_SIGNAL】

  • 这是一个额外的宏,允许您将单个函数标记为信号。它可能非常有用,特别是当您使用第三方源代码解析器时,该解析器不理解信号或Q_SIGNALS组。
  • 使用此宏替换类声明中的signals关键字,如果您想使用Qt signals和Slots 通过  3rd party signal/slot mechanism
  • 该宏通常在.pro文件中使用CONFIG变量指定no_keywords时使用,但即使没有指定no_keywords时也可以使用。

【Q_SLOT】

  • 这是一个额外的宏,允许您将单个函数标记为插槽。它非常有用,特别是当您使用第三方源代码解析器时,该解析器不理解插槽或Q_SLOTS组。
  • 使用此宏替换类声明中的signals关键字,如果您想使用Qt signals和Slots 通过  3rd party signal/slot mechanism
  • 该宏通常在.pro文件中使用CONFIG变量指定no_keywords时使用,但即使没有指定no_keywords时也可以使用。

Qt 中 Q_OBJECT 宏及 moc_*.cpp文件

Qt 中 Q_OBJECT 宏及 moc_前缀文件
Q_OBJECT 宏
元对象系统 (Meta-Object System)
了解 Q_OBJECT 宏
展开 Q_OBJECT 宏
MOC 预编译器
MOC (Meta-Object Compiler)
前缀 moc_xx.cpp文件
代码示例如下
MakeFile文件
生成的moc文件如下
==moc_whatismoc.cpp== 文件分析
片段一
结构体`qt_meta_stringdata_WhatIsMoc_t`
片段二
宏`QT_MOC_LITERAL`和`qt_meta_stringdata_WhatIsMoc`
片段三
数组`qt_meta_data_WhatIsMoc[]`
片段四
结构体`WhatIsMoc::staticMetaObject`
片段五
公有接口`metaObject`、`qt_metacall`、`qt_metacast`
信号槽调用实现`qt_static_metacall`
信号定义
总结
注意
文章参考
Q_OBJECT 宏
元对象系统 (Meta-Object System)
Qt框架在C++基础上增加了元对象系统 (Meta-Object-System)
Qt中所有类全部继承自QObject类,这是使用元对象系统的前提
Qt中信号-槽机制是元对象系统提供的最重要内容,本质上是基于C++中函数、函数指针、回调函数等一系列语法构成,其实现与Q_OBJECT宏有关
了解 Q_OBJECT 宏
启用 Meta-Object-System 一系列功能,如信号-槽机制
Q_OBJECT宏会对Qt中的类进行标识,以便被Qt提供的moc预编译器识别
moc预编译器将头文件中的Q_OBJECT 宏展开,若类的声明及定义在.cpp文件内,则预编译器不会产生相对应的moc_xx.cpp文件
Q_OBJECT宏不应当随意去除,不仅信号-槽机制依赖于该宏,其他一些例如国际化机制、不基于 C++ RTTI 的反射等能力也依赖于该宏
展开 Q_OBJECT 宏
#define Q_OBJECT \
public: \
    QT_WARNING_PUSH \
    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 \
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, "")

由此不难看出,Qt源码中Q_OBJECT宏展开后,主要内容为 “元对象系统” 的一部分函数 (如信号-槽机制) 声明,但其实现并非在qobject.cpp文件中,而是在moc_qobject.cpp文件中
static const QMetaObject staticMetaObject;
保存该类的元对象系统信息,为类的所有对象共享
virtual const QMetaObject *metaObject() const;
这个函数通常情况下会返回类的静态元对象staticMetaObject的地址,如果在 QML 程序中,会返回非静态元对象
virtual int qt_metacall(QMetaObject::Call, int, void **);
这个函数负责槽函数的调用和属性系统的读写,内部会调用下方函数
qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
这个函数是槽函数调用机制的实现
QT_TR_FUNCTIONS
这个宏负责实现翻译国际化字符串,展开如下
#  define QT_TR_FUNCTIONS \
    static inline QString tr(const char *s, const char *c = nullptr, int n = -1) \
        { return staticMetaObject.tr(s, c, n); } \
    QT_DEPRECATED static inline QString trUtf8(const char *s, const char *c = nullptr, int n = -1) \
        { return staticMetaObject.tr(s, c, n); }

MOC 预编译器
MOC (Meta-Object Compiler)
moc 即元对象系统的预编译器
Qt 将源代码交给标准 C++ 编译器 (如 gcc) 之前,需要事先将这些扩展的语法去除掉。完成这一操作的就是 moc。预处理器执行之后,Q_OBJECT宏将不存在
moc 读取一个C++头文件,如果它找到包含Q_OBJECT宏的一个或多个类声明,则会生成一个包含这些类的元对象代码的C++源文件,并且以moc_作为前缀
信号和槽机制、运行时类型信息和动态属性系统都需要元对象代码。所以由moc生成的C++源文件必须编译并与类的实现联系起来。通常,moc无需手工调用,而是由构建系统自动调用的,因此它不需要程序员额外的工作。
前缀 moc_xx.cpp文件
moc 预编译后产生的对应类的元对象代码C++源文件
代码示例如下
whatismoc.h
#ifndef WHATISMOC_H
#define WHATISMOC_H

#include <QObject>
#include <QString>
#include <iostream>

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

signals:
    void sigTest1(const QString& text);
    void sigTest2(const QString& text, int num);
    void sigTest3(const QString& text); // 不连接任何槽;

public slots:
    void sltTest1(const QString& text);
    void sltTest2(const QString& text, int num);
    void sltTest3(const QString& text); // 不连接任何信号;
};

#endif // WHATISMOC_H


自定义WhatIsMoc类继承自QObject,并分别定义信号与槽:
信号:sigTest1、sigTest2、sigTest3
槽:sltTest1、sltTest2、sltTest3
其中:sigTest3不与任何槽连接、sltTest3不与任何信号连接

whatismoc.cpp
#include "whatismoc.h"

WhatIsMoc::WhatIsMoc(QObject *parent) : QObject(parent)
{
    connect(this, &WhatIsMoc::sigTest1, this, &WhatIsMoc::sltTest1);
    connect(this, &WhatIsMoc::sigTest2, this, &WhatIsMoc::sltTest2);
}

void WhatIsMoc::sltTest1(const QString &text)
{
    std::cout << "sltTest1";
}

void WhatIsMoc::sltTest2(const QString &text, int num)
{
    std::cout << "sltTest2";
}

void WhatIsMoc::sltTest3(const QString &text)
{
    std::cout << "sltTest3";
}

main.cpp
#include <QCoreApplication>
#include "whatismoc.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    WhatIsMoc moc;

    return a.exec();
}

MakeFile文件
打开Qt自动生成的MakeFile文件,看到如下一行:
SOURCES       = ..\WhatIsMoc\main.cpp \
        ..\WhatIsMoc\whatismoc.cpp debug\moc_whatismoc.cpp

由此可知,Qt自动生成了moc_whatismoc.cpp文件
生成的moc文件如下
moc_whatismoc.cpp
/****************************************************************************
** Meta object code from reading C++ file 'whatismoc.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.9.9)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/

#include "../../WhatIsMoc/whatismoc.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'whatismoc.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.9.9. 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_WhatIsMoc_t {
    QByteArrayData data[10];
    char stringdata0[74];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_WhatIsMoc_t, stringdata0) + ofs \
        - idx * sizeof(QByteArrayData)) \
    )
static const qt_meta_stringdata_WhatIsMoc_t qt_meta_stringdata_WhatIsMoc = {
    {
QT_MOC_LITERAL(0, 0, 9), // "WhatIsMoc"
QT_MOC_LITERAL(1, 10, 8), // "sigTest1"
QT_MOC_LITERAL(2, 19, 0), // ""
QT_MOC_LITERAL(3, 20, 4), // "text"
QT_MOC_LITERAL(4, 25, 8), // "sigTest2"
QT_MOC_LITERAL(5, 34, 3), // "num"
QT_MOC_LITERAL(6, 38, 8), // "sigTest3"
QT_MOC_LITERAL(7, 47, 8), // "sltTest1"
QT_MOC_LITERAL(8, 56, 8), // "sltTest2"
QT_MOC_LITERAL(9, 65, 8) // "sltTest3"

    },
    "WhatIsMoc\0sigTest1\0\0text\0sigTest2\0num\0"
    "sigTest3\0sltTest1\0sltTest2\0sltTest3"
};
#undef QT_MOC_LITERAL

static const uint qt_meta_data_WhatIsMoc[] = {

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

 // signals: name, argc, parameters, tag, flags
       1,    1,   44,    2, 0x06 /* Public */,
       4,    2,   47,    2, 0x06 /* Public */,
       6,    1,   52,    2, 0x06 /* Public */,

 // slots: name, argc, parameters, tag, flags
       7,    1,   55,    2, 0x0a /* Public */,
       8,    2,   58,    2, 0x0a /* Public */,
       9,    1,   63,    2, 0x0a /* Public */,

 // signals: parameters
    QMetaType::Void, QMetaType::QString,    3,
    QMetaType::Void, QMetaType::QString, QMetaType::Int,    3,    5,
    QMetaType::Void, QMetaType::QString,    3,

 // slots: parameters
    QMetaType::Void, QMetaType::QString,    3,
    QMetaType::Void, QMetaType::QString, QMetaType::Int,    3,    5,
    QMetaType::Void, QMetaType::QString,    3,

       0        // eod
};

void WhatIsMoc::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        WhatIsMoc *_t = static_cast<WhatIsMoc *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->sigTest1((*reinterpret_cast< const QString(*)>(_a[1]))); break;
        case 1: _t->sigTest2((*reinterpret_cast< const QString(*)>(_a[1])),(*reinterpret_cast< int(*)>(_a[2]))); break;
        case 2: _t->sigTest3((*reinterpret_cast< const QString(*)>(_a[1]))); break;
        case 3: _t->sltTest1((*reinterpret_cast< const QString(*)>(_a[1]))); break;
        case 4: _t->sltTest2((*reinterpret_cast< const QString(*)>(_a[1])),(*reinterpret_cast< int(*)>(_a[2]))); break;
        case 5: _t->sltTest3((*reinterpret_cast< const QString(*)>(_a[1]))); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast<int *>(_a[0]);
        {
            typedef void (WhatIsMoc::*_t)(const QString & );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&WhatIsMoc::sigTest1)) {
                *result = 0;
                return;
            }
        }
        {
            typedef void (WhatIsMoc::*_t)(const QString & , int );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&WhatIsMoc::sigTest2)) {
                *result = 1;
                return;
            }
        }
        {
            typedef void (WhatIsMoc::*_t)(const QString & );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&WhatIsMoc::sigTest3)) {
                *result = 2;
                return;
            }
        }
    }
}

const QMetaObject WhatIsMoc::staticMetaObject = {
    { &QObject::staticMetaObject, qt_meta_stringdata_WhatIsMoc.data,
      qt_meta_data_WhatIsMoc,  qt_static_metacall, nullptr, nullptr}
};


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

void *WhatIsMoc::qt_metacast(const char *_clname)
{
    if (!_clname) return nullptr;
    if (!strcmp(_clname, qt_meta_stringdata_WhatIsMoc.stringdata0))
        return static_cast<void*>(this);
    return QObject::qt_metacast(_clname);
}

int WhatIsMoc::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 < 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;
}

// SIGNAL 0
void WhatIsMoc::sigTest1(const QString & _t1)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

// SIGNAL 1
void WhatIsMoc::sigTest2(const QString & _t1, int _t2)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)), const_cast<void*>(reinterpret_cast<const void*>(&_t2)) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
}

// SIGNAL 2
void WhatIsMoc::sigTest3(const QString & _t1)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 2, _a);
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE

moc_whatismoc.cpp 文件分析
片段一
结构体qt_meta_stringdata_WhatIsMoc_t
moc 文件的开头为结构体
struct qt_meta_stringdata_WhatIsMoc_t {
    QByteArrayData data[10]; //信号函数3个、信号函数各个参数共4个、槽函数3个
    char stringdata0[74];
};

结构体内含两个数组 其中QByteArrayData在qarraydata.h中

QByteArrayData中的一部分定义
struct Q_CORE_EXPORT QArrayData
{
   QtPrivate::RefCount ref;
    int size;
    uint alloc : 31;
    uint capacityReserved : 1;
 
    qptrdiff offset; // in bytes from beginning of header
 
    void *data()
    {
        Q_ASSERT(size == 0
                || offset < 0 || size_t(offset) >= sizeof(QArrayData));
        return reinterpret_cast<char *>(this) + offset;
    }
 
    const void *data() const
    {
        Q_ASSERT(size == 0
                || offset < 0 || size_t(offset) >= sizeof(QArrayData));
        return reinterpret_cast<const char *>(this) + offset;
    }
    // 以下省略
};

size是指针指向数据的大小 offset是数据指针和当前对象指针的距离(偏移量)
如果数据大小不是0,data函数就根据偏移量offset和当前对象的地址返回当前数据的指针

片段二
宏QT_MOC_LITERAL和qt_meta_stringdata_WhatIsMoc
宏QT_MOC_LITERAL的实现
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_WhatIsMoc_t, stringdata0) + ofs \
        - idx * sizeof(QByteArrayData)) \
    )

分为两部分
宏:Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET
参数:(len, qptrdiff(offsetof(qt_meta_stringdata_moctest_t, stringdata0) + ofs - idx * sizeof(QByteArrayData)))

部分一
宏Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET展开如下

#define Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset) \
    Q_STATIC_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset)
    
#define Q_STATIC_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset) \
    { Q_REFCOUNT_INITIALIZE_STATIC, size, 0, 0, offset } \

{}里面的内容正好用来初始化QArrayData中的成员变量
size对应Q_REFCOUNT_INITIALIZE_STATIC,值是-1
alloc和capacityReserved值都是0
offset对应offset

部分二
参数(len, qptrdiff(offsetof(qt_meta_stringdata_moctest_t, stringdata0) + ofs - idx * sizeof(QByteArrayData)))

len对应size
qptrdiff就是一个数,数据类型为int, offsetof用于求结构体中一个成员在该结构体中的偏移量,在stddef.h头文件中, qptrdiff(offsetof(qt_meta_stringdata_moctest_t, stringdata0) + ofs - idx * sizeof(QByteArrayData))对应offset
算出来这两个数据的值,然后初始化QByteArrayData数组

结构体qt_meta_stringdata_WhatIsMoc内容

static const qt_meta_stringdata_WhatIsMoc_t qt_meta_stringdata_WhatIsMoc = {
    {
QT_MOC_LITERAL(0, 0, 9), // "WhatIsMoc"
QT_MOC_LITERAL(1, 10, 8), // "sigTest1"
QT_MOC_LITERAL(2, 19, 0), // ""
QT_MOC_LITERAL(3, 20, 4), // "text"
QT_MOC_LITERAL(4, 25, 8), // "sigTest2"
QT_MOC_LITERAL(5, 34, 3), // "num"
QT_MOC_LITERAL(6, 38, 8), // "sigTest3"
QT_MOC_LITERAL(7, 47, 8), // "sltTest1"
QT_MOC_LITERAL(8, 56, 8), // "sltTest2"
QT_MOC_LITERAL(9, 65, 8) // "sltTest3"

    },
    "WhatIsMoc\0sigTest1\0\0text\0sigTest2\0num\0"
    "sigTest3\0sltTest1\0sltTest2\0sltTest3"
};
#undef QT_MOC_LITERAL

这个结构体就是片段一中的结构体 该结构体的第一个数组就是QByteArrayData
所以宏QT_MOC_LITERAL返回的一定是QByteArrayData类型数据
QT_MOC_LITERAL宏后面的字符串对应片段一中结构体qt_meta_stringdata_WhatIsMoc_t中的第二个数组
字符串的内容分别是类名、信号、信号参数、槽

总结
这一部分会通过宏QT_MOC_LITERAL初始化片段一中结构体qt_meta_stringdata_WhatIsMoc_t

片段三
数组qt_meta_data_WhatIsMoc[]
数组qt_meta_data_WhatIsMoc[]内容
static const uint qt_meta_data_WhatIsMoc[] = {

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

 // signals: name, argc, parameters, tag, flags
       1,    1,   44,    2, 0x06 /* Public */,
       4,    2,   47,    2, 0x06 /* Public */,
       6,    1,   52,    2, 0x06 /* Public */,

 // slots: name, argc, parameters, tag, flags
       7,    1,   55,    2, 0x0a /* Public */,
       8,    2,   58,    2, 0x0a /* Public */,
       9,    1,   63,    2, 0x0a /* Public */,

 // signals: parameters
    QMetaType::Void, QMetaType::QString,    3,
    QMetaType::Void, QMetaType::QString, QMetaType::Int,    3,    5,
    QMetaType::Void, QMetaType::QString,    3,

 // slots: parameters
    QMetaType::Void, QMetaType::QString,    3,
    QMetaType::Void, QMetaType::QString, QMetaType::Int,    3,    5,
    QMetaType::Void, QMetaType::QString,    3,

       0        // eod
};

数组分解
部分一

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

revison:版本号,不用管
classname:类名在stringdata0中的索引,总是0,qt_meta_data_WhatIsMoc.data[0].data()
classinfo:不用管
methods:信号和槽总数量6,从qt_meta_data_WhatIsMoc.data[13]开始描述
properties:属性数量0,数组起始位置0
signalCount:信号数量3

这些数据存储在如下结构体QMetaObjectPrivate中

struct QMetaObjectPrivate
{
    // revision 7 is Qt 5.0 everything lower is not supported
    // revision 8 is Qt 5.12: It adds the enum name to QMetaEnum
    enum { OutputRevision = 8 }; // Used by moc, qmetaobjectbuilder and qdbus
 
    int revision;
    int className;
    int classInfoCount, classInfoData;
    int methodCount, methodData;
    int propertyCount, propertyData;
    int enumeratorCount, enumeratorData;
    int constructorCount, constructorData;
    int flags;
    int signalCount;
    //以下省略
};

部分二

 // signals: name, argc, parameters, tag, flags
       1,    1,   44,    2, 0x06 /* Public */,
       4,    2,   47,    2, 0x06 /* Public */,
       6,    1,   52,    2, 0x06 /* Public */,

 // slots: name, argc, parameters, tag, flags
       7,    1,   55,    2, 0x0a /* Public */,
       8,    2,   58,    2, 0x0a /* Public */,
       9,    1,   63,    2, 0x0a /* Public */,


第一列:表示信号槽的索引
第二列:表示信号槽函数的参数个数
第三列:表示参数数据的描述是从数组qt_meta_data_WhatIsMoc.data[n]开始描述的
第四列:表示tag信息
第五列:表示信号槽函数标志位

信号槽函数标志位在qmetaobject_p.h里声明,声明如下

enum MethodFlags  {
    AccessPrivate = 0x00,
    AccessProtected = 0x01,
    AccessPublic = 0x02,
    AccessMask = 0x03, //mask
 
    MethodMethod = 0x00,
    MethodSignal = 0x04,
    MethodSlot = 0x08,
    MethodConstructor = 0x0c,
    MethodTypeMask = 0x0c,
 
    MethodCompatibility = 0x10,
    MethodCloned = 0x20,
    MethodScriptable = 0x40,
    MethodRevisioned = 0x80
};

将上述枚举按位或,就能得到第五列数据
以第一行数据1, 1, 44, 2, 0x06 /* Public */为例,0x06 = 0x02 | 0x04,表示public signal

部分三

 // signals: parameters
    QMetaType::Void, QMetaType::QString,    3,
    QMetaType::Void, QMetaType::QString, QMetaType::Int,    3,    5,
    QMetaType::Void, QMetaType::QString,    3,

 // slots: parameters
    QMetaType::Void, QMetaType::QString,    3,
    QMetaType::Void, QMetaType::QString, QMetaType::Int,    3,    5,
    QMetaType::Void, QMetaType::QString,    3,

       0        // eod

以第一行数据QMetaType::Void, QMetaType::QString, 3,为例
QMetaType::Void:返回参数
QMetaType::QString:形参

片段四
结构体WhatIsMoc::staticMetaObject
结构体内容
const QMetaObject WhatIsMoc::staticMetaObject = {
    { &QObject::staticMetaObject, 
        qt_meta_stringdata_WhatIsMoc.data,
        qt_meta_data_WhatIsMoc,
        qt_static_metacall, 
        nullptr, 
        nullptr}
};


WhatIsMoc::staticMetaObject的类型是QMetaObject

QMetaObject的结构在qobjectdefs.h中声明

struct Q_CORE_EXPORT QMetaObject
{
    struct { // private data
        SuperData superdata;
        const QByteArrayData *stringdata;
        const uint *data;
        typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);
        StaticMetacallFunction static_metacall;
        const SuperData *relatedMetaObjects;
        void *extradata; //reserved for future use
    } d;
};

superdata:初始值为QMetaObject::SuperData::link<QObject::staticMetaObject>(),表示基类的元对象数据
stringdata:初始值为qt_meta_stringdata_WhatIsMoc.data,指向的是qt_meta_stringdata_WhatIsMoc_t中的data数组
data:初始值为qt_meta_data_WhatIsMoc,该指针指向数组qt_meta_data_WhatIsMoc
typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);声明了一个函数指针,指针的类型是void (*)(QObject *, QMetaObject::Call, int, void **);
static_metacall:初始值为qt_static_metacall
relatedMetaObjects:相关元对象指针
extradata:额外数据

片段五
公有接口metaObject、qt_metacall、qt_metacast
公有接口的定义
//获取元对象,可以调用this->metaObject()->className();获取类名称
const QMetaObject *WhatIsMoc::metaObject() const
{
    return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}


//这个函数负责将传递来到的类字符串描述转化为void*
void *WhatIsMoc::qt_metacast(const char *_clname)
{
    if (!_clname) return nullptr;
    if (!strcmp(_clname, qt_meta_stringdata_WhatIsMoc.stringdata0))
        return static_cast<void*>(this);
    return QObject::qt_metacast(_clname);
}


//调用方法
int WhatIsMoc::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 < 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_static_metacall
接口定义
void WhatIsMoc::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        WhatIsMoc *_t = static_cast<WhatIsMoc *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->sigTest1((*reinterpret_cast< const QString(*)>(_a[1]))); break;
        case 1: _t->sigTest2((*reinterpret_cast< const QString(*)>(_a[1])),(*reinterpret_cast< int(*)>(_a[2]))); break;
        case 2: _t->sigTest3((*reinterpret_cast< const QString(*)>(_a[1]))); break;
        case 3: _t->sltTest1((*reinterpret_cast< const QString(*)>(_a[1]))); break;
        case 4: _t->sltTest2((*reinterpret_cast< const QString(*)>(_a[1])),(*reinterpret_cast< int(*)>(_a[2]))); break;
        case 5: _t->sltTest3((*reinterpret_cast< const QString(*)>(_a[1]))); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast<int *>(_a[0]);
        {
            typedef void (WhatIsMoc::*_t)(const QString & );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&WhatIsMoc::sigTest1)) {
                *result = 0;
                return;
            }
        }
        {
            typedef void (WhatIsMoc::*_t)(const QString & , int );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&WhatIsMoc::sigTest2)) {
                *result = 1;
                return;
            }
        }
        {
            typedef void (WhatIsMoc::*_t)(const QString & );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&WhatIsMoc::sigTest3)) {
                *result = 2;
                return;
            }
        }
    }
}

执行对象所对应的信号或槽
对信号槽函数进行参数检查

信号定义
// SIGNAL 0
void WhatIsMoc::sigTest1(const QString & _t1)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

// SIGNAL 1
void WhatIsMoc::sigTest2(const QString & _t1, int _t2)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)), const_cast<void*>(reinterpret_cast<const void*>(&_t2)) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
}

// SIGNAL 2
void WhatIsMoc::sigTest3(const QString & _t1)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 2, _a);
}

信号之所以不用定义,是因为其定义有Qt框架完成,并与类的源文件一同编译
信号函数实现:

定义一个void*的指针数组,数组的第一个元素都是空,给元对系统内部注册元方法参数类型时使用,剩下的元素都是信号函数的参数的指针,然后将数组传入activate
总结
宏Q_OBJECT是Qt框架建立元对象系统的关键部分
moc文件是关于宏Q_OBJECT中函数声明的定义以及结构体的赋值
信号-槽机制就是建立“注册索引”的机制