基于Qt4.8.6版本

一、信号槽机制的原理

1、信号槽简介

信号槽是观察者模式的一种实现,特性如下:

A、一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知;

B、一个槽就是一个观察者,通常就是在被观察的对象发生改变的时候——也可以说是信号发出的时候——被调用的函数;

C、信号与槽的连接,形成一种观察者-被观察者的关系;

D、当事件或者状态发生改变的时候,信号就会被发出;同时,信号发出者有义务调用所有注册的对这个事件(信号)感兴趣的函数(槽)。

信号和槽是多对多的关系。一个信号可以连接多个槽,而一个槽也可以监听多个信号。

信号槽与语言无关,有多种方法可以实现信号槽,不同的实现机制会导致信号槽的差别很大。信号槽术语最初来自 Trolltech 公司的 Qt 库,由于其设计理念的先进性,立刻引起计算机科学界的注意,提出了多种不同的实现。目前,信号槽依然是 Qt 库的核心之一,其他许多库也提供了类似的实现,甚至出现了一些专门提供这一机制的工具库。

信号槽是Qt对象以及其派生类对象之间的一种高效通信接口,是Qt的核心特性,也是Qt区别与其他工具包的重要地方。信号槽完全独立于标准的C/C++语言,因此要正确的处理好信号和槽,必须借助于一个成为MOC(Meta Object Compiler)的Qt工具,MOC工具是一个C++预处理程序,能为高层次的事件处理自动生成所需要的附加代码。

2、不同平台的实现

MFC中的消息机制没有采用C++中的虚函数机制,原因是消息太多,虚函数开销太大。在Qt中也没有采用C++中的虚函数机制,而是采用了信号槽机制,原因与此相同。更深层次的原因上,多态的底层实现机制只有两种,一种是按照名称查表,一种是按照位置查表。两种方式各有利弊,而C++的虚函数机制无条件的采用了后者,导致的问题就是在子类很少重载基类实现的时候开销太大,再加上界面编程中子类众多的情况,基本上C++的虚函数机制效率太低,于是各家库的编写者就只好自谋生路,当然,这其实是C++语言本身的缺陷。

二、Qt信号槽实例解析

1、信号槽使用示例

使用简单的实例:

#ifndef OBJECT_H
#define OBJECT_H#include <QObject>
#include <QString>
#include <QDebug>class Object : public QObject
{Q_OBJECTQ_PROPERTY(int age READ age  WRITE setAge NOTIFY ageChanged)Q_PROPERTY(int score READ score  WRITE setScore NOTIFY scoreChanged)Q_PROPERTY(Level level READ level WRITE setLevel)Q_CLASSINFO("Author", "Scorpio")Q_CLASSINFO("Version", "1.0")
public:enum Level{Basic = 1,Middle,Advanced,Master};Q_ENUMS(Level)
protected:QString m_name;Level m_level;int m_age;int m_score;void setLevel(const int& score){if(score <= 60){m_level = Basic;}else if(score < 100){m_level = Middle;}else if(score < 150){m_level = Advanced;}else{m_level = Master;}}
public:explicit Object(QString name, QObject *parent = 0):QObject(parent){m_name = name;setObjectName(m_name);connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));}int age()const{return m_age;}void setAge(const int& age){m_age = age;emit ageChanged(m_age);}int score()const{return m_score;}void setScore(const int& score){m_score = score;setLevel(m_score);emit scoreChanged(m_score);}Level level()const{return m_level;}void setLevel(const Level& level){m_level = level;}
signals:void ageChanged(int age);void scoreChanged(int score);
public slots:void onAgeChanged(int age){qDebug() << "age changed:" << age;}void onScoreChanged(int score){qDebug() << "score changed:" << score;}
};#endif // OBJECT_H

Main函数:

#include <QCoreApplication>
#include "Object.h"int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);Object ob("object");//设置属性ageob.setProperty("age", QVariant(30));qDebug() << "age: " << ob.age();qDebug() << "property age: " << ob.property("age").toInt();//设置属性scoreob.setProperty("score", QVariant(90));qDebug() << "score: " << ob.score();qDebug() << "property score: " << ob.property("score").toInt();qDebug() << "Level: " << ob.level();ob.setProperty("level", 4);qDebug() << "level: " << ob.level();qDebug() << "Property level: " << ob.property("level").toInt();//内省intropection,运行时查询对象信息qDebug() << "object name: " << ob.objectName();qDebug() << "class name: " << ob.metaObject()->className();qDebug() << "isWidgetType: " << ob.isWidgetType();qDebug() << "inherit: " << ob.inherits("QObject");return a.exec();
}

2、SIGNAL与SLOT宏

SIGNAL与SLOT宏定义在/src/corelib/kernel/Qobjectdefs.h文件中。

Q_CORE_EXPORT const char *qFlagLocation(const char *method);#define QTOSTRING_HELPER(s) #s
#define QTOSTRING(s) QTOSTRING_HELPER(s)
#ifndef QT_NO_DEBUG
# define QLOCATION "\0" __FILE__ ":" QTOSTRING(__LINE__)
# ifndef QT_NO_KEYWORDS
#  define METHOD(a)   qFlagLocation("0"#a QLOCATION)
# endif# define SLOT(a)     qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a)   qFlagLocation("2"#a QLOCATION)#else# ifndef QT_NO_KEYWORDS
#  define METHOD(a)   "0"#a
# endif# define SLOT(a)     "1"#a
# define SIGNAL(a)   "2"#a#endif

SIGNAL与SLOT宏会利用预编译器将一些参数转化成字符串,并且在前面添加上编码。

在调试模式中,如果signal的连接出现问题,提示警告信息的时候还会注明对应的文件位置。qFlagLocation 用于定位代码对应的行信息,会将对应代码的地址信息注册到一个有两个入口的表里。

