信號槽是觀察者模式的一種實現,特性以下:
A、一個信號就是一個可以被觀察的事件,或者至少是事件已經發生的一種通知;
B、一個槽就是一個觀察者,一般就是在被觀察的對象發生改變的時候——也能夠說是信號發出的時候——被調用的函數;
C、信號與槽的鏈接,造成一種觀察者-被觀察者的關係;
D、當事件或者狀態發生改變的時候,信號就會被髮出;同時,信號發出者有義務調用全部註冊的對這個事件(信號)感興趣的函數(槽)。
信號和槽是多對多的關係。一個信號能夠鏈接多個槽,而一個槽也能夠監聽多個信號。
信號槽與語言無關,有多種方法能夠實現信號槽,不一樣的實現機制會致使信號槽的差異很大。信號槽術語最初來自 Trolltech 公司的 Qt 庫,因爲其設計理念的先進性,馬上引發計算機科學界的注意,提出了多種不一樣的實現。目前,信號槽依然是 Qt 庫的核心之一,其餘許多庫也提供了相似的實現,甚至出現了一些專門提供這一機制的工具庫。
信號槽是Qt對象以及其派生類對象之間的一種高效通訊接口,是Qt的核心特性,也是Qt區別與其餘工具包的重要地方。信號槽徹底獨立於標準的C/C++語言,所以要正確的處理好信號和槽,必須藉助於一個成爲MOC(Meta Object Compiler)的Qt工具,MOC工具是一個C++預處理程序,能爲高層次的事件處理自動生成所須要的附加代碼。ios
MFC中的消息機制沒有采用C++中的虛函數機制,緣由是消息太多,虛函數開銷太大。在Qt中也沒有采用C++中的虛函數機制,而是採用了信號槽機制,緣由與此相同。更深層次的緣由上,多態的底層實現機制只有兩種,一種是按照名稱查表,一種是按照位置查表。兩種方式各有利弊,而C++的虛函數機制無條件的採用了後者,致使的問題就是在子類不多重載基類實現的時候開銷太大,再加上界面編程中子類衆多的狀況,基本上C++的虛函數機制效率過低,因而各家庫的編寫者就只好自謀生路,固然,這實際上是C++語言自己的缺陷。編程
使用簡單的實例:windows
#ifndef OBJECT_H #define OBJECT_H #include <QObject> #include <QString> #include <QDebug> class Object : public QObject { Q_OBJECT Q_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"); //設置屬性age ob.setProperty("age", QVariant(30)); qDebug() << "age: " << ob.age(); qDebug() << "property age: " << ob.property("age").toInt(); //設置屬性score ob.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(); }
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"));
程序編譯時make調用MOC對工程源碼進行解析,生成相應類的moc_xxx.cpp文件,工具
const QMetaObject Object::staticMetaObject = { { &QObject::staticMetaObject, qt_meta_stringdata_Object, qt_meta_data_Object, &staticMetaObjectExtraData } };
靜態成員staticMetaObject被填充的值以下:
const QMetaObject superdata;//元數據表明的類的基類的元數據,被填充爲基類的元數據指針&QWidget::staticMetaObject
const char stringdata;//元數據的簽名標記,被填充爲qt_meta_stringdata_Widget.data
const uint *data;//元數據的索引數組的指針,被填充爲qt_meta_data_Widget
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成員。post
const QMetaObject *Object::metaObject() const { return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; }
QObject::d_ptr->metaObject僅供動態元對象(QML對象)使用,因此通常而言,虛函數 metaObject() 僅返回類的 staticMetaObject。
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數組中索引爲0 2, 14, // classinfo 類信息,有2個cassinfo定義, 4, 18, // methods 類有4個自定義方法,即信號與槽個數, 3, 38, // properties 屬性的位置信息,有3個自定義屬性, 1, 50, // enums/sets 枚舉的位置信息,有一個自定義枚舉,在qt_meta_stringdata_Object數組中索引爲50 0, 0, // constructors 構造函數的位置信息 0, // flags 2, // signalCount // classinfo: key, value //類信息的存儲在qt_meta_stringdata_Object數組中, 15, 7, //第一個類信息,key的數組索引爲15,即Author,value的數組索引爲7,即Scorpio 26, 22, //第二個類信息,key的數組索引爲26,即Version,value的數組索引爲22,即1.0 // signals: signature, parameters, type, tag, flags 39, 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, flags 79, 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, flags 35, 117, 0x02495103, // 第一個自定義屬性的簽名存儲在qt_meta_stringdata_Object中,索引是35,即age 55, 117, 0x02495103, // 第二個自定義屬性的簽名存儲在qt_meta_stringdata_Object中,索引是55,即score 127, 121, 0x0009510b, // 第三個自定義屬性的簽名存儲在qt_meta_stringdata_Object中,索引是127,即level // properties: notify_signal_id //屬性關聯的信號編號 0, 1, 0, // enums: name, flags, count, data 121, 0x0, 4, 54, //枚舉的定義,存儲在qt_meta_stringdata_Object中,索引是121,即Level,內含4個枚舉常量 // enum data: key, value //枚舉數據的鍵值對 133, uint(Object::Basic), //數組索引是133,即Basic 139, uint(Object::Middle), //數組索引是139,即Middle 146, uint(Object::Advanced), //數組索引是146,即Advanced 155, uint(Object::Master), //數組索引是155,即Master 0 // 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" };
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); }
利用槽函數在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: ; } } }
在每個QMetaObject對象中,槽、信號以及其它的對象可調用函數都會分配一個從0開始的索引。索引是有順序的,信號在第一位,槽在第二位,最後是其它函數。這個索引在內部被稱爲相對索引,不包含父對象的索引位。
爲了實現包含在繼承鏈中其它函數的索引,在相對索引的基礎上添加一個偏移量,獲得絕對索引。絕對索引是在公開API中使用的索引,由QMetaObject::indexOf(Signal, Slot, Method) 相似的函數返回。
鏈接機制使用以信號爲索引的向量。可是在向量中,全部的槽也會佔有必定空間,一般在一個對象中,槽的數量要比信號多。因此從 Qt 4.6開始,使用的是一種僅包含信號索引的新的內部實現。
開始鏈接時,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 ConnectionList Connection *nextConnectionList; //senders linked list Connection *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 list struct 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 user QThreadData *threadData; // id of the thread that owns the object QObjectConnectionListVector *connectionLists;//鏈接鏈表向量容器 Connection *senders; // linked list of connections connected to this object Sender *currentSender; // object currently activating the object mutable quint32 connectedSignals[2]; // preserve binary compatibility with code compiled without Qt 3 support // keeping the binary layout stable helps the Qt Creator debugger void *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 list QAtomicPointer<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 signatures tmp_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 above smeta = 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 code const 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 methods tmp_method_name = QMetaObject::normalizedSignature(method); method = tmp_method_name.constData(); // rmeta may have been modified above rmeta = 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 ConnectionList Connection *nextConnectionList; //senders linked list Connection *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_metacall QObjectPrivate::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::UniqueConnection if (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_metacall QT_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; }
使用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); else list = &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 queue if ((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); } //根據接收者、接收者的方法索引等參數調用發送元對象的metacall metacall(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); else return 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_PROPERTIES else 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_PROPERTIES return _id; }
qt_metacall函數內部調用了qt_static_metacall函數。
槽函數最終經過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: ; } } }
在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回調函數,進而回調的。
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_emit class 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);//根據索引調用元方法 } }
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; }
sigslot是信號槽的一個很是精煉的C++實現,做者是Sarah Thompson,sigslot實現只有一個頭文件sigslot.h,跨平臺且線程安全。在WebRTC中,sigslot .h是其基礎的事件處理框架, 在多個模塊的消息通知,響應處理中被使用。
sigslot庫官網:
http://sigslot.sourceforge.net/
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宏。
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());
Boost.Signals | Qt Signals 和 Slots |
---|---|
一個信號就是一個對象 | 信號只能是成員函數 |
發出信號相似於函數調用 | 發出信號相似於函數調用,Qt 提供了一個 emit 關鍵字來完成這個操做 |
信號能夠是全局的、局部的或者是成員對象 | 信號只能是成員函數 |
任何可以訪問到信號對象的代碼均可以發出信號 | 只有信號的擁有者才能發出信號 |
槽是任何可被調用的函數或者函數對象 | 槽是通過特別設計的成員函數 |
能夠有返回值,返回值能夠在多個槽中使用 | 沒有返回值 |
同步的 | 同步的或者隊列的 |
非線程安全 | 線程安全,能夠跨線程使用 |
當且僅當槽是可追蹤的時候,槽被銷燬時,鏈接自動斷開 | 槽被銷燬時,鏈接都會自動斷開(由於全部槽都是可追蹤的) |
類型安全(編譯器檢查) | 類型安全(運行期檢查) |
參數列表必須徹底一致 | 槽能夠忽略信號中多餘的參數 |
信號、槽能夠是模板 | 信號、槽不能是模板 |
C++ 直接實現 | 經過由 moc 生成的元對象實現(moc 以及元對象系統都是 C++ 直接實現的) |
沒有內省機制 | 能夠經過內省發現,能夠經過元對象調用,鏈接能夠從資源文件中自動推斷出 |