信号与槽(Signal
& Slot
)是 Qt 编程的基础,也是 Qt 的一大创新。因为有了信号与槽的编程机制,在 Qt 中处理界面各个组件的交互操作时变得更加直观和简单。
信号(Signal
)就是在特定情况下被发射的事件,例如 PushButton
最常见的信号就是鼠标单击时发射的 clicked()
信号,一个 ComboBox
最常见的信号是选择的列表项变化时发射的 CurrentIndexChanged()
信号。
槽(Slot
)就是对信号响应的函数。槽是一个函数,与一般的 C++ 函数一样,可以定义在类的任何部分(public
、private
或 protected
),可以具有任何参数,也可以被直接调用。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。
在 QtDesigner
中设置是非常容易的,右键点击你所需要的设置的 控件 -> 转到槽 -> 选择触发信号 -> 编写处理函数 即可。
那么Qt为我们做了什么?
用 QtDesigner
设计的 *.ui
文件可以通过 uic工具
转换为 ui_*.h文件
。
以 mainwindow.ui
为例,每个 *.ui
文件编译后均会生成相应的 ui_xxx.h
;该文件一般不会出现在工程目录中。
注意:ui_xxx.h
是根据UI设计器可视化设计自动生成的,由于再次编译会重新生成,对其进行手动修改并无意义。
ui_xxx.h
一般包含以下内容:
定义了一个类 ui_xxxx
,用于封装可视化设计的界面。
自动生成了界面各组件成员变量的定义。在 public
部分为界面的每个组件定义一个指针变量,类名就是我们在UI可视化设计界面中设置的 ObjectName
。
定义了 setupUi()
函数,相信对该函数会很眼熟,它在 mainwindow.cpp
中会被调用。控件将在此函数中被创建,然后我们就可以使用 ui->xxx
的形式使用它们。
setupUi()
还调用了 retranslateUi(Widget)
,用来设置界面各组件的文字内容属性,如标签的文字、按键的文字、窗体的标题等。将界面上的文字设置的内容独立出来作为一个函数 retranslateUi()
,在设计多语言界面时会用到这个函数。
setupUi()
还调用了一行看起来比较特别的代码:QMetaObject::connectSlotsByName(MainWindow);
递归搜索给定对象的所有子对象,并将匹配的信号从它们连接到遵循以下形式的对象插槽:
void on_<object name>_<signal name>(<signal parameters>);
借助 QMetaObject::connectSlotsByName()
的作用,按以下命名规则定义槽函数。至于连接就交给 connectSlotsByName
,当然这个方法要我们的 ui_xxx.cpp
中存在这个控件(这样偷懒是有代价的,有一点点的性能损耗)。
void on_<object name>_<signal name>(<signal parameters>);
对于自定义控件等,就不能这样偷懒了,需要我们自己定义槽函数并进行 connect
。
对于Qt自有的控件,若我们自己写 connect
需要注意的是:由于 QMetaObject::connectSlotsByName()
的作用,槽函数的名称需要注意,不要和默认名称相似或相同。轻则警告,重则自动帮我们进行连接,于是信号触发时会调用两次,一次是我们手动连接的,一次是 connectSlotsByName
连接的。
该方法最为常见,但是它编译时不做错误检查。
connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(pbutton_clicked()));
connect(ui->pushButton,&QPushButton::clicked,this,&MainWindow::pbutton_clicked);
较上一种方式,该写法省去了字符查找的过程因此会更快一些。连接建立后,从信号触发到槽函数的执行,两种写法是没有区别的。
该方法较为简洁,而且在编译过程中会对函数参数类型、个数做检查。美中不足的是,若涉及被重载的函数,将非常不优雅!
参考 Differences between String-Based and Functor-Based Connections 实现:
QLCDNumber::display(int)
QLCDNumber::display(double)
QLCDNumber::display(QString)
auto slider = new QSlider(this);
auto lcd = new QLCDNumber(this);
// String-based syntax
connect(slider, SIGNAL(valueChanged(int)),
lcd, SLOT(display(int)));
// Functor-based syntax, first alternative
connect(slider, &QSlider::valueChanged,
lcd, static_cast<void (QLCDNumber::*)(int)>(&QLCDNumber::display));
// Functor-based syntax, second alternative
void (QLCDNumber::*mySlot)(int) = &QLCDNumber::display;
connect(slider, &QSlider::valueChanged,
lcd, mySlot);
// Functor-based syntax, third alternative
connect(slider, &QSlider::valueChanged,
lcd, QOverload<int>::of(&QLCDNumber::display));
// Functor-based syntax, fourth alternative (requires C++14)
connect(slider, &QSlider::valueChanged,
lcd, qOverload<int>(&QLCDNumber::display));
注意:在一些需要 运行期反射 的情况下(头文件都没有,只知道有这么个对象,和函数的名字),只能用元方法式的写法。
连接信号到任意 Lambda
、std::bind
上,下面是 Lambda
表达式示例:
QByteArray page = ...;
QTcpSocket *socket = new QTcpSocket;
socket->connectToHost("qt-project.org", 80);
QObject::connect(socket, &QTcpSocket::connected, this, [=] () {
socket->write("GET " + page + "\r\n");
}, Qt::AutoConnection);
注意:
connect(sender, SIGNAL(mySignal(int, const QString &)),receiver, SLOT(mySlot(int)));
disconnect
断开连接。之前在看 Qt图表 Callout
例程中就发现 为避免重复connect()
,每次绑定前都先进行disconnect()
的写法。直接戳 Qt - 一文理解QThread多线程(万字剖析整理)- connect 部分
信号槽特殊的地方,是 moc(元对象编译器)
为其生成了一份 元信息 ,可以通过 QMetaObject::invokeMethod
的方式调用。平时我们不需要用到,但在”运行期反射”的情况下(头文件都没有,只知道有这么个对象,和函数的名字)就需要它了。
QMetaObject::invokeMethod(&mSwitch, "change");
注意:Qt元对象系统不是本文的重点,由于无法绕开它。这里仅引出一个问题,Qt中信号槽机制使用的前提是什么?
信号槽机制与元对象系统关系较为紧密。QObject
的子类本身已经附带了元信息,我们可以直接调用信号槽。
对于基础数据类型,Qt已经注册进元系统了,我们可以到QMetaType中查看,对于自定义的结构体,类等,则需要注册元类型。
Q_DECLARE_METATYPE(MyStruct)
Q_DECLARE_METATYPE(MyNamespace::MyStruct)
若采用队列链接的 connect
,则还需要使用 qRegisterMetaType()
注册类型:
With queued connections, the parameters must be of types that are known to Qt's meta-object system, because Qt needs to copy the arguments to store them in an event behind the scenes.
#include
Q_DECLARE_METATYPE(MyStruct);
qRegisterMetaType<MyStruct>("MyStruct");
connect()
成功连接将返回连接的句柄 QMetaObject::Connection
,若失败则返回无效的句柄。可以将句柄转换为布尔变量进行判断。
不知你是否有过困惑,为什么使用信号槽的机制必须是 QObject
的直接或间接子类,且必须加上宏 Q_OBJECT
?
其实,Qt程序的编译,在使用我们 Kits
中设置的编译器之前,还需要经过元对象编译器moc
。这个编译器已经被集成到 qmake
当中。
moc编译器解析一些C/C++不存在的关键字,处理 Q_OBJECT
等相关宏,并生成 moc_xxx.cpp
文件。
下面是我生成的 moc_mainwindow.cpp
:
/****************************************************************************
** Meta object code from reading C++ file 'mainwindow.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.9.3)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/
#include "../../test1/mainwindow.h"
#include
#include
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'mainwindow.h' doesn't include ."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.9.3. 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[5];
char stringdata0[56];
};
#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, 11), // "user_signal"
QT_MOC_LITERAL(2, 23, 0), // ""
QT_MOC_LITERAL(3, 24, 21), // "on_pushButton_clicked"
QT_MOC_LITERAL(4, 46, 9) // "user_slot"
},
"MainWindow\0user_signal\0\0on_pushButton_clicked\0"
"user_slot"
};
#undef QT_MOC_LITERAL
static const uint qt_meta_data_MainWindow[] = {
// content:
7, // revision
0, // classname
0, 0, // classinfo
3, 14, // methods
0, 0, // properties
0, 0, // enums/sets
0, 0, // constructors
0, // flags
1, // signalCount
// signals: name, argc, parameters, tag, flags
1, 0, 29, 2, 0x06 /* Public */,
// slots: name, argc, parameters, tag, flags
3, 0, 30, 2, 0x08 /* Private */,
4, 0, 31, 2, 0x08 /* Private */,
// signals: parameters
QMetaType::Void,
// slots: parameters
QMetaType::Void,
QMetaType::Void,
0 // eod
};
void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
MainWindow *_t = static_cast<MainWindow *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->user_signal(); break;
case 1: _t->on_pushButton_clicked(); break;
case 2: _t->user_slot(); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
{
typedef void (MainWindow::*_t)();
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::user_signal)) {
*result = 0;
return;
}
}
}
Q_UNUSED(_a);
}
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 < 3)
qt_static_metacall(this, _c, _id, _a);
_id -= 3;
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
if (_id < 3)
*reinterpret_cast<int*>(_a[0]) = -1;
_id -= 3;
}
return _id;
}
// SIGNAL 0
void MainWindow::user_signal()
{
QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE
/* qmake ignore 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, "")
QMetaObject
是元对象,元对象系统是Qt另外一套核心机制。在这里,我们仅需要知道这个类存储了父类的源对象、我们当前类描述、函数描述和 qt_static_metacall
函数地址即可。
下面我们逐个看宏展开增加的内容:
QMetaObject staticMetaObject
构造一个 QMetaObject
对象,传入当前moc文件的动态信息。
QMetaObject ※metaObject()
注意:※ = *
返回当前 QMetaObject
,一般而言,虚函数 metaObject()
仅返回类的 staticMetaObject
对象。
qt_metacall(QMetaObject::Call, int, void)
调用函数回调,该函数被异步处理信号时调用,或者Qt规定的有一定格式的槽函数(on_xxx_signalname()
)触发。在我的 moc_mainwindow.cpp
中查看,发现其内部还是调用了 qt_static_metacall
。
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 < 3)
qt_static_metacall(this, _c, _id, _a);
_id -= 3;
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
if (_id < 3)
*reinterpret_cast<int*>(_a[0]) = -1;
_id -= 3;
}
return _id;
}
qt_static_metacall(QObject ※, QMetaObject::Call, int, void )
注意:※ = *
根据函数索引进行槽函数的调用,有趣的是自行定义的信号也在其索引中,此为信号信号连接合法性佐证二。
void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
MainWindow *_t = static_cast<MainWindow *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->user_signal(); break;
case 1: _t->on_pushButton_clicked(); break;
case 2: _t->user_slot(); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
{
typedef void (MainWindow::*_t)();
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::user_signal)) {
*result = 0;
return;
}
}
}
Q_UNUSED(_a);
}
如果你跟踪过 signals
,会发现其是是一个函数声明:
# define signals Q_SIGNALS
# define Q_SIGNALS public QT_ANNOTATE_ACCESS_SPECIFIER(qt_signal)
#ifndef QT_ANNOTATE_ACCESS_SPECIFIER
# define QT_ANNOTATE_ACCESS_SPECIFIER(x)
#endif
那么为什么我们可以可以只声明不实现呢? 不过有moc为我们负重前行罢了~
在 moc_mainwindow.cpp
中发现自定义的信号的实现:
// SIGNAL 0
void MainWindow::user_signal()
{
QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}
要提供一种对象之间的通信机制。这种机制,要能够给两个不同对象中的函数建立映射关系,前者被调用时后者也能被自动调用。
再深入一些,两个对象都互相不知道对方的存在,仍然可以建立联系。甚至一对一的映射可以扩展到多对多,具体对象之间的映射可以扩展到抽象概念之间。
理解这些概念对我们查看Qt信号槽实现的源码大有裨益。
假设现在有一个 Dog
类和一个 Host
类,Host
中有一个 breakdown()
方法;Dog中有一个 WanWan()
方法。
显然我们是想实现当房屋遭到破坏时,看门狗发出WanWan警报。
在该场景中,若要采用直接调用的方式,则需要在Host类中创建一个Dog类,并在 breakdown()
中调用Dog的 WanWan()
方法。
class Host{
Dog * m_dog;
void breakdown(){
m_dog.WanWan();
}
}
这种方法存在以下问题:
我们将 WanWan()
函数存在某个地方,采用 Map
保存映射关系,创建一个 Connections
类进行管理。
class Connections{
void connect(const std::string &name, const std::function<void()> &callback){
m_callbackMap[name] = callback;
}
void run(){
auto cur = m_callbackMap.find(name);
//迭代器判断
if (cur != m_callbackMap.end()) {
//迭代器有效的情况,直接调用
cur->second();
}
}
private:
std::map<std::string, std::function<void()>> m_callbackMap;
}
//保存上下文连接
static Connections mConnections;
class Host{
void breakdown(){
cout << "The house was damaged." << endl;
mConnections.run();
}
}
class Dog(){
void Dog(){
mConnections.connect("Dog",std::bind(&Dog::WanWan, this));
}
void WanWan(){
cout << "WanWan..." << endl;
}
}
//Main
void main(){
Host mHost;
mHost.breakdown
}
这样当我们的Host调用 breakdown()
时,与之绑定的Dog将 WanWan...
问题1-2 也得以解决,但是由于监听的动作是在Dog构造函数中实现的,耦合性还是比较高。
思路与上一种方法类似,我们将Host和Dog抽象出来,形成目标类 Subject
和观察者类 Observer
,在目标类中存在一私有变量保存与之绑定的观察者。 这一模型又被称为 发布-订阅模型。可以很容易地实现目标对象的状态变化通知到所用已绑定的观察者。
//抽象类-观察者
class Observer{
virtual void update();
}
//具体观察者
class Dog :Observer{
void update(){
count << "WanWan..." << endl;
}
}
//目标类-主题
class Subject{
private:
vector<Observer> oVector = new Vector<>();
void add(Observer &server){
oVector.add(server);
}
bool remove(Observer &server){
return oVector.delete(server);
}
//通知所有观察者
void notityObserver(){
for(Observer observer : oVector) {
observer.update();
}
}
//目标对象
class Host:Subject{
void breakdown(){
cout << "The house was damaged." << endl;
notityObserver();
}
}
int main(int argc, char const *argv[])
{
Host host = new Host;
Dog dog1 = new Dog;
Dog dog2 = new Dog;
host.add(dog1);
host.add(dog2);
host.breakdown();
return 0;
}
void QMetaObject::connectSlotsByName(QObject *o)
{
//...
// 遍历对象中的每个槽方法
for (int i = 0; i < mo->methodCount(); ++i) {
//...
//检查列表中的每个对象
for(int j = 0; j < list.count(); ++j) {
// "on__"规则匹配
//...
//匹配到了相应的槽
if (Connection(QMetaObjectPrivate::connect(co, sigIndex, smeta, o, i))) {
foundIt = true;
break;
}
//...
}
}
}
可以发现它最后调用了 QMetaObjectPrivate::connect()
。
由于需要遍历匹配,相比直接 connect()
于程序整体而言都损失了一部分效率(偷懒的代价)。
跟踪 connect(this,SIGNAL(user_signal()),this,SLOT(user_slot()));
中的 connect()
:
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *method,
Qt::ConnectionType type)
{
//...
QByteArray tmp_signal_name;
// 【1】检查是否为信号
if (!check_signal_macro(sender, signal, "connect", "bind"))
return QMetaObject::Connection(0);
const QMetaObject *smeta = sender->metaObject();
const char *signal_arg = signal;
++signal; //skip code 将前面的标志去掉
QArgumentTypeArray signalTypes;
Q_ASSERT(QMetaObjectPrivate::get(smeta)->revision >= 7);
//获取信号名
QByteArray signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
//获取函数index
int signal_index = QMetaObjectPrivate::indexOfSignalRelative(
&smeta, signalName, signalTypes.size(), signalTypes.constData());
//...
QByteArray tmp_method_name;
int membcode = extract_code(method);
if (!check_method_code(membcode, receiver, method, "connect"))
return QMetaObject::Connection(0);
const char *method_arg = method;
++method; // skip code
QArgumentTypeArray methodTypes;
QByteArray methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);
const QMetaObject *rmeta = receiver->metaObject();
int method_index_relative = -1;
Q_ASSERT(QMetaObjectPrivate::get(rmeta)->revision >= 7);
//槽函数运行是信号||槽(信号可以作为槽函数的证据之一)
switch (membcode) {
case QSLOT_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
case QSIGNAL_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
}
//...
//【2】
QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types));
return handle;
}
【1】check_signal_macro()
检测是否为信号的实现:
static bool check_signal_macro(const QObject *sender, const char *signal,
const char *func, const char *op)
{
int sigcode = extract_code(signal);
if (sigcode != QSIGNAL_CODE) {
if (sigcode == QSLOT_CODE)
qWarning("QObject::%s: Attempt to %s non-signal %s::%s",
func, op, sender->metaObject()->className(), signal+1);
else
qWarning("QObject::%s: Use the SIGNAL macro to %s %s::%s",
func, op, sender->metaObject()->className(), signal);
return false;
}
return true;
}
static int extract_code(const char *member)
{
// extract code, ensure QMETHOD_CODE <= code <= QSIGNAL_CODE
return (((int)(*member) - '0') & 0x3);
}
#define QSLOT_CODE 1
#define QSIGNAL_CODE 2
也就是说Qt是通过 char *signal
中的字符判断来确实它是不是信号的,1xxx
表示槽,2xxx
表示信号。
【2】是我们的重点,了解下面的代码需要先了解 观察者模式
。
QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender,
int signal_index, const QMetaObject *smeta,
const QObject *receiver, int method_index,
const QMetaObject *rmeta, int type, int *types)
{
QObject *s = const_cast<QObject *>(sender);
QObject *r = const_cast<QObject *>(receiver);
int method_offset = rmeta ? rmeta->methodOffset() : 0;
Q_ASSERT(!rmeta || QMetaObjectPrivate::get(rmeta)->revision >= 6);
/*
moc文件中的qt_static_metacall函数的地址被传递给了staticMetaObject对象的static_metacall域。
这里又将static_metacall域的内容传递给callFunction。所以callFunction指向了moc文件中的qt_static_metacall函数。
*/
QObjectPrivate::StaticMetaCallFunction callFunction = rmeta ? rmeta->d.static_metacall : nullptr;
//...
//connection对象,监听者模式
std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection};
c->sender = s;
c->signal_index = signal_index;
c->receiver.storeRelaxed(r);
QThreadData *td = r->d_func()->threadData;
td->ref();
c->receiverThreadData.storeRelaxed(td);
c->method_relative = method_index;
c->method_offset = method_offset;
c->connectionType = type;
c->isSlotObject = false;
c->argumentTypes.storeRelaxed(types);
c->callFunction = callFunction;
//【3】添加监听
QObjectPrivate::get(s)->addConnection(signal_index, c.get());
locker.unlock();
QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
if (smethod.isValid())
s->connectNotify(smethod);
return c.release();
}
void QObjectPrivate::addConnection(int signal, Connection *c)
{
Q_ASSERT(c->sender == q_ptr);
ensureConnectionData();
ConnectionData *cd = connections.loadRelaxed();
cd->resizeSignalVector(signal + 1);
//取得链接列表list
ConnectionList &connectionList = cd->connectionsForSignal(signal);
if (connectionList.last.loadRelaxed()) {
Q_ASSERT(connectionList.last.loadRelaxed()->receiver.loadRelaxed());
connectionList.last.loadRelaxed()->nextConnectionList.storeRelaxed(c);
} else {
connectionList.first.storeRelaxed(c);
}
c->id = ++cd->currentConnectionId;
c->prevConnectionList = connectionList.last.loadRelaxed();
connectionList.last.storeRelaxed(c);
QObjectPrivate *rd = QObjectPrivate::get(c->receiver.loadRelaxed());
rd->ensureConnectionData();
//【4】注意:senders是 Connection *
// 这里操作元对象中的Connections
c->prev = &(rd->connections.loadRelaxed()->senders);
c->next = *c->prev;
*c->prev = c;
if (c->next)
c->next->prev = &c->next;
}
【4】Connection
类
struct Connection
{
//发送者
QObject *sender;
//接收者
QObject *receiver;
union {
//接收者static_meatcall函数地址
StaticMetaCallFunction callFunction;
QtPrivate::QSlotObjectBase *slotObj;
};
// The next pointer for the singly-linked ConnectionList
Connection *nextConnectionList;
//senders linked list
Connection *next;
Connection **prev;
QAtomicPointer<const int> argumentTypes;
QAtomicInt ref_;
ushort method_offset;
ushort method_relative;
uint signal_index : 27; // In signal range (see QObjectPrivate::signalIndex())
ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking
ushort isSlotObject : 1;
ushort ownArgumentTypes : 1;
Connection() : nextConnectionList(nullptr), ref_(2), ownArgumentTypes(true) {
//ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection
}
~Connection();
int method() const { Q_ASSERT(!isSlotObject); return method_offset + method_relative; }
void ref() { ref_.ref(); }
void deref() {
if (!ref_.deref()) {
Q_ASSERT(!receiver);
delete this;
}
}
};
// ConnectionList is a singly-linked list
struct ConnectionList {
ConnectionList() : first(nullptr), last(nullptr) {}
Connection *first;
Connection *last;
};
发射信号时,我们常使用 emit
,但它是一个空的宏:
# define emit
也就是说,我们 emit signal()
和 直接调用 signal()
完全没有区别。
那么我们 emit user_signal()
就变成了直接调用 user_signal()
,user_signal()
只调用了QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
,跟踪下源码:
void QMetaObject::activate(QObject *sender, int signal_index, void **argv)
{
const QMetaObject *mo = sender->metaObject();
//若信号非子类实现,则可能是其祖先类信号,遍历找到血缘关系最近且具有该信号的类
while (mo->methodOffset() > signal_index)
mo = mo->superClass();
activate(sender, mo, signal_index - mo->methodOffset(), argv);
}
void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv)
{
int signal_index = signalOffset + local_signal_index;
if (Q_UNLIKELY(qt_signal_spy_callback_set.loadRelaxed()))
doActivate<true>(sender, signal_index, argv);
else
doActivate<false>(sender, signal_index, argv);
}
template <bool callbacks_enabled>
void doActivate(QObject *sender, int signal_index, void **argv)
{
//...
//链接记录列表
const QObjectPrivate::ConnectionList *list;
if (signal_index < signalVector->count())
list = &signalVector->at(signal_index);
else
list = &signalVector->at(-1);
//获取当前线程id
Qt::HANDLE currentThreadId = QThread::currentThreadId();
//判断是否为发射者线程id
bool inSenderThread = currentThreadId == QObjectPrivate::get(sender)->threadData->threadId.loadRelaxed();
// We need to check against the highest connection id to ensure that signals added
// during the signal emission are not emitted in this emission.
uint highestConnectionId = connections->currentConnectionId.loadRelaxed();
//遍历连接
do {
QObjectPrivate::Connection *c = list->first.loadRelaxed();
if (!c)
continue;
//遍历槽
do {
QObject * const receiver = c->receiver.loadRelaxed();
if (!receiver)
continue;
QThreadData *td = c->receiverThreadData.loadRelaxed();
if (!td)
continue;
//判断是否处于同一线程
bool receiverInSameThread;
if (inSenderThread) {
receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();
} else {
// need to lock before reading the threadId, because moveToThread() could interfere
QMutexLocker lock(signalSlotLock(receiver));
receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();
}
// determine if this connection should be sent immediately or
// put into the event queue
// 队列连接或自动连接且不在同一线程中则放入发射队列
if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
|| (c->connectionType == Qt::QueuedConnection)) {
//【1】
queued_activate(sender, signal_index, c, argv);
continue;
#if QT_CONFIG(thread)
//阻塞连接
} else if (c->connectionType == Qt::BlockingQueuedConnection) {
if (receiverInSameThread) {
//该链接方式在同一线程中可能存在死锁,这里警告
qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: "
"Sender is %s(%p), receiver is %s(%p)",
sender->metaObject()->className(), sender,
receiver->metaObject()->className(), receiver);
}
QSemaphore semaphore;
{
QBasicMutexLocker locker(signalSlotLock(sender));
if (!c->receiver.loadAcquire())
continue;
QMetaCallEvent *ev = c->isSlotObject ?
new QMetaCallEvent(c->slotObj, sender, signal_index, 0, 0, argv, &semaphore) :
new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal_index, 0, 0, argv, &semaphore);
// 发送事件-槽函数由事件系统调用
//【2】
QCoreApplication::postEvent(receiver, ev);
}
semaphore.acquire();
continue;
#endif
}
//直接调用槽函数或回调函数
//【3】
QObjectPrivate::Sender senderData(receiverInSameThread ? receiver : nullptr, sender, signal_index);
if (c->isSlotObject) {
c->slotObj->ref();
struct Deleter {
void operator()(QtPrivate::QSlotObjectBase *slot) const {
if (slot) slot->destroyIfLastRef();
}
};
const std::unique_ptr<QtPrivate::QSlotObjectBase, Deleter> obj{c->slotObj};
{
Q_TRACE_SCOPE(QMetaObject_activate_slot_functor, obj.get());
obj->call(receiver, argv);
}
} else if (c->callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {
//we compare the vtable to make sure we are not in the destructor of the object.
const int method_relative = c->method_relative;
const auto callFunction = c->callFunction;
const int methodIndex = (Q_HAS_TRACEPOINTS || callbacks_enabled) ? c->method() : 0;
if (callbacks_enabled && signal_spy_set->slot_begin_callback != nullptr)
signal_spy_set->slot_begin_callback(receiver, methodIndex, argv);
{
Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, methodIndex);
//调用回调函数
callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv);
}
if (callbacks_enabled && signal_spy_set->slot_end_callback != nullptr)
signal_spy_set->slot_end_callback(receiver, methodIndex);
} else {
const int method = c->method_relative + c->method_offset;
if (callbacks_enabled && signal_spy_set->slot_begin_callback != nullptr) {
signal_spy_set->slot_begin_callback(receiver, method, argv);
}
{
Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, method);
//直接调用(元对象系统实现反射)
QMetaObject::metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv);
}
if (callbacks_enabled && signal_spy_set->slot_end_callback != nullptr)
signal_spy_set->slot_end_callback(receiver, method);
}
} while ((c = c->nextConnectionList.loadRelaxed()) != nullptr && c->id <= highestConnectionId);
} while (list != &signalVector->at(-1) &&
//start over for all signals;
((list = &signalVector->at(-1)), true));
if (connections->currentConnectionId.loadRelaxed() == 0)
senderDeleted = true;
}
if (!senderDeleted)
sp->connections.loadRelaxed()->cleanOrphanedConnections(sender);
if (callbacks_enabled && signal_spy_set->signal_end_callback != nullptr)
signal_spy_set->signal_end_callback(sender, signal_index);
}
加了注释以下若还是觉得不够清晰,可以查看以下缩略版 doActivate()
:
template <bool callbacks_enabled>
void doActivate(QObject *sender, int signal_index, void **argv)
{
//...
//获取当前线程id
Qt::HANDLE currentThreadId = QThread::currentThreadId();
//判断是否为发射者线程id
bool inSenderThread = currentThreadId == QObjectPrivate::get(sender)->threadData->threadId.loadRelaxed();
// We need to check against the highest connection id to ensure that signals added
// during the signal emission are not emitted in this emission.
uint highestConnectionId = connections->currentConnectionId.loadRelaxed();
//遍历连接
do {
//...
//遍历槽
do {
//...
//判断是否处于同一线程
bool receiverInSameThread;
if (inSenderThread) {
receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();
} else {
// need to lock before reading the threadId, because moveToThread() could interfere
QMutexLocker lock(signalSlotLock(receiver));
receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();
}
// 队列连接或自动连接且不在同一线程中则放入发射队列
if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
|| (c->connectionType == Qt::QueuedConnection)) {
//...
#if QT_CONFIG(thread)
// 阻塞连接
} else if (c->connectionType == Qt::BlockingQueuedConnection) {
// ...
#endif
}
//其他:直接调用槽函数或回调函数
} while ((c = c->nextConnectionList.loadRelaxed()) != nullptr && c->id <= highestConnectionId);
} while (list != &signalVector->at(-1) &&
//start over for all signals;
((list = &signalVector->at(-1)), true));
}
可以发现:
同一个线程内的信号-槽,就相当于函数调用,和前面的观察者模式相似。
跨线程的信号-槽,在信号触发时,发送者线程将槽函数的调用转化成了一次“调用事件”,放入事件循环中。接收者线程执行到下一次事件处理时,处理“调用事件”,调用相应的函数。
emit user_signal()
经历了什么:
首先 emit
将被脱去,直接调用 user_signal()
,user_signal()
的实现是 moc
生成的。
它只调用了QMetaObject::activate(QObject *sender, int signal_index, void **argv)
,在这里如果若信号非子类实现,则可能是其祖先类信号,遍历找到血缘关系最近且具有该信号的类,再调用 QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv)
。
最后殊途同归,调用 doActivate(QObject *sender, int signal_index, void **argv)
,该函数将完成槽函数的调用。
Qt实用技能4-认清信号槽的本质
Qt原理-窥探信号槽的实现细节
QT 的信号与槽机制介绍
Qt connect信号连接的几种写法
菜鸟教程-观察者模式(笔记区更精彩)
简说设计模式——观察者模式
观察者模式(Observer模式)详解
Qt信号槽-原理分析
Qt5学习应用之路–信号与槽(signal & slot)
深入理解QT的SIGNAL\SLOT机制(二):QMetaObject相关知识
深入理解QT的SIGNAL\SLOT机制(三):QObject::connect函数
深入理解QT的SIGNAL\SLOT机制(四):Connection结构
深入理解QT的SIGNAL\SLOT机制(五):信号的发射过程