Object.h文件中有关SIGNAL与SLOT宏部分代码如下:

connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));
connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));

通过对Object.h文件进行预编译,得到Object.i文件。

使用G++进行预编译:

g++ -E Object.h -o Object.i -I/usr/local/Trolltech/Qt-4.8.6/include/QtCore -I/usr/local/Trolltech/Qt-4.8.6/include -I. 

Object.i文件中结果如下:

connect(this, qFlagLocation("2""ageChanged(int)" "\0" "Object.h" ":" "54"), this, qFlagLocation("1""onAgeChanged(int)" "\0" "Object.h" ":" "54"));connect(this, qFlagLocation("2""scoreChanged(int)" "\0" "Object.h" ":" "55"), this, qFlagLocation("1""onScoreChanged(int)" "\0" "Object.h" ":" "55"));

3、类的元对象

程序编译时make调用MOC对工程源码进行解析,生成相应类的moc_xxx.cpp文件,

const QMetaObject Object::staticMetaObject = {{ &QObject::staticMetaObject, qt_meta_stringdata_Object,qt_meta_data_Object, &staticMetaObjectExtraData }
};

静态成员staticMetaObject被填充的值如下:

//元数据代表的类的基类的元数据,被填充为基类的元数据指针&QWidget::staticMetaObject
const QMetaObject *superdata;
//元数据的签名标记,被填充为qt_meta_stringdata_Widget.data
const char *stringdata;
//元数据的索引数组的指针,被填充为qt_meta_data_Widget
const uint *data;
//扩展元数据表的指针,内部被填充为函数指针
const QMetaObject **extradata;
qt_static_metacall。

staticMetaObjectExtraData初始化如下:

const QMetaObjectExtraData Object::staticMetaObjectExtraData = {0,  qt_static_metacall
};

QMetaObjectExtraData类型的内部成员static_metacall是一个指向Object::qt_static_metacall 的函数指针。

Object的内存布局如下:

Object内存布局已经包含了静态成员staticMetaObject和staticMetaObjectExtraData成员。

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

QObject::d_ptr->metaObject仅供动态元对象(QML对象)使用,所以一般而言,虚函数 metaObject() 仅返回类的 staticMetaObject。

4、元数据表

Qt程序编译时make会调用MOC工具对源文件进行分析,如果某个类包含了Q_OBJECT宏,MOC会生成对应的moc_xxx.cpp文件。

moc_Object.cpp文件内容中:

Object的元数据如下:

static const uint qt_meta_data_Object[] = {// content:内容信息6,       // revision        MOC生成代码的版本号0,       // classname      类名,在qt_meta_stringdata_Object数组中索引为02,   14, // classinfo       类信息,有2个cassinfo定义,4,   18, // methods         类有4个自定义方法,即信号与槽个数,3,   38, // properties      属性的位置信息,有3个自定义属性,1,   50, // enums/sets      枚举的位置信息,有一个自定义枚举,在qt_meta_stringdata_Object数组中索引为500,    0, // constructors    构造函数的位置信息0,       // flags2,       // signalCount// classinfo: key, value    //类信息的存储在qt_meta_stringdata_Object数组中,15,    7,    //第一个类信息,key的数组索引为15,即Author,value的数组索引为7,即Scorpio26,   22,      //第二个类信息,key的数组索引为26,即Version,value的数组索引为22,即1.0// signals: signature, parameters, type, tag, flags39,   35,   34,   34, 0x05,   //第一个自定义信号的签名存储在qt_meta_stringdata_Object数组中,//索引是39,即ageChanged(int)61,   55,   34,   34, 0x05,   //第二个自定义信号的签名存储在qt_meta_stringdata_Object数组中,//索引是61,即scoreChanged(int)// slots: signature, parameters, type, tag, flags79,   35,   34,   34, 0x0a,   //第一个自定义槽函数的签名存储在qt_meta_stringdata_Object数组中,//索引是79,即onAgeChanged(int)97,   55,   34,   34, 0x0a,   //第二个自定义槽函数的签名存储在qt_meta_stringdata_Object数组中,//索引是79,即onScoreChanged(int)// properties: name, type, flags35,  117, 0x02495103,        // 第一个自定义属性的签名存储在qt_meta_stringdata_Object中,索引是35,即age55,  117, 0x02495103,     // 第二个自定义属性的签名存储在qt_meta_stringdata_Object中,索引是55,即score127,  121, 0x0009510b,     // 第三个自定义属性的签名存储在qt_meta_stringdata_Object中,索引是127,即level// properties: notify_signal_id    //属性关联的信号编号0,1,0,// enums: name, flags, count, data121, 0x0,    4,   54,        //枚举的定义,存储在qt_meta_stringdata_Object中,索引是121,即Level,内含4个枚举常量// enum data: key, value          //枚举数据的键值对133, uint(Object::Basic),    //数组索引是133,即Basic139, uint(Object::Middle),    //数组索引是139,即Middle146, uint(Object::Advanced),  //数组索引是146,即Advanced155, uint(Object::Master),    //数组索引是155,即Master0        // eod   元数据结束标记
};

内省表是一个 uint 数组,分为五个部分:第一部分content,即内容,分为9行。第一行revision,指MOC生成代码的版本号(Qt4 是6,Qt5则是7)。第二个classname,即类名,该值是一个索引,指向字符串表的某一个位置(本例中就是第0位)。

static const char qt_meta_stringdata_Object[] = {"Object\0Scorpio\0Author\0""1.0\0Version\0\0""age\0ageChanged(int)\0score\0scoreChanged(int)\0""onAgeChanged(int)\0onScoreChanged(int)\0""int\0Level\0level\0Basic\0Middle\0Advanced\0""Master\0"
};

