基于Qt4.8.6版本
信號槽是觀察者模式的一種實現(xiàn),特性如下:
A、一個信號就是一個能夠被觀察的事件,或者至少是事件已經(jīng)發(fā)生的一種通知;
B、一個槽就是一個觀察者,通常就是在被觀察的對象發(fā)生改變的時候——也可以說是信號發(fā)出的時候——被調(diào)用的函數(shù);
C、信號與槽的連接,形成一種觀察者-被觀察者的關(guān)系;
D、當(dāng)事件或者狀態(tài)發(fā)生改變的時候,信號就會被發(fā)出;同時,信號發(fā)出者有義務(wù)調(diào)用所有注冊的對這個事件(信號)感興趣的函數(shù)(槽)。
信號和槽是多對多的關(guān)系。一個信號可以連接多個槽,而一個槽也可以監(jiān)聽多個信號。
信號槽與語言無關(guān),有多種方法可以實現(xiàn)信號槽,不同的實現(xiàn)機制會導(dǎo)致信號槽的差別很大。信號槽術(shù)語最初來自 Trolltech 公司的 Qt 庫,由于其設(shè)計理念的先進(jìn)性,立刻引起計算機科學(xué)界的注意,提出了多種不同的實現(xiàn)。目前,信號槽依然是 Qt 庫的核心之一,其他許多庫也提供了類似的實現(xiàn),甚至出現(xiàn)了一些專門提供這一機制的工具庫。
? 信號槽是Qt對象以及其派生類對象之間的一種高效通信接口,是Qt的核心特性,也是Qt區(qū)別與其他工具包的重要地方。信號槽完全獨立于標(biāo)準(zhǔn)的C/C++語言,因此要正確的處理好信號和槽,必須借助于一個成為MOC(Meta Object Compiler)的Qt工具,MOC工具是一個C++預(yù)處理程序,能為高層次的事件處理自動生成所需要的附加代碼。
成都創(chuàng)新互聯(lián)公司是一家專業(yè)提供札達(dá)企業(yè)網(wǎng)站建設(shè),專注與成都網(wǎng)站設(shè)計、網(wǎng)站建設(shè)、H5響應(yīng)式網(wǎng)站、小程序制作等業(yè)務(wù)。10年已為札達(dá)眾多企業(yè)、政府機構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站制作公司優(yōu)惠進(jìn)行中。
MFC中的消息機制沒有采用C++中的虛函數(shù)機制,原因是消息太多,虛函數(shù)開銷太大。在Qt中也沒有采用C++中的虛函數(shù)機制,而是采用了信號槽機制,原因與此相同。更深層次的原因上,多態(tài)的底層實現(xiàn)機制只有兩種,一種是按照名稱查表,一種是按照位置查表。兩種方式各有利弊,而C++的虛函數(shù)機制無條件的采用了后者,導(dǎo)致的問題就是在子類很少重載基類實現(xiàn)的時候開銷太大,再加上界面編程中子類眾多的情況,基本上C++的虛函數(shù)機制效率太低,于是各家?guī)斓木帉懻呔椭缓米灾\生路,當(dāng)然,這其實是C++語言本身的缺陷。
使用簡單的實例:
#ifndef OBJECT_H
#define OBJECT_H
#include
#include
#include
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函數(shù):
#include
#include "Object.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Object ob("object");
//設(shè)置屬性age
ob.setProperty("age", QVariant(30));
qDebug() << "age: " << ob.age();
qDebug() << "property age: " << ob.property("age").toInt();
//設(shè)置屬性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();
//內(nèi)省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宏會利用預(yù)編譯器將一些參數(shù)轉(zhuǎn)化成字符串,并且在前面添加上編碼。
在調(diào)試模式中,如果signal的連接出現(xiàn)問題,提示警告信息的時候還會注明對應(yīng)的文件位置。qFlagLocation?用于定位代碼對應(yīng)的行信息,會將對應(yīng)代碼的地址信息注冊到一個有兩個入口的表里。
Object.h文件中有關(guān)SIGNAL與SLOT宏部分代碼如下:
connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));
connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));
通過對Object.h文件進(jìn)行預(yù)編譯,得到Object.i文件。
使用G++進(jìn)行預(yù)編譯: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文件中結(jié)果如下:
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調(diào)用MOC對工程源碼進(jìn)行解析,生成相應(yīng)類的moc_xxx.cpp文件,
const QMetaObject Object::staticMetaObject = {
{ &QObject::staticMetaObject, qt_meta_stringdata_Object,
qt_meta_data_Object, &staticMetaObjectExtraData }
};
靜態(tài)成員staticMetaObject被填充的值如下:
??????const QMetaObject superdata;//元數(shù)據(jù)代表的類的基類的元數(shù)據(jù),被填充為基類的元數(shù)據(jù)指針&QWidget::staticMetaObject
????const char stringdata;//元數(shù)據(jù)的簽名標(biāo)記,被填充為qt_meta_stringdata_Widget.data
????const uint *data;//元數(shù)據(jù)的索引數(shù)組的指針,被填充為qt_meta_data_Widget
??????? const QMetaObject **extradata;//擴展元數(shù)據(jù)表的指針,內(nèi)部被填充為函數(shù)指針qt_static_metacall。
staticMetaObjectExtraData初始化如下:
const QMetaObjectExtraData Object::staticMetaObjectExtraData = {
0, qt_static_metacall
};
QMetaObjectExtraData類型的內(nèi)部成員static_metacall是一個指向Object::qt_static_metacall 的函數(shù)指針。
Object的內(nèi)存布局如下:
Object內(nèi)存布局已經(jīng)包含了靜態(tài)成員staticMetaObject和
staticMetaObjectExtraData成員。
const QMetaObject *Object::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
}
QObject::d_ptr->metaObject僅供動態(tài)元對象(QML對象)使用,所以一般而言,虛函數(shù) metaObject() 僅返回類的 staticMetaObject。
Qt程序編譯時make會調(diào)用MOC工具對源文件進(jìn)行分析,如果某個類包含了Q_OBJECT宏,MOC會生成對應(yīng)的moc_xxx.cpp文件。
moc_Object.cpp文件內(nèi)容中:
Object的元數(shù)據(jù)如下:
static const uint qt_meta_data_Object[] = {
// content:內(nèi)容信息
6, // revision MOC生成代碼的版本號
0, // classname 類名,在qt_meta_stringdata_Object數(shù)組中索引為0
2, 14, // classinfo 類信息,有2個cassinfo定義,
4, 18, // methods 類有4個自定義方法,即信號與槽個數(shù),
3, 38, // properties 屬性的位置信息,有3個自定義屬性,
1, 50, // enums/sets 枚舉的位置信息,有一個自定義枚舉,在qt_meta_stringdata_Object數(shù)組中索引為50
0, 0, // constructors 構(gòu)造函數(shù)的位置信息
0, // flags
2, // signalCount
// classinfo: key, value //類信息的存儲在qt_meta_stringdata_Object數(shù)組中,
15, 7, //第一個類信息,key的數(shù)組索引為15,即Author,value的數(shù)組索引為7,即Scorpio
26, 22, //第二個類信息,key的數(shù)組索引為26,即Version,value的數(shù)組索引為22,即1.0
// signals: signature, parameters, type, tag, flags
39, 35, 34, 34, 0x05, //第一個自定義信號的簽名存儲在qt_meta_stringdata_Object數(shù)組中,
//索引是39,即ageChanged(int)
61, 55, 34, 34, 0x05, //第二個自定義信號的簽名存儲在qt_meta_stringdata_Object數(shù)組中,
//索引是61,即scoreChanged(int)
// slots: signature, parameters, type, tag, flags
79, 35, 34, 34, 0x0a, //第一個自定義槽函數(shù)的簽名存儲在qt_meta_stringdata_Object數(shù)組中,
//索引是79,即onAgeChanged(int)
97, 55, 34, 34, 0x0a, //第二個自定義槽函數(shù)的簽名存儲在qt_meta_stringdata_Object數(shù)組中,
//索引是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 //屬性關(guān)聯(lián)的信號編號
0,
1,
0,
// enums: name, flags, count, data
121, 0x0, 4, 54, //枚舉的定義,存儲在qt_meta_stringdata_Object中,索引是121,即Level,內(nèi)含4個枚舉常量
// enum data: key, value //枚舉數(shù)據(jù)的鍵值對
133, uint(Object::Basic), //數(shù)組索引是133,即Basic
139, uint(Object::Middle), //數(shù)組索引是139,即Middle
146, uint(Object::Advanced), //數(shù)組索引是146,即Advanced
155, uint(Object::Master), //數(shù)組索引是155,即Master
0 // eod 元數(shù)據(jù)結(jié)束標(biāo)記
};
內(nèi)省表是一個 uint 數(shù)組,分為五個部分:第一部分content,即內(nèi)容,分為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文件中實現(xiàn)了信號,創(chuàng)建了一個指向參數(shù)的指針的數(shù)組,并將指針數(shù)組傳給QMetaObject::activate函數(shù)。數(shù)組的第一個元素是返回值。本例中值是0,因為返回值是void。傳給activate函數(shù)的第三個參數(shù)是信號的索引(本例中是0)。
// SIGNAL 0,ageChanged信號的實現(xiàn)
void Object::ageChanged(int _t1)
{
void *_a[] = { 0, const_cast(reinterpret_cast(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
// SIGNAL 1 scoreChanged信號的實現(xiàn)
void Object::scoreChanged(int _t1)
{
void *_a[] = { 0, const_cast(reinterpret_cast(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
利用槽函數(shù)在qt_static_metacall 函數(shù)的索引位置來調(diào)用槽函數(shù):
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
在每一個QMetaObject對象中,槽、信號以及其它的對象可調(diào)用函數(shù)都會分配一個從0開始的索引。索引是有順序的,信號在第一位,槽在第二位,最后是其它函數(shù)。這個索引在內(nèi)部被稱為相對索引,不包含父對象的索引位。
為了實現(xiàn)包含在繼承鏈中其它函數(shù)的索引,在相對索引的基礎(chǔ)上添加一個偏移量,得到絕對索引。絕對索引是在公開API中使用的索引,由QMetaObject::indexOf(Signal, Slot, Method) 類似的函數(shù)返回。
連接機制使用以信號為索引的向量。但是在向量中,所有的槽也會占有一定空間,通常在一個對象中,槽的數(shù)量要比信號多。所以從 Qt 4.6開始,使用的是一種僅包含信號索引的新的內(nèi)部實現(xiàn)。
開始連接時,Qt所要做的第一件事是找出所需要的信號和槽的索引。Qt會去查找元對象的字符串表來找出相應(yīng)的索引。
然后,創(chuàng)建一個 QObjectPrivate::Connection 對象,將其添加到內(nèi)部的鏈表中。
由于允許多個槽連接到同一個信號,需要為每一個信號添加一個已連接的槽的列表。每一個連接都必須包含接收對象和槽的索引。在接收對象銷毀的時候,相應(yīng)的連接也能夠被自動銷毀。所以每一個接收對象都需要知道誰連接到它自己,以便能夠清理連接。
QObject對象的私有數(shù)據(jù)QObjectPrivate如下:
class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
Q_DECLARE_PUBLIC(QObject)
public:
struct ExtraData
{
ExtraData() {}
QList propertyNames;
QList 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 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 *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 > 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 sharedRefcount;
};
每一個QObject對象都有一個連接鏈表容器QObjectConnectionListVector *connectionLists:將每一個信號與一個 QObjectPrivate::Connection 的鏈表關(guān)聯(lián)起來。
QObject::connect函數(shù)的實現(xiàn)如下:
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
//在發(fā)送者對象的元對象中將信號的相對索引找到
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();
//在接受者對象的元對象中將槽函數(shù)的相對索引找到
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;
}
//檢查連接參數(shù)是否匹配
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;
//調(diào)用QMetaObjectPrivate::connect將信號與槽進(jìn)行連接
if (!QMetaObjectPrivate::connect(sender, signal_index, receiver, method_index_relative, rmeta ,type, types))
return false;
const_cast(sender)->connectNotify(signal - 1);
return true;
}
QObject::connect函數(shù)的主要功能是在接受者對象的元對象中將槽函數(shù)的相對索引找到,在接受者對象的元對象中將槽函數(shù)的相對索引找到,最后調(diào)用QMetaObjectPrivate::connect將信號與槽進(jìn)行連接。QObject及其派生類對象的元對象在創(chuàng)建時就有一個QObjectConnectionListVector連接鏈表容器,QObject::connect的作用就是將新的連接加入到信號發(fā)送者附屬的元對象的連接鏈表容器的相應(yīng)信號的連接鏈表中(一個信號可能連接多個槽函數(shù))。
每個QObject及其派生類對象都有一個QObjectConnectionListVector *connectionLists連接鏈表容器,將信號的索引作為容器的索引,將每一個信號與一個 QObjectPrivate::ConnectionList鏈表關(guān)聯(lián)起來。同時,QObjectPrivate::ConnectionList鏈表中連接的某個槽函數(shù)可能是接收者對象的槽函數(shù)鏈表中的一個。每個接收者對象的鏈表如下:
senderList 的 prev 指針是一個指針的指針。這是因為并不是真的指向上一個節(jié)點,而是指向上一個節(jié)點中的 next 指針。這個指針僅在連接銷毀時使用,并且不能向后遍歷。它允許不為第一個元素添加特殊處理。
容器中存儲的ConnectionList如下:
struct ConnectionList {
ConnectionList() : first(0), last(0) {}
Connection *first;//第一個結(jié)點
Connection *last;//最后一個結(jié)點
};
每個ConnectionList類型元素是一個雙向鏈表,保存了信號的所有連接。連接的類型Connection結(jié)構(gòu)如下:
struct Connection
{
QObject *sender;//發(fā)送者
QObject *receiver;//接受者
StaticMetaCallFunction callFunction;//調(diào)用的槽函數(shù)
// The next pointer for the singly-linked ConnectionList
Connection *nextConnectionList;
//senders linked list
Connection *next;
Connection **prev;
QBasicAtomicPointer 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函數(shù)源碼如下:
//將一個新的連接加入到信號發(fā)送者的連接鏈表容器中相應(yīng)信號的連接鏈表中,其中連接加入的連接鏈表的索引為信號的索引
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(sender);
QObject *r = const_cast(receiver);
int method_offset = rmeta ? rmeta->methodOffset() : 0;
//在元對象的元數(shù)據(jù)字符串中找到回調(diào)的函數(shù)指針qt_static_metacall
QObjectPrivate::StaticMetaCallFunction callFunction =
(rmeta && QMetaObjectPrivate::get(rmeta)->revision >= 6 && rmeta->d.extradata)
? reinterpret_cast(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)
{
//根據(jù)信號索引獲取信號的連接
const QObjectPrivate::Connection *c2 =
(*connectionLists)[signal_index].first;
int method_index_absolute = method_index + method_offset;
while (c2)
{ //如果信號的接收者相同并且槽函數(shù)相同,即相同的連接已經(jīng)存在
if (c2->receiver == receiver && c2->method() == method_index_absolute)
return false;//直接返回,
c2 = c2->nextConnectionList;//下一個信號連接
}
}
type &= Qt::UniqueConnection - 1;
}
//創(chuàng)建一個新的連接
QObjectPrivate::Connection *c = new QObjectPrivate::Connection;
//設(shè)置連接的屬性
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;//設(shè)置回調(diào)的函數(shù)指針為qt_static_metacall
QT_TRY
{ //將連接添加到發(fā)送者的連接鏈表容器中相應(yīng)的信號對應(yīng)的連接鏈表中
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發(fā)射信號時,實際調(diào)用MOC實現(xiàn)的信號函數(shù),信號函數(shù)內(nèi)部調(diào)用了QMetaObject::activate()函數(shù)。
// SIGNAL 0,ageChanged信號的實現(xiàn)
void Object::ageChanged(int _t1)
{
void *_a[] = { 0, const_cast(reinterpret_cast(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
// SIGNAL 1 scoreChanged信號的實現(xiàn)
void Object::scoreChanged(int _t1)
{
void *_a[] = { 0, const_cast(reinterpret_cast(&_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; // 如果發(fā)送的信號沒有槽連接,直接返回
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));
//獲取發(fā)送者的連接鏈表容器
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;
//從發(fā)送者的連接鏈表容器中使用信號索引作為索引,獲取相應(yīng)的連接鏈表
const QObjectPrivate::ConnectionList *list;
if (signal_index < connectionLists->count())
list = &connectionLists->at(signal_index);
else
list = &connectionLists->allsignals;
do {
//索取發(fā)送的信號的連接鏈表的第一個連接
QObjectPrivate::Connection *c = list->first;
if (!c) continue;//如果連接為空,繼續(xù)
// 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;//如果連接的接收者為空,繼續(xù)
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);
}
//獲取連接的回調(diào)函數(shù)指針
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);
//根據(jù)接收者的方法偏移,接收者等參數(shù)調(diào)用qt_static_metacall回調(diào)函數(shù)
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);
}
//根據(jù)接收者、接收者的方法索引等參數(shù)調(diào)用發(fā)送元對象的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函數(shù)內(nèi)部調(diào)用了qt_metacall函數(shù)。
int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv)
{
if (QMetaObject *mo = object->d_ptr->metaObject)
return static_cast(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函數(shù)內(nèi)部調(diào)用了qt_static_metacall函數(shù)。
槽函數(shù)最終通過qt_static_metacall函數(shù)根據(jù)參數(shù)調(diào)用相應(yīng)的槽函數(shù)。
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
在onAgeChanged(int age)槽函數(shù)內(nèi)部斷點調(diào)試。
得到的函數(shù)調(diào)用棧如下:
函數(shù)調(diào)用棧分析:
Object::qt_metacall函數(shù)內(nèi)部調(diào)用了Object::setAge函數(shù),setAge內(nèi)部調(diào)用Object::ageChanged信號函數(shù),ageChanged信號函數(shù)內(nèi)部調(diào)用了QMetaObject::activate函數(shù),activate函數(shù)內(nèi)部調(diào)用Object::qt_static_metacall函數(shù),最終qt_static_metacall函數(shù)內(nèi)部調(diào)用了槽函數(shù)onAgeChanged。
因此在本例中,當(dāng)調(diào)用ob.setProperty("age", QVariant(30));設(shè)置屬性時,觸發(fā)了QMetaProperty::Write函數(shù)的調(diào)用,進(jìn)而調(diào)用MOC實現(xiàn)的moc_Object.cpp文件中的Object::qt_metacall,qt_metacall內(nèi)部調(diào)用setAge函數(shù),setAge函數(shù)內(nèi)部發(fā)射信號ageChanged,即調(diào)用Object::ageChanged信號函數(shù),Object::ageChanged函數(shù)內(nèi)部調(diào)用了Object對象的元對象的QMetaObject::activate函數(shù),activate函數(shù)內(nèi)部調(diào)用了Object::qt_static_metacall函數(shù),最終qt_static_metacall內(nèi)部實現(xiàn)對槽函數(shù)onAgeChanged的調(diào)用。
本例中,信號和槽處于同一線程,連接類型為直接連接,因此屬于同步調(diào)用,是最簡單的調(diào)用類型。QMetaObject::activate函數(shù)內(nèi)部實際上根據(jù)Object對象的元對象中的信號連接鏈表容器查找得到信號對應(yīng)的:qt_static_metacall回調(diào)函數(shù),進(jìn)而回調(diào)的。
Object類的實現(xiàn):
#ifndef OBJECT_H
#define OBJECT_H
#include
moc_Object.cpp實現(xiàn):
#include "Object.h"
//信號的名稱
static const char signalNames[] = "valueChanged\n";
//槽的名稱
static const char slotNames[] = "onValueChanged\n";
//靜態(tài)元對象的填充
MetaObject Object::meta = { signalNames, slotNames };
//元方法調(diào)用函數(shù)的實現(xiàn),根據(jù)連接的索引回調(diào)槽函數(shù)
void Object::metacall(int idx)
{
switch (idx) {
case 0:
onValueChanged();
break;
default:
break;
};
}
//信號的實現(xiàn)
void Object::valueChanged()
{
MetaObject::active(this, 0);
}
//激活信號
void MetaObject::active(Object* sender, int idx)
{
ConnectionMapIt it;
std::pair ret;
ret = sender->connections.equal_range(idx);
for (it = ret.first; it != ret.second; ++it)
{
Connection c = (*it).second;
c.receiver->metacall(c.method);//根據(jù)索引調(diào)用元方法
}
}
Main.cpp文件:
#include
#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));
//發(fā)射一個信號進(jìn)行測試
obj1.emitSignal();
getchar();
return 0;
}
sigslot是信號槽的一個非常精煉的C++實現(xiàn),作者是Sarah Thompson,sigslot實現(xiàn)只有一個頭文件sigslot.h,跨平臺且線程安全。在WebRTC中,sigslot .h是其基礎(chǔ)的事件處理框架, 在多個模塊的消息通知,響應(yīng)處理中被使用。
sigslot庫官網(wǎng):
http://sigslot.sourceforge.net/
Sigslot使用示例如下:
#include "sigslot.h"
#include
#include
#include
#include
using namespace sigslot;
using namespace std;
class CSender
{
public:
sigslot::signal2 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函數(shù)名會和Qt中的emit宏沖突,修改方法有兩個,一是將sigslot.h的emit改成其他名字,二是在.pro文件中添加DEFINES+=QT_NO_EMIT,禁用Qt的emit宏。
Boost.Signals實現(xiàn)了signals/slots模式,信號(signals)被發(fā)射,而插槽(slots)接收該信號。
#include
#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 sig;
sig.connect(&firstSlot);
sig.connect(secondSlot());
std::cout << "Emitting a signal... ";
sig();
}
插槽函數(shù)的執(zhí)行順序是隨機的,可以使用分組參數(shù)來控制調(diào)用順序。
sig.connect(1,&firstSlot);
sig.connect(2,secondSlot());
Boost.Signals | Qt Signals 和 Slots |
---|---|
一個信號就是一個對象 | 信號只能是成員函數(shù) |
發(fā)出信號類似于函數(shù)調(diào)用 | 發(fā)出信號類似于函數(shù)調(diào)用,Qt 提供了一個 emit 關(guān)鍵字來完成這個操作 |
信號可以是全局的、局部的或者是成員對象 | 信號只能是成員函數(shù) |
任何能夠訪問到信號對象的代碼都可以發(fā)出信號 | 只有信號的擁有者才能發(fā)出信號 |
槽是任何可被調(diào)用的函數(shù)或者函數(shù)對象 | 槽是經(jīng)過特別設(shè)計的成員函數(shù) |
可以有返回值,返回值可以在多個槽中使用 | 沒有返回值 |
同步的 | 同步的或者隊列的 |
非線程安全 | 線程安全,可以跨線程使用 |
當(dāng)且僅當(dāng)槽是可追蹤的時候,槽被銷毀時,連接自動斷開 | 槽被銷毀時,連接都會自動斷開(因為所有槽都是可追蹤的) |
類型安全(編譯器檢查) | 類型安全(運行期檢查) |
參數(shù)列表必須完全一致 | 槽可以忽略信號中多余的參數(shù) |
信號、槽可以是模板 | 信號、槽不能是模板 |
C++ 直接實現(xiàn) ? | 通過由 moc 生成的元對象實現(xiàn)(moc 以及元對象系統(tǒng)都是 C++ 直接實現(xiàn)的) |
沒有內(nèi)省機制 | 可以通過內(nèi)省發(fā)現(xiàn),可以通過元對象調(diào)用,連接可以從資源文件中自動推斷出 |