Qt信號槽-原理分析(轉(zhuǎn))

一祠肥、問題

學(xué)習(xí)Qt有一段時間了,信號槽用的也是666梯皿,可是對信號槽的機制還是一知半解仇箱,總覺著不是那么得勁兒县恕,萬一哪天面試被問到了還說不清楚,那豈不是很尷尬剂桥。最近抽空研究了下Qt的信號和槽進制忠烛,結(jié)果發(fā)現(xiàn)也不是那么難嘛!不管是同步還是異步权逗,說白了都是函數(shù)回調(diào)美尸,只是回調(diào)的地方變了而已

首先,我們先看如下幾個問題斟薇,認真的思考下师坎,從以前的知識儲備中嘗試回答他們,如果說這幾個問題你都很清楚堪滨,那么恭喜你胯陋,你不適合看這篇文章。

  1. moc預(yù)編譯在干嘛
  2. signals和slots關(guān)鍵字產(chǎn)生的理由
  3. 信號槽連接方式有什么區(qū)別
  4. 信號和槽函數(shù)有什么區(qū)別
  5. connect到底干了什么
  6. 信號觸發(fā)原理

下面我們就分模塊來講述下Qt的信號槽袱箱,首先分析下Moc他到底干了什么惶岭,如果沒有他信號槽還能行嗎?接著我們在來分析下最常用的connect函數(shù)犯眠,最后在看下信號執(zhí)行后是怎么觸發(fā)槽函數(shù)的?

二症革、Moc

qt中的moc 全稱是 Meta-Object Compiler筐咧,也就是“元對象編譯器”,當(dāng)我們編譯C++
文件時噪矛,如果類聲明中包含了宏Q_OBJECT量蕊,則會生成另外一個C++源文件,也就是我們經(jīng)惩Оぃ看到的moc_xxx.cpp文件残炮,執(zhí)行流程可能會像這樣。

image

Q_OBJECT是一個非常重要的宏缩滨,他是Qt實現(xiàn)元編譯系統(tǒng)的一個關(guān)鍵宏势就,這個宏展開后,里邊包含了很多Qt幫助我們寫的代碼脉漏,包括了變量定義苞冯、函數(shù)聲明等等,下邊是一個測試例子侧巨,是我用moc命令生成的一個moc文件舅锄。

分析下面這個幾個變量和函數(shù),將有助于我們更好的理解元編譯系統(tǒng)
1司忱、變量

- static const qt_meta_stringdata_completerTst_t qt_meta_stringdata_completerTst:存儲函數(shù)列表
- static const uint qt_meta_data_completerTst:類文件描述

2皇忿、Q_OBJECT展開后的函數(shù)聲明
以下5個函數(shù)都是使用Q_OBJECT宏自動生成的

- void xxx::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
- const QMetaObject xxx::staticMetaObject
- const QMetaObject *xxx::metaObject()
- void *xxx::qt_metacast(const char *_clname)
- int xxx::qt_metacall(QMetaObject::Call _c, int _id, void **_a)

為了更好的理解這5個函數(shù)畴蹭,我們首先需要引入一個Qt元對象,也就是QMetaObject鳍烁,這個類里邊存儲了父類的源對象叨襟、我們當(dāng)前類描述、函數(shù)描述和qt_static_metacall函數(shù)地址老翘。

a芹啥、qt_static_metacall
很重要,根據(jù)函數(shù)索引進行調(diào)用槽函數(shù)铺峭,這塊需要注意一個很大的細節(jié)問題墓怀,這個回調(diào)中,信號和槽都是可以被回調(diào)的,自動生成代碼如下

 if (_c == QMetaObject::InvokeMetaMethod) {
    completerTst *_t = static_cast<completerTst *>(_o);
    Q_UNUSED(_t)
    switch (_id) {
    case 0: _t->lanuch(); break;
    case 1: _t->test(); break;
    default: ;
    }
}

lanch是一個信號聲明陶舞,但是卻也可以被回調(diào)搀玖,這也間接的說明了一個問題,信號是可以當(dāng)槽函數(shù)一樣使用的钓账。

b、staticMetaObject
構(gòu)造一個QMetaObject對象絮宁,傳入當(dāng)前moc文件的動態(tài)信息

c梆暮、metaObject
返回當(dāng)前QMetaObject,一般而言绍昂,虛函數(shù) metaObject() 僅返回類的 staticMetaObject對象啦粹。

d、qt_metacast
是否可以進行類型轉(zhuǎn)換窘游,被QObject::inherits直接調(diào)用唠椭,用于判斷是否是繼承自某個類。判斷時忍饰,需要傳入父類的字符串名稱贪嫂。

e、qt_metacall
調(diào)用函數(shù)回調(diào)艾蓝,內(nèi)部還是調(diào)用了qt_static_metacall函數(shù)力崇,該函數(shù)被異步處理信號時調(diào)用,或者Qt規(guī)定的有一定格式的槽函數(shù)(on_xxx_clicked())觸發(fā)赢织,異步調(diào)用代碼如下所示