5、信号的实现

MOC在生成的moc_xxx.cpp文件中实现了信号,创建了一个指向参数的指针的数组,并将指针数组传给QMetaObject::activate函数。数组的第一个元素是返回值。本例中值是0,因为返回值是void。传给activate函数的第三个参数是信号的索引(本例中是0)。

// SIGNAL 0,ageChanged信号的实现
void Object::ageChanged(int _t1)
{void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };QMetaObject::activate(this, &staticMetaObject, 0, _a);
}// SIGNAL 1  scoreChanged信号的实现
void Object::scoreChanged(int _t1)
{void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };QMetaObject::activate(this, &staticMetaObject, 1, _a);
}

6、槽函数的调用

利用槽函数在qt_static_metacall 函数的索引位置来调用槽函数:

void Object::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void**_a)
{if (_c == QMetaObject::InvokeMetaMethod) {Q_ASSERT(staticMetaObject.cast(_o));Object *_t = static_cast<Object *>(_o);switch (_id) {case 0: _t->ageChanged((*reinterpret_cast< int(*)>(_a[1]))); break;case 1: _t->scoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;case 2: _t->onAgeChanged((*reinterpret_cast< int(*)>(_a[1]))); break;case 3: _t->onScoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;default: ;}}
}

7、元对象中的索引

在每一个QMetaObject对象中,槽、信号以及其它的对象可调用函数都会分配一个从0开始的索引。索引是有顺序的,信号在第一位,槽在第二位,最后是其它函数。这个索引在内部被称为相对索引,不包含父对象的索引位。

为了实现包含在继承链中其它函数的索引,在相对索引的基础上添加一个偏移量,得到绝对索引。绝对索引是在公开API中使用的索引,由QMetaObject::indexOf(Signal, Slot, Method) 类似的函数返回。

连接机制使用以信号为索引的向量。但是在向量中,所有的槽也会占有一定空间,通常在一个对象中,槽的数量要比信号多。所以从 Qt 4.6开始,使用的是一种仅包含信号索引的新的内部实现。

8、信号与槽的连接

开始连接时,Qt所要做的第一件事是找出所需要的信号和槽的索引。Qt会去查找元对象的字符串表来找出相应的索引。

然后,创建一个 QObjectPrivate::Connection 对象,将其添加到内部的链表中。

由于允许多个槽连接到同一个信号,需要为每一个信号添加一个已连接的槽的列表。每一个连接都必须包含接收对象和槽的索引。在接收对象销毁的时候,相应的连接也能够被自动销毁。所以每一个接收对象都需要知道谁连接到它自己,以便能够清理连接。

QObject对象的私有数据QObjectPrivate如下:

class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{Q_DECLARE_PUBLIC(QObject)
public:struct ExtraData{ExtraData() {}QList<QByteArray> propertyNames;QList<QVariant> propertyValues;};typedef void (*StaticMetaCallFunction)(QObject *, QMetaObject::Call, int, void **);struct Connection{QObject *sender;QObject *receiver;StaticMetaCallFunction callFunction;// The next pointer for the singly-linked ConnectionListConnection *nextConnectionList;//senders linked listConnection *next;Connection **prev;QBasicAtomicPointer<int> argumentTypes;ushort method_offset;ushort method_relative;ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking~Connection();int method() const { return method_offset + method_relative; }};// ConnectionList is a singly-linked liststruct ConnectionList {ConnectionList() : first(0), last(0) {}Connection *first;Connection *last;};struct Sender{QObject *sender;int signal;int ref;};QObjectPrivate(int version = QObjectPrivateVersion);virtual ~QObjectPrivate();void deleteChildren();void setParent_helper(QObject *);void moveToThread_helper();void setThreadData_helper(QThreadData *currentData, QThreadData *targetData);void _q_reregisterTimers(void *pointer);bool isSender(const QObject *receiver, const char *signal) const;QObjectList receiverList(const char *signal) const;QObjectList senderList() const;void addConnection(int signal, Connection *c);void cleanConnectionLists();static inline Sender *setCurrentSender(QObject *receiver,Sender *sender);static inline void resetCurrentSender(QObject *receiver,Sender *currentSender,Sender *previousSender);static void clearGuards(QObject *);static QObjectPrivate *get(QObject *o) {return o->d_func();}int signalIndex(const char *signalName) const;inline bool isSignalConnected(uint signalIdx) const;// To allow arbitrary objects to call connectNotify()/disconnectNotify() without making// the API public in QObject. This is used by QDeclarativeNotifierEndpoint.inline void connectNotify(const char *signal);inline void disconnectNotify(const char *signal);static inline void signalSignature(const QMetaMethod &signal,QVarLengthArray<char> *result);
public:QString objectName;ExtraData *extraData;    // extra data set by the userQThreadData *threadData; // id of the thread that owns the objectQObjectConnectionListVector *connectionLists;//连接链表向量容器Connection *senders;     // linked list of connections connected to this objectSender *currentSender;   // object currently activating the objectmutable quint32 connectedSignals[2];// preserve binary compatibility with code compiled without Qt 3 support// keeping the binary layout stable helps the Qt Creator debuggervoid *unused;QList<QPointer<QObject> > eventFilters;union {QObject *currentChildBeingDeleted;QAbstractDeclarativeData *declarativeData; //extra data used by the declarative module};// these objects are all used to indicate that a QObject was deleted// plus QPointer, which keeps a separate listQAtomicPointer<QtSharedPointer::ExternalRefCountData> sharedRefcount;
};

每一个QObject对象都有一个连接链表容器QObjectConnectionListVector *connectionLists:将每一个信号与一个 QObjectPrivate::Connection 的链表关联起来。

QObject::connect函数的实现如下:

bool QObject::connect(const QObject *sender, const char *signal,const QObject *receiver, const char *method,Qt::ConnectionType type)
{{const void *cbdata[] = { sender, signal, receiver, method, &type };if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))return true;}if (type == Qt::AutoCompatConnection) {type = Qt::AutoConnection;}if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",sender ? sender->metaObject()->className() : "(null)",(signal && *signal) ? signal+1 : "(null)",receiver ? receiver->metaObject()->className() : "(null)",(method && *method) ? method+1 : "(null)");return false;}QByteArray tmp_signal_name;if (!check_signal_macro(sender, signal, "connect", "bind"))return false;const QMetaObject *smeta = sender->metaObject();const char *signal_arg = signal;++signal; //skip code//在发送者对象的元对象中将信号的相对索引找到int signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, false);if (signal_index < 0){// check for normalized signaturestmp_signal_name = QMetaObject::normalizedSignature(signal - 1);signal = tmp_signal_name.constData() + 1;smeta = sender->metaObject();signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, false);}if (signal_index < 0){// re-use tmp_signal_name and signal from abovesmeta = sender->metaObject();signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, true);}if (signal_index < 0) {err_method_notfound(sender, signal_arg, "connect");err_info_about_objects("connect", sender, receiver);return false;}signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index);int signalOffset, methodOffset;computeOffsets(smeta, &signalOffset, &methodOffset);int signal_absolute_index = signal_index + methodOffset;signal_index += signalOffset;QByteArray tmp_method_name;int membcode = extract_code(method);if (!check_method_code(membcode, receiver, method, "connect"))return false;const char *method_arg = method;++method; // skip codeconst QMetaObject *rmeta = receiver->metaObject();//在接受者对象的元对象中将槽函数的相对索引找到int method_index_relative = -1;switch (membcode) {case QSLOT_CODE:method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, false);break;case QSIGNAL_CODE:method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, method, false);break;}if (method_index_relative < 0) {// check for normalized methodstmp_method_name = QMetaObject::normalizedSignature(method);method = tmp_method_name.constData();// rmeta may have been modified abovermeta = receiver->metaObject();switch (membcode) {case QSLOT_CODE:method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, false);if (method_index_relative < 0)method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, true);break;case QSIGNAL_CODE:method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, method, false);if (method_index_relative < 0)method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, method, true);break;}}if (method_index_relative < 0) {err_method_notfound(receiver, method_arg, "connect");err_info_about_objects("connect", sender, receiver);return false;}//检查连接参数是否匹配if (!QMetaObject::checkConnectArgs(signal, method)){qWarning("QObject::connect: Incompatible sender/receiver arguments""\n        %s::%s --> %s::%s",sender->metaObject()->className(), signal,receiver->metaObject()->className(), method);return false;}int *types = 0;if ((type == Qt::QueuedConnection)&& !(types = queuedConnectionTypes(smeta->method(signal_absolute_index).parameterTypes())))return false;//调用QMetaObjectPrivate::connect将信号与槽进行连接if (!QMetaObjectPrivate::connect(sender, signal_index, receiver, method_index_relative, rmeta ,type, types))return false;const_cast<QObject*>(sender)->connectNotify(signal - 1);return true;
}

QObject::connect函数的主要功能是在接受者对象的元对象中将槽函数的相对索引找到,在接受者对象的元对象中将槽函数的相对索引找到,最后调用QMetaObjectPrivate::connect将信号与槽进行连接。QObject及其派生类对象的元对象在创建时就有一个QObjectConnectionListVector连接链表容器,QObject::connect的作用就是将新的连接加入到信号发送者附属的元对象的连接链表容器的相应信号的连接链表中(一个信号可能连接多个槽函数)。

每个QObject及其派生类对象都有一个QObjectConnectionListVector *connectionLists连接链表容器,将信号的索引作为容器的索引,将每一个信号与一个 QObjectPrivate::ConnectionList链表关联起来。同时,QObjectPrivate::ConnectionList链表中连接的某个槽函数可能是接收者对象的槽函数链表中的一个。每个接收者对象的链表如下:

senderList 的 prev 指针是一个指针的指针。这是因为并不是真的指向上一个节点,而是指向上一个节点中的 next 指针。这个指针仅在连接销毁时使用,并且不能向后遍历。它允许不为第一个元素添加特殊处理。

