深入理解QT的SIGNAL\SLOT機制(三):QObject::connect函數

本章我們來看connect函數是如何綁定信號和槽的
我們在MyWidget.cpp代碼中的connect左邊打斷點進行DEBUG:按F11,進入connect函數。這裏寫圖片描述
下面是QObject::connect函數的代碼:
這裏寫圖片描述
2663~2670行:判斷傳入的參數是否爲空,爲空返回。
2673~2674行:判斷信號是不是信號。大家看右邊的DEBUG,sender是MyWidget對象,signal是“2mysignal()”,method是“1myslot()”。那麼,如何判斷信號是不是信號呢?
然後我們看check_signal_macro函數:
這裏寫圖片描述
這個函數最核心的就是2266行,extract_code函數,member就是signal,指向的是字符串“2mysignal()”,OK,來看這個函數的實現:

static int extract_code(const char *member)
{
      return (((int)(*member) - '0') & 0x3);
}

對member解引用之後是‘2’,‘2’-‘0’轉int之後爲2,2(0x10)&3(0x11)=2(0x10),並返回。
我們返回check_signal_macro函數,繼續看:2267~2277是在判斷信號是不是信號,怎麼判斷呢?來看QSIGNAL_CODE和QSIGNAL_SLOT兩個宏定義:

#define QSLOT_CODE    1
#define QSIGNAL_CODE  2

現在明白了,信號是在函數名(其實是字符串)之間增加‘2’,槽函數是在函數名(其實是字符串)前面增加‘1’,這就解釋了上文提到的“2mysignal()”和“1myslot()”。
我們繼續來看connet函數。
2675行獲取MyWidget的QMetaObject對象指針。按F11進入,來看moc文件:

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

這裏運行的結果是將staticMetaObject地址返回,所以第二章重點講解的是staticQMetaObject對象。
我們繼續來看connect函數。
2677行:++signal,所以signal指向的是“mysignal()”字符串。
2680行:調用decodeMethodSignature函數獲取singal的名稱,該函數中基本都是字符串操作,不再贅述,但要注意參數類型信息放在了QArgumentTypeArray signalTypes中。
2681行:獲取信號的Index。
這裏寫圖片描述
在indexOfSignalRelative函數中,725行:獲取method的偏移量(SIGNAL本質也是method),我們來看indexOfMethodRelative函數:
這裏寫圖片描述
594行:一開始是一個for循環,遍歷QMetaObject對象。
QMetaObject結構體的superdata域指向父類的QMetaObject,所以就形成了一個單鏈表(QMetaObject結構體見第二章)。
597行:Priv(m->d.data),data是uint *類型,priv是將其強制轉換爲QMetaObjectPrivate類型(這就是第二章中爲什麼藥按照QMetaObjectPrivate的格式解析qt_meta_data_mywidget[]數組的原因),priv函數如下:

static inline const QMetaObjectPrivate *priv(const uint* data)
{ return reinterpret_cast<const QMetaObjectPrivate*>(data); }

Priv(m->d.data)->signalCount是信號的個數(qt_meta_data_mywidget[]數組對照QMetaOBjectPrivate格式,去找signalCount,就是1)。
598行:End是0,說白了就是計算出signal的數量,進行遍歷。
601~609行:methodData這裏是14(還記得第二章中的signal只有1個,該signal信息在14位置麼?)爲什麼要加 5*i?是因爲moc文件中的qt_meta_data_mywidget[]數組爲每一個信號或槽存儲的信息是5個int值,所以這裏要加 5*i。,methodMatch是檢查匹配,主要檢查函數名、參數、類型是否匹配,不在贅述。匹配成功,返回i(我們這裏只有一個信號,所以i是0),否則-1。
我們返回indexOfSignalRelative,接下來是做一次安全檢查,將i值返回。
我們在回到最初的connect函數中:
2683~2701行:做signal_index的安全檢查,
2702~2771行:是做slot的檢查、獲取函數名、獲取函數index、檢查是否匹配,和signal幾乎一樣。
2772行:最核心的部分,重點來了,QMetaObjectPrivate::connect函數
這裏寫圖片描述
3279~3280行:callFunction指向了receiver的QMetaObject的callFunction(第二章中提到,moc文件中的qt_static_metacall函數的地址被傳遞給了staticMetaObject對象的static_metacall域,這裏又將static_metacall域的內容傳遞給callFunction,也就是callFunction指向了moc文件中的qt_static_metacall函數,這點要搞清楚,因爲發射信號之後調用的就是該函數)。
3285~3300行:只有UniqueConnection類型纔會進入,我們的type是0,也就是AutoConnection。
這裏寫圖片描述
3302行:new一個connection對象,並初始化QScopedPointer對象。
3303~3312行:設置conncection的各個屬性,這裏的3312行將callFunction傳遞給了c的callFunction(這個真的很重要,因爲涉及到發射信號之後要調用那個函數)
3314行:get(s)是拿到sender的實體類,再調用addConnection函數,將c加入到sender的鏈表結構中。
這裏寫圖片描述
386~389行:如果sender的實體類中的connectionLists是null,就new出來。大小不夠就擴容。
391行:通過下標找到對應的鏈表,時間複雜度O(1)。我們這裏只有一個信號,所以下表是0。
392~397行:修改first、last指針。
400~405行:修改senders鏈表,該鏈表使用與刪除connection的。
到此爲止connection結構就被插入到了connectionLists中。
我們回到QMetaObjectPrivate::connect中,繼續來看:
3321行:c.take()將只能指針的裸指針返回,保證出作用域,c不會被析構。
再次回到QObject::connect函數中,我們發現其實真個連接過程已經結束。

connection的結構在下一章,莫慌!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章