void QMetaCallEvent::placeMetaCall(QObject *object)
{
    if (slotObj_) {
        slotObj_->call(object, args_);
    } else if (callFunction_ && method_offset_ <= object->metaObject()->methodOffset()) {
        callFunction_(object, QMetaObject::InvokeMetaMethod, method_relative_, args_);
    } else {
        QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, method_offset_ + method_relative_, args_);
    }
}

3餐曹、自定義信號
下面這個函數(shù)是我們自己定義的一個信號,moc命令幫我們生成了一個信號函數(shù)實現(xiàn)敌厘,由此可見台猴,信號其實也是一個函數(shù),只是我們只管寫信號聲明,而信號實現(xiàn)Qt會幫助我們自動生成饱狂;槽函數(shù)我們不僅僅需要寫函數(shù)聲明曹步,函數(shù)實現(xiàn)也必須自己寫。

- void xxx::lanuch():自定義信號

這里Qt怎么會知道我們定義了信號呢休讳?這個也是文章開頭我們提出的第2個問題讲婚。答案就是signals,當(dāng)Qt發(fā)現(xiàn)這個標志后俊柔,默認我們是在定義信號筹麸,它則幫助我們生產(chǎn)了信號的實現(xiàn)體,slots標志是同樣的道理雏婶,Qt元系統(tǒng)用來解析槽函數(shù)時用的物赶。
我們在C++文件中添加了編譯器不認識的關(guān)鍵字,這個時候編譯為什么會沒有報錯呢留晚?
因為我們使用了define宏定義酵紫,定義了這個關(guān)鍵字

# define signals

三、connect

上面我們分析了moc系統(tǒng)幫助我們生成的moc文件错维,他是實現(xiàn)信號槽的基礎(chǔ)奖地,也是關(guān)鍵所在,這一小節(jié)我們來了解下我們平時使用最多的connect函數(shù)赋焕,看看他到底干了些什么参歹。

當(dāng)我們執(zhí)行connect時,實際上他可能像這樣的執(zhí)行流程


image

從這張圖上我們可以看到隆判,connect干的事情并不多犬庇,好像就是構(gòu)造了一個Connection對象,然后存儲在了發(fā)送者的內(nèi)存中蜜氨,具體存儲了哪些內(nèi)容,可以看下面代碼捎泻,這是我從Qt源碼中沾出來的部分代碼飒炎。

QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection);
c->sender = s;   //發(fā)送者
c->signal_index = signal_index;//信號索引
c->receiver = r;//接收者
c->method_relative = method_index;//槽函數(shù)索引
c->method_offset = method_offset;//槽函數(shù)偏移 主要是區(qū)別于多個信號
c->connectionType = type;//連接類型
c->isSlotObject = false;//是否是槽對象 默認是true
c->argumentTypes.store(types);//參數(shù)類型
c->nextConnectionList = 0;//指向下個連接對象
c->callFunction = callFunction;//靜態(tài)回調(diào)函數(shù),也就是qt_static_metacall

QObjectPrivate::get(s)->addConnection(signal_index, c.data());

上述代碼中我只把關(guān)鍵代碼貼出來了笆豁,Qt的源碼實現(xiàn)有很多異常判斷我們這里不需要考慮

發(fā)送者內(nèi)存中存儲結(jié)構(gòu)

class QObjectConnectionListVector : public QVector<QObjectPrivate::ConnectionList>

信號槽連接后在內(nèi)存中已QObjectConnectionListVector對象存儲郎汪,這是一個數(shù)組,Qt巧妙的借用了數(shù)組快速訪問指定元素的方式闯狱,把信號所在的索引作為下標來索引他連接的Connection對象煞赢,眾所周知一個信號可以被多個槽連接,那么我們的的數(shù)組自然而然也就存儲了一個鏈表哄孤,用于方便的插入和移除照筑,也就是CommectionList對象。

四、信號觸發(fā)

一切準備就緒凝危,接下來我們看看信號觸發(fā)后波俄,是怎么關(guān)聯(lián)到槽函數(shù)的

Qt為我們提供了5種類型的連接方式,如下

  • Qt::AutoConnection 自動連接蛾默,根據(jù)sender和receiver是否在一個線程里來決定使用哪種連接方式懦铺,同一個線程使用直連,否則使用隊列連接
  • Qt::DirectConnection 直連
  • Qt::QueuedConnection 隊列連接
  • Qt::BlockingQueuedConnection 阻塞隊列連接支鸡,顧名思義冬念,雖然是跨線程的,但是還是希望槽執(zhí)行完之后牧挣,才能執(zhí)行信號的下一步代碼
  • Qt::UniqueConnection 唯一連接

一般情況下急前,我們都使用默認的連接方式,除非一些特殊的需求浸踩,我們才會主動指定連接方式叔汁。當(dāng)我們執(zhí)行信號時,函數(shù)的調(diào)用關(guān)系可能會像下面這樣


image
emit testSignal(); 執(zhí)行信號

信號觸發(fā)后检碗,就相當(dāng)于調(diào)用QMetaObject::activate函數(shù)据块,信號的函數(shù)體是moc幫助我們自動生成的。

下面我們來分析下幾個關(guān)鍵的連接方式折剃,他們都是怎么工作的