容器中存储的ConnectionList如下:

  struct ConnectionList {ConnectionList() : first(0), last(0) {}Connection *first;//第一个结点Connection *last;//最后一个结点};

每个ConnectionList类型元素是一个双向链表,保存了信号的所有连接。连接的类型Connection结构如下:

struct Connection
{QObject *sender;//发送者QObject *receiver;//接受者StaticMetaCallFunction callFunction;//调用的槽函数// The next pointer for the singly-linked ConnectionListConnection *nextConnectionList;//senders linked listConnection *next;Connection **prev;QBasicAtomicPointer<int> argumentTypes;ushort method_offset;ushort method_relative;ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking~Connection();int method() const { return method_offset + method_relative; }
};

QMetaObjectPrivate::connect函数源码如下:
//将一个新的连接加入到信号发送者的连接链表容器中相应信号的连接链表中,其中连接加入的连接链表的索引为信号的索引

bool QMetaObjectPrivate::connect(const QObject *sender, int signal_index,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;//在元对象的元数据字符串中找到回调的函数指针qt_static_metacallQObjectPrivate::StaticMetaCallFunction callFunction =(rmeta && QMetaObjectPrivate::get(rmeta)->revision >= 6 && rmeta->d.extradata)? reinterpret_cast<const QMetaObjectExtraData *>(rmeta->d.extradata)->static_metacall : 0;QOrderedMutexLocker locker(signalSlotLock(sender),signalSlotLock(receiver));//如果连接类型为Qt::UniqueConnectionif (type & Qt::UniqueConnection){QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;if (connectionLists && connectionLists->count() > signal_index){//根据信号索引获取信号的连接const QObjectPrivate::Connection *c2 =(*connectionLists)[signal_index].first;int method_index_absolute = method_index + method_offset;while (c2){   //如果信号的接收者相同并且槽函数相同,即相同的连接已经存在if (c2->receiver == receiver && c2->method() == method_index_absolute)return false;//直接返回,c2 = c2->nextConnectionList;//下一个信号连接}}type &= Qt::UniqueConnection - 1;}//创建一个新的连接QObjectPrivate::Connection *c = new QObjectPrivate::Connection;//设置连接的属性c->sender = s;c->receiver = r;c->method_relative = method_index;c->method_offset = method_offset;c->connectionType = type;c->argumentTypes = types;c->nextConnectionList = 0;c->callFunction = callFunction;//设置回调的函数指针为qt_static_metacallQT_TRY{   //将连接添加到发送者的连接链表容器中相应的信号对应的连接链表中QObjectPrivate::get(s)->addConnection(signal_index, c);} QT_CATCH(...) {delete c;QT_RETHROW;}c->prev = &(QObjectPrivate::get(r)->senders);c->next = *c->prev;*c->prev = c;if (c->next)c->next->prev = &c->next;QObjectPrivate *const sender_d = QObjectPrivate::get(s);if (signal_index < 0){sender_d->connectedSignals[0] = sender_d->connectedSignals[1] = ~0;}else if (signal_index < (int)sizeof(sender_d->connectedSignals) * 8){sender_d->connectedSignals[signal_index >> 5] |= (1 << (signal_index & 0x1f));}return true;
}

9、信号的发射

使用emit发射信号时,实际调用MOC实现的信号函数,信号函数内部调用了QMetaObject::activate()函数。

// SIGNAL 0,ageChanged信号的实现
void Object::ageChanged(int _t1)
{void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };QMetaObject::activate(this, &staticMetaObject, 0, _a);
}// SIGNAL 1  scoreChanged信号的实现
void Object::scoreChanged(int _t1)
{void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };QMetaObject::activate(this, &staticMetaObject, 1, _a);
}void QMetaObject::activate(QObject *sender, const QMetaObject *m,int local_signal_index,void **argv)
{int signalOffset;int methodOffset;computeOffsets(m, &signalOffset, &methodOffset);int signal_index = signalOffset + local_signal_index;if (!sender->d_func()->isSignalConnected(signal_index))return; // 如果发送的信号没有槽连接,直接返回if (sender->d_func()->blockSig)return;//如果阻塞,直接返回int signal_absolute_index = methodOffset + local_signal_index;void *empty_argv[] = { 0 };if (qt_signal_spy_callback_set.signal_begin_callback != 0){qt_signal_spy_callback_set.signal_begin_callback(sender, signal_absolute_index,argv ? argv : empty_argv);}Qt::HANDLE currentThreadId = QThread::currentThreadId();QMutexLocker locker(signalSlotLock(sender));//获取发送者的连接链表容器QObjectConnectionListVector *connectionLists = sender->d_func()->connectionLists;if (!connectionLists){locker.unlock();if (qt_signal_spy_callback_set.signal_end_callback != 0)qt_signal_spy_callback_set.signal_end_callback(sender, signal_absolute_index);return;}++connectionLists->inUse;//从发送者的连接链表容器中使用信号索引作为索引,获取相应的连接链表const QObjectPrivate::ConnectionList *list;if (signal_index < connectionLists->count())list = &connectionLists->at(signal_index);elselist = &connectionLists->allsignals;do {//索取发送的信号的连接链表的第一个连接QObjectPrivate::Connection *c = list->first;if (!c) continue;//如果连接为空,继续// We need to check against last here to ensure that signals added// during the signal emission are not emitted in this emission.QObjectPrivate::Connection *last = list->last;do{if (!c->receiver)continue;//如果连接的接收者为空,继续QObject * const receiver = c->receiver;const bool receiverInSameThread = currentThreadId == receiver->d_func()->threadData->threadId;// determine if this connection should be sent immediately or// put into the event queueif ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)|| (c->connectionType == Qt::QueuedConnection)){queued_activate(sender, signal_absolute_index, c, argv ? argv : empty_argv);continue;
#ifndef QT_NO_THREAD}//阻塞队列连接类型else if (c->connectionType == Qt::BlockingQueuedConnection){locker.unlock();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;QCoreApplication::postEvent(receiver, new QMetaCallEvent(c->method_offset, c->method_relative,c->callFunction,sender, signal_absolute_index,0, 0,argv ? argv : empty_argv,&semaphore));semaphore.acquire();locker.relock();continue;
#endif}QObjectPrivate::Sender currentSender;QObjectPrivate::Sender *previousSender = 0;if (receiverInSameThread){currentSender.sender = sender;currentSender.signal = signal_absolute_index;currentSender.ref = 1;previousSender = QObjectPrivate::setCurrentSender(receiver, ¤tSender);}//获取连接的回调函数指针const QObjectPrivate::StaticMetaCallFunction callFunction = c->callFunction;const int method_relative = c->method_relative;//如果连接的方法的偏移小于接收者的元对象的方法的偏移if (callFunction && c->method_offset <= receiver->metaObject()->methodOffset()){//we compare the vtable to make sure we are not in the destructor of the object.locker.unlock();if (qt_signal_spy_callback_set.slot_begin_callback != 0)qt_signal_spy_callback_set.slot_begin_callback(receiver, c->method(), argv ? argv : empty_argv);//根据接收者的方法偏移,接收者等参数调用qt_static_metacall回调函数callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv ? argv : empty_argv);if (qt_signal_spy_callback_set.slot_end_callback != 0)qt_signal_spy_callback_set.slot_end_callback(receiver, c->method());locker.relock();}else{const int method = method_relative + c->method_offset;locker.unlock();if (qt_signal_spy_callback_set.slot_begin_callback != 0){qt_signal_spy_callback_set.slot_begin_callback(receiver,method,argv ? argv : empty_argv);}//根据接收者、接收者的方法索引等参数调用发送元对象的metacallmetacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);if (qt_signal_spy_callback_set.slot_end_callback != 0)qt_signal_spy_callback_set.slot_end_callback(receiver, method);locker.relock();}if (receiverInSameThread)QObjectPrivate::resetCurrentSender(receiver, ¤tSender, previousSender);if (connectionLists->orphaned)break;} while (c != last && (c = c->nextConnectionList) != 0);if (connectionLists->orphaned)break;} while (list != &connectionLists->allsignals &&//start over for all signals;((list = &connectionLists->allsignals), true));--connectionLists->inUse;Q_ASSERT(connectionLists->inUse >= 0);if (connectionLists->orphaned){if (!connectionLists->inUse)delete connectionLists;}else if (connectionLists->dirty){sender->d_func()->cleanConnectionLists();}locker.unlock();if (qt_signal_spy_callback_set.signal_end_callback != 0)qt_signal_spy_callback_set.signal_end_callback(sender, signal_absolute_index);}

metacall函数内部调用了qt_metacall函数。

int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv)
{if (QMetaObject *mo = object->d_ptr->metaObject)return static_cast<QAbstractDynamicMetaObject*>(mo)->metaCall(cl, idx, argv);elsereturn object->qt_metacall(cl, idx, argv);
}int Object::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 < 4)qt_static_metacall(this, _c, _id, _a);_id -= 4;}
#ifndef QT_NO_PROPERTIESelse if (_c == QMetaObject::ReadProperty) {void *_v = _a[0];switch (_id) {case 0: *reinterpret_cast< int*>(_v) = age(); break;case 1: *reinterpret_cast< int*>(_v) = score(); break;case 2: *reinterpret_cast< Level*>(_v) = level(); break;}_id -= 3;} else if (_c == QMetaObject::WriteProperty) {void *_v = _a[0];switch (_id) {case 0: setAge(*reinterpret_cast< int*>(_v)); break;case 1: setScore(*reinterpret_cast< int*>(_v)); break;case 2: setLevel(*reinterpret_cast< Level*>(_v)); break;}_id -= 3;} else if (_c == QMetaObject::ResetProperty) {_id -= 3;} else if (_c == QMetaObject::QueryPropertyDesignable) {_id -= 3;} else if (_c == QMetaObject::QueryPropertyScriptable) {_id -= 3;} else if (_c == QMetaObject::QueryPropertyStored) {_id -= 3;} else if (_c == QMetaObject::QueryPropertyEditable) {_id -= 3;} else if (_c == QMetaObject::QueryPropertyUser) {_id -= 3;}#endif // QT_NO_PROPERTIESreturn _id;
}

