Qt原理-窥探信号槽的实现细节
转自https://zhuanlan.zhihu.com/p/80539605
猫和老鼠的故事
还是拿上次的设定来说明:Tom有个技能叫”喵”,就是发出猫叫,而正在偷吃东西的Jerry,听见猫叫声就会逃跑。
我们用信号-槽的方式写出来。
//Tom.h
#pragma once
#include <QObject> #include <QDebug> class Tom : public QObject { Q_OBJECT public: Tom(QObject *parent = nullptr) : QObject(parent) { } void miaow() { qDebug() << u8"喵!" ; emit miao(); } signals: void miao(); };
//Jerry.h
#pragma once
#include <QObject> #include <QDebug> class Jerry : public QObject { Q_OBJECT public: Jerry(QObject *parent = nullptr) : QObject(parent) { } public slots: void runAway() { qDebug() << u8"那只猫又来了,快溜!" ; } };
以上面的代码为例,要使用信号-槽功能,先决条件是继承QObject类,并在类声明中增加Q_OBJECT宏。
之后在”signals:” 字段之后声明一些函数,这些函数就是信号。
在”public slots:” 之后声明的函数,就是槽函数。
接下来看看我们的main函数:
//main.cpp
#include <QCoreApplication> #include "Tom.h" #include "Jerry.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Tom tom; Jerry jerry; QObject::connect(&tom, &Tom::miao, &jerry, &Jerry::runAway); tom.miaow(); return a.exec(); }
信号-槽都准备好了,接下来创建两个对象实例,并使用QObject::connect将信号和槽连接起来。
最后使用emit发送信号,就会自动触发槽函数了。
运行结果:
声明与实现
信号和槽的本质都是函数。
我们知道C++中的函数要有声明(declare),也要有实现(implement),
而信号只要声明,不需要写实现。这是因为moc会为我们自动生成。
另外触发信号时,不写emit关键字,直接调用信号函数,也是没有问题的。
这是因为emit是一个空的宏
#define emit
Q_OBJECT宏
我们来看一下Q_OBJECT宏,展开如下:
(不同的Qt版本有些差异,涛哥这里用的是5.12.4,以此为例)
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, "")
我们看到,关键的地方,是声明了一个只读的静态成员变量staticMetaObject,以及3个public的成员函数
static const QMetaObject staticMetaObject; virtual const QMetaObject *metaObject() const; virtual void *qt_metacast(const char *); virtual int qt_metacall(QMetaObject::Call, int, void **);
还有一个private的静态成员函数qt_static_metacall
static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **)
那么声明的这些成员变量/函数,在哪里实现?答案是moc生成的cpp文件。
信号的moc生成
如上图所示目录结构,项目编译完成后,在build文件夹中,自动生成了moc_Jerry.cpp 和 moc_Tom.cpp两个文件
其中moc_Tom.cpp内容如下:
/****************************************************************************
** Meta object code from reading C++ file 'Tom.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.12.4)
** ** WARNING! All changes made in this file will be lost! *****************************************************************************/ #include "../../TomJerry/Tom.h" #include <QtCore/qbytearray.h> #include <QtCore/qmetatype.h> #if !defined(Q_MOC_OUTPUT_REVISION) #error "The header file 'Tom.h' doesn't include <QObject>." #elif Q_MOC_OUTPUT_REVISION != 67 #error "This file was generated using the moc from 5.12.4. 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_Tom_t { QByteArrayData data[3]; char stringdata0[10]; }; #define QT_MOC_LITERAL(idx, ofs, len) \ Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ qptrdiff(offsetof(qt_meta_stringdata_Tom_t, stringdata0) + ofs \ - idx * sizeof(QByteArrayData)) \ ) static const qt_meta_stringdata_Tom_t qt_meta_stringdata_Tom = { { QT_MOC_LITERAL(0, 0, 3), // "Tom" QT_MOC_LITERAL(1, 4, 4), // "miao" QT_MOC_LITERAL(2, 9, 0) // "" }, "Tom\0miao\0" }; #undef QT_MOC_LITERAL static const uint qt_meta_data_Tom[] = { // content: 8, // revision 0, // classname 0, 0, // classinfo 1, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 1, // signalCount // signals: name, argc, parameters, tag, flags 1, 0, 19, 2, 0x06 /* Public */, // signals: parameters QMetaType::Void, 0 // eod }; void Tom::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) { if (_c == QMetaObject::InvokeMetaMethod) { auto *_t = static_cast<Tom *>(_o); Q_UNUSED(_t) switch (_id) { case 0: _t->miao(); break; default: ; } } else if (_c == QMetaObject::IndexOfMethod