1另假、直連

對于大多數(shù)的開發(fā)工作來說,我們可能都是在同一個線程里進行的怕犁,因此直連也是我們使用連接方式最多的一種边篮,直連說白了就是函數(shù)回調(diào)。還記得我們第三小節(jié)講的connect嗎奏甫,他構(gòu)造了一個Connection對象戈轿,存儲在了發(fā)送者的內(nèi)存中,直連其實就是調(diào)用了我們之前存儲在Connection中的函數(shù)地址阵子。

如下圖所示思杯,是一個直連時,回調(diào)到槽函數(shù)中的一個內(nèi)存堆棧挠进。


image

講connect函數(shù)時色乾,我們分析到,該函數(shù)內(nèi)部其實就是構(gòu)造了一個Connection對象存儲在了發(fā)送者內(nèi)存中领突,其中有一個變量是isSlotObject暖璧,默認是true。當(dāng)我們使用connect連接信號槽時君旦,該參數(shù)默認就是一個true澎办,但是Qt還提供了了另外一種規(guī)定格式的槽函數(shù)嘲碱,此時isSlotObject就是false啦。

如下圖所示浮驳,這是一個使用Qt規(guī)定格式的槽函數(shù)悍汛。格式:on_objectname_clicked();。


image

2至会、隊列連接

connect連接信號槽時离咐,我們使用Qt::QueuedConnection作為連接類型時,槽函數(shù)的執(zhí)行是通過拋出QMetaCallEvent事件奉件,經(jīng)過Qt的事件循環(huán)達到異步的效果

如下圖所示宵蛀,是使用隊列連接時,槽函數(shù)的回調(diào)堆棧


image

下面代碼摘自Qt源碼县貌,queued_activate函數(shù)即是處理隊列請求的函數(shù)术陶,當(dāng)我們使用自動連接并且接受者和發(fā)送者不在一個線程時使用隊列連接;或者當(dāng)我們指定連接方式為隊列時使用隊列連接煤痕。

// 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_index, c, argv ? argv : empty_argv, locker);
    continue;

五梧宫、總結(jié)

講了這么多,Qt信號槽的實現(xiàn)原理其實就是函數(shù)回調(diào)摆碉,不同的是直連直接回調(diào)塘匣、隊列連接使用Qt的事件循環(huán)隔離了一次達到異步,最終還是使用函數(shù)回調(diào)

  1. moc預(yù)編譯幫助我們構(gòu)建了信號槽回調(diào)的開頭(信號函數(shù)體)和結(jié)尾(qt_static_metacall回調(diào)函數(shù))巷帝,中間的回調(diào)過程Qt已經(jīng)在QOjbect函數(shù)中實現(xiàn)
  2. signals和slots就是為了方便moc解析我們的C++文件忌卤,從中解析出信號和槽
  3. 信號槽總共有5種連接方式,前四種是互斥的楞泼,可以表示為異步和同步驰徊。第五種唯一連接時配合前4種方式使用的
  4. 信號和槽本質(zhì)上是一樣的,但是對于使用者來說堕阔,信號只需要聲明棍厂,moc幫你實現(xiàn),槽函數(shù)聲明和實現(xiàn)都需要自己寫
  5. connect方法就是把發(fā)送者超陆、信號牺弹、接受者和槽存儲起來,供后續(xù)執(zhí)行信號時查找
  6. 信號觸發(fā)就是一系列函數(shù)回調(diào)

六侥猬、推薦閱讀

信號槽5種連接方式: 線程例驹,connect的第五個參數(shù)

moc文件解析:Qt高級——Qt信號槽機制源碼解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捐韩,一起剝皮案震驚了整個濱河市退唠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荤胁,老刑警劉巖瞧预,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡垢油,警方通過查閱死者的電腦和手機盆驹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滩愁,“玉大人躯喇,你說我怎么就攤上這事∠跬鳎” “怎么了廉丽?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長妻味。 經(jīng)常有香客問我正压,道長,這世上最難降的妖魔是什么责球? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任焦履,我火速辦了婚禮,結(jié)果婚禮上雏逾,老公的妹妹穿的比我還像新娘嘉裤。我一直安慰自己,他們只是感情好校套,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布价脾。 她就那樣靜靜地躺著,像睡著了一般笛匙。 火紅的嫁衣襯著肌膚如雪侨把。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天妹孙,我揣著相機與錄音秋柄,去河邊找鬼。 笑死蠢正,一個胖子當(dāng)著我的面吹牛骇笔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嚣崭,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼笨触,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了雹舀?” 一聲冷哼從身側(cè)響起芦劣,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎说榆,沒想到半個月后虚吟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寸认,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年串慰,在試婚紗的時候發(fā)現(xiàn)自己被綠了偏塞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡邦鲫,死狀恐怖灸叼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情庆捺,我是刑警寧澤怜姿,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站疼燥,受9級特大地震影響沧卢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜醉者,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一但狭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撬即,春花似錦立磁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至粒竖,卻和暖如春颅崩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蕊苗。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工沿后, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人朽砰。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓尖滚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瞧柔。 傳聞我的和親對象是個殘疾皇子漆弄,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354