qt_metacall函数内部调用了qt_static_metacall函数。

10、槽函数的调用

槽函数最终通过qt_static_metacall函数根据参数调用相应的槽函数。

void Object::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{if (_c == QMetaObject::InvokeMetaMethod) {Q_ASSERT(staticMetaObject.cast(_o));Object *_t = static_cast<Object *>(_o);switch (_id) {case 0: _t->ageChanged((*reinterpret_cast< int(*)>(_a[1]))); break;case 1: _t->scoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;case 2: _t->onAgeChanged((*reinterpret_cast< int(*)>(_a[1]))); break;case 3: _t->onScoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;default: ;}}
}

11、函数调用流程分析

在onAgeChanged(int age)槽函数内部断点调试。

得到的函数调用栈如下:

函数调用栈分析:

Object::qt_metacall函数内部调用了Object::setAge函数,setAge内部调用Object::ageChanged信号函数,ageChanged信号函数内部调用了QMetaObject::activate函数,activate函数内部调用Object::qt_static_metacall函数,最终qt_static_metacall函数内部调用了槽函数onAgeChanged。

因此在本例中,当调用ob.setProperty("age", QVariant(30));设置属性时,触发了QMetaProperty::Write函数的调用,进而调用MOC实现的moc_Object.cpp文件中的Object::qt_metacall,qt_metacall内部调用setAge函数,setAge函数内部发射信号ageChanged,即调用Object::ageChanged信号函数,Object::ageChanged函数内部调用了Object对象的元对象的QMetaObject::activate函数,activate函数内部调用了Object::qt_static_metacall函数,最终qt_static_metacall内部实现对槽函数onAgeChanged的调用。

本例中,信号和槽处于同一线程,连接类型为直接连接,因此属于同步调用,是最简单的调用类型。QMetaObject::activate函数内部实际上根据Object对象的元对象中的信号连接链表容器查找得到信号对应的:qt_static_metacall回调函数,进而回调的。

三、信号槽的标准C++实现

1、Qt元对象的模拟实现

Object类的实现:

#ifndef OBJECT_H
#define OBJECT_H#include<map>
#include <iostream>
#include <cstring>
using namespace std;//宏定义
#define SLOT(a)     #a
#define SIGNAL(a)   #a
#define cpp_slots
#define cpp_signals protected
#define cpp_emitclass Object;
//元对象系统,负责搜集信号与槽的名称
struct MetaObject
{//信号组const char * signal;//槽组const char * slot;//激活某个信号,idx为信号索引static void active(Object * sender, int idx);
};//被连接对象信息
struct Connection
{Object * receiver;//信号的接收者int method;//槽函数索引
};//保存信号索引与连接对象映射
typedef std::multimap<int, Connection> ConnectionMap;
typedef std::multimap<int, Connection>::iterator ConnectionMapIt;//信号和槽的索引查找函数,返回信号或槽的索引
static int find_string(const char * str, const char * substr)
{if (strlen(str) < strlen(substr))return -1;int idx = 0;int len = strlen(substr);bool start = true;const char * pos = str;while (*pos){if (start && !strncmp(pos, substr, len) && pos[len] == '\n')return idx;start = false;if (*pos == '/n'){idx++;start = true;}pos++;}return -1;
}class Object
{static MetaObject meta;//静态元对象声明void metacall(int idx);//声明元方法调用函数
public:Object(){}//建立连接static void cpp_connect(Object* sender, const char* sig, Object* receiver, const char* slt){cout << "connecting a signal to slot..." << endl;//从元对象数据表中查看信号和槽是否存在int sig_idx = find_string(sender->meta.signal, sig);int slt_idx = find_string(receiver->meta.slot, slt);//如果没有找到信号或者槽if (sig_idx == -1 || slt_idx == -1){perror("signal or slot not found!");}else{//创建一个连接,连接内存储接收者和槽函数的索引Connection c = { receiver, slt_idx };cout << "add a signal index and an Connection of receiver to sender's Connection map..." << endl;//将信号的索引和接收者的信息存储到信号发射者的map容器中sender->connections.insert(std::pair<int, Connection>(sig_idx, c));cout << "connected success." << endl;}}void emitSignal()//公有测试函数,发送一个信号{cout << "emiting a signal..." << endl;cpp_emit valueChanged();}cpp_signals:void valueChanged();//信号声明
public cpp_slots:void onValueChanged()//槽函数{cout << "Value Changed."<< endl;}friend class MetaObject;
private:ConnectionMap connections;//连接键值对
};#endif // OBJECT_H

moc_Object.cpp实现:

#include "Object.h"//信号的名称
static const char signalNames[] = "valueChanged\n";//槽的名称
static const char slotNames[] = "onValueChanged\n";//静态元对象的填充
MetaObject Object::meta = { signalNames, slotNames };//元方法调用函数的实现,根据连接的索引回调槽函数
void Object::metacall(int idx)
{switch (idx) {case 0:onValueChanged();break;default:break;};
}//信号的实现
void Object::valueChanged()
{MetaObject::active(this, 0);
}//激活信号
void MetaObject::active(Object* sender, int idx)
{ConnectionMapIt it;std::pair<ConnectionMapIt, ConnectionMapIt> ret;ret = sender->connections.equal_range(idx);for (it = ret.first; it != ret.second; ++it){Connection c = (*it).second;c.receiver->metacall(c.method);//根据索引调用元方法}
}

2、信号槽模拟使用

Main.cpp文件:

#include <iostream>
#include "Object.h"using namespace std;int main(int argc, char *argv[])
{char p[32] = SLOT(Object);cout << "cur_value: " << p << endl;Object obj1, obj2;//连接信号和槽Object::cpp_connect(&obj1, SLOT(valueChanged), &obj2, SIGNAL(onValueChanged));//发射一个信号进行测试obj1.emitSignal();getchar();return 0;
}

四、信号槽的开源实现

1、sigslot

sigslot是信号槽的一个非常精炼的C++实现,作者是Sarah Thompson,sigslot实现只有一个头文件sigslot.h,跨平台且线程安全。在WebRTC中,sigslot .h是其基础的事件处理框架, 在多个模块的消息通知,响应处理中被使用。

sigslot库官网:

sigslot - C++ Signal/Slot Library

Sigslot使用示例如下:

#include "sigslot.h"
#include <string>
#include <stdio.h>
#include <iostream>
#include <windows.h>using namespace sigslot;using namespace std;class CSender
{
public:sigslot::signal2<string, int> m_pfnsigDanger;void Panic(){static int nVal = 0;char szVal[20] = { 0 };sprintf_s(szVal,20, "help--%d", nVal);m_pfnsigDanger(szVal, nVal++);}
};class CReceiver :public sigslot::has_slots<>
{
public:void OnDanger(string strMsg, int nVal){//printf("%s ==> %d", strMsg.c_str(), nVal);cout << strMsg.c_str() << " ==> " << nVal << endl;}
};int main()
{CSender sender;CReceiver recever;cout << "create object ok..." << endl;  sender.m_pfnsigDanger.connect(&recever, &CReceiver::OnDanger); cout << "connect succ!" << endl;while (1){cout << "in while..." << endl;sender.Panic();Sleep(2000);cout << "end of sleep" << endl;}return 0;
}

如果在Qt工程中使用sigslot.h,sigslot.h中的emit函数名会和Qt中的emit宏冲突,修改方法有两个,一是将sigslot.h的emit改成其他名字,二是在.pro文件中添加DEFINES+=QT_NO_EMIT,禁用Qt的emit宏。

2、Boost.Signals

Boost.Signals实现了signals/slots模式,信号(signals)被发射,而插槽(slots)接收该信号。

#include <iostream>
#include "boost/signals.hpp"void firstSlot() {std::cout << "void firstSlot()";
}class secondSlot {
public:void operator()() const {std::cout <<"void secondSlot::operator()() const ";}
};int main()
{boost::signal<void ()> sig;sig.connect(&firstSlot);sig.connect(secondSlot());std::cout << "Emitting a signal... ";sig();
}

插槽函数的执行顺序是随机的,可以使用分组参数来控制调用顺序。

sig.connect(1,&firstSlot);sig.connect(2,secondSlot());

3、Qt信号槽实现与Boost信号槽实现的区别

Boost.Signals

Qt Signals 和 Slots

一个信号就是一个对象

信号只能是成员函数

发出信号类似于函数调用

发出信号类似于函数调用,Qt 提供了一个 emit 关键字来完成这个操作

信号可以是全局的、局部的或者是成员对象

信号只能是成员函数

任何能够访问到信号对象的代码都可以发出信号

只有信号的拥有者才能发出信号

槽是任何可被调用的函数或者函数对象

槽是经过特别设计的成员函数

可以有返回值,返回值可以在多个槽中使用

没有返回值

同步的

同步的或者队列的

非线程安全

线程安全,可以跨线程使用

当且仅当槽是可追踪的时候,槽被销毁时,连接自动断开

槽被销毁时,连接都会自动断开(因为所有槽都是可追踪的)

类型安全(编译器检查)

类型安全(运行期检查)

参数列表必须完全一致

槽可以忽略信号中多余的参数

信号、槽可以是模板

信号、槽不能是模板

C++ 直接实现

通过由 moc 生成的元对象实现(moc 以及元对象系统都是 C++ 直接实现的)

没有内省机制

可以通过内省发现

可以通过元对象调用

连接可以从资源文件中自动推断出

Hello Qt——Qt信号槽机制源码解析相关推荐

  1. Qt的信号槽机制介绍(含Qt5与Qt4的差异对比)

    转载地址: https://blog.csdn.net/nicai888/article/details/51169520 一 闲谈: 熟悉Window下编程的小伙伴们,对其消息机制并不陌生, 话说: ...

  2. 学习QT之信号槽机制详解

    学习QT之信号槽机制详解 一.Qt信号槽机制 概念:信号槽是Qt框架引以为豪的机制之一.所谓信号槽,实际就是观察者模式.当某个事件发生之后,比如:按钮检测到自己被点击了一下,它就会发出一个信号(sig ...

  3. JVM-白话聊一聊JVM类加载和双亲委派机制源码解析

    文章目录 Java 执行代码的大致流程 类加载loadClass的步骤 类加载器和双亲委派机制 sun.misc.Launcher源码解析 Launcher实例化 Launcher 构造函数 双亲委派 ...

  4. Qt的信号槽机制介绍

    Qt 是一个跨平台的 C++ GUI 应用构架,它提供了丰富的窗口部件集,具有面向对象.易于扩展.真正的组件编程等特点,更为引人注目的是目前 Linux 上最为流行的 KDE 桌面环境就是建立在 Qt ...

  5. MapReduce的分片机制源码解析

    目录 一.分⽚的概念 二.分片大小的选择 三.源码解析 1)FileSplit源码解析 2)FileInputFormat源码解析 3)TextInputFormat源码解析 4) LineRecor ...

  6. Android Handler消息机制源码解析

    好记性不如烂笔头,今天来分析一下Handler的源码实现 Handler机制是Android系统的基础,是多线程之间切换的基础.下面我们分析一下Handler的源码实现. Handler消息机制有4个 ...

  7. 聊聊Dubbo - Dubbo可扩展机制源码解析

    2019独角兽企业重金招聘Python工程师标准>>> 摘要: 在Dubbo可扩展机制实战中,我们了解了Dubbo扩展机制的一些概念,初探了Dubbo中LoadBalance的实现, ...

  8. Kubernetes的Device Plugin机制源码解析

    简介: Kubernetes 1.8 引入的Device Plugin机制,通过扩展的方式实现支持GPU.FPGA.高性能 NIC.InfiniBand等各种设备的集成.而Device Manager ...

  9. Android View系列(二):事件分发机制源码解析

    概述 在介绍点击事件规则之前,我们需要知道我们分析的是MotionEvent,即点击事件,所谓的事件分发就是对MotionEvent事件的分发过程,即当一个MotionEvent生成以后,系统需要把这 ...

最新文章

  1. boost::function_types::result_type用法的测试程序
  2. Java的HTTP服务端响应式编程
  3. 你的安全设置不允许在您的计算机,你的安全设置不允许网站使用安装在你的计算机上的ActiveX控件...
  4. 在sql当中为了让数据做缓存做with as的操作
  5. jdk 流合并_JDK 12,合并的收集器和命名的挑战
  6. C语言中文件定位函数总结
  7. thinkserver rd650管理口地址_路由器WAN口和LAN口有什么区别【区别介绍】
  8. Delphi控件的“拿来主义”
  9. 找不到腾讯云MFA动态码了,无法登录腾讯云的解决办法
  10. 视图函数的基本理解 django
  11. HTML5 canvas游戏工作原理
  12. 解决:用PivotGridControl 与 chartControl 配合使用,Series最大只显示10条
  13. SpringBoot项目的测试类
  14. python分布式爬虫系统_python简单分布式爬虫
  15. 服务器网站出现service,网站出现service unavailable的解决方法
  16. Google 2018 更新内容
  17. Django中文文档
  18. vim 插件安装和卸载
  19. 计算机动画的教育应用研究,计算机动画技术在高校CAI课件制作中的应用研究
  20. 阿里旺旺自动回复工具开发一

热门文章

  1. 伤感的时候写个博客,缓解伤心。
  2. 元宇宙 3D 开荒场 - 探味奇遇记
  3. 用Excel自动生成工作表目录
  4. face detection[DSFD]
  5. tensorflow2.0使用GELU激活函数
  6. 苹果开发者账号中,测试机器列表达到100台上限,如何删除一些设备、添加新设备?
  7. 分布式session的实现方式
  8. 设置input只读不能修改
  9. manjaro安装Microsoft Edge浏览器
  10. NX CAM API 二次开发获取、设置平面铣 PLANAR_MILL 的【切削模式】的方法代码