Qt::AutoConnection的signal-slot連接是在運(yùn)行時(shí)確定連接類型,多線程安全的。
Qt中的關(guān)鍵字:signals 其實(shí)就是public;而slots則什么都沒有。
Signals 和 Slots 用于對象間的通信(communication between objects)。這種機(jī)制是Qt區(qū)別于其他框架的主要特點(diǎn)。這種機(jī)制是靠Qt的meta-object system實(shí)現(xiàn)的。
介紹
很多框架使用callback技術(shù)(MFC,CVI等)。一個(gè) callback 其實(shí)就是一個(gè)函數(shù)指針,但是Qt認(rèn)為callback并不直觀,而且在callback參數(shù)上容易出問題。
信號與槽
在Qt中使用另一種方案:信號與槽。信號在特定事件發(fā)生時(shí)被發(fā)射。Qt widgets有很多預(yù)定義的信號。我們可以子類化widgets來添加自己的信號。槽函數(shù)在所連接信號發(fā)射后被調(diào)用,作為事件的響應(yīng)。? ? Qt widgets有很多預(yù)定義的槽。實(shí)際中我們經(jīng)常子類化widget來添加自己的槽函數(shù),來響應(yīng)感興趣的信號。
信號與槽機(jī)制是類型安全的:信號的簽名必須和槽一致(實(shí)際槽的簽名可以比信號更短,忽略部分參數(shù))。由于簽名兼容,編譯器可以幫助我們檢查類型是否匹配。基于字符串的 SIGNAL 和 SLOT 語法可以在運(yùn)行階段檢查類型匹配。
所有繼承自QObject的類都可以包含信號和槽。槽函數(shù)既可以用來接收信號,也可以當(dāng)做普通函數(shù)使用。
信號和槽可以是一對多、多對一。也可以把一個(gè)信號連接到另一個(gè)信號(這樣第一個(gè)信號發(fā)射后會接著發(fā)射第二個(gè)信號)。callback技術(shù)(即函數(shù)指針)只能是一對一。
信號/槽非常類似于C#中事件(event)的發(fā)布和訂閱。
信號
信號是 public 類型的,可以從任何地方發(fā)射。但是推薦在定義信號的類內(nèi)部發(fā)射(signals are public access functions and can be emitted from any where, but we recommend to only emit them from the class that defines the signals and its subclasses)。
Qt中的關(guān)鍵字:signals 其實(shí)就是public;而slots則什么都沒有。
(信號非常類似C#中的事件event,可以被訂閱)
當(dāng)信號發(fā)射時(shí),連接的槽函數(shù)通常立即執(zhí)行(direct connection),就像普通的函數(shù)調(diào)用。此時(shí)信號和槽機(jī)制與GUI的事件循環(huán)是獨(dú)立的。emit 之后的代碼在所有槽函數(shù)返回后才會被執(zhí)行(正常,此時(shí)的信號-槽,就是函數(shù)指針-函數(shù))。這和 queued connections 不同,后者是立即執(zhí)行 emit 之后的代碼,而槽函數(shù)在晚些時(shí)候執(zhí)行。
如果多個(gè)槽連接到一個(gè)信號,當(dāng)信號發(fā)射時(shí),槽函數(shù)的執(zhí)行順序就是連接的順序。
信號由moc自動生成,不可以在.cpp文件中實(shí)現(xiàn),而且也不能有返回類型(即只能使用void)。
槽
槽函數(shù)就是普通的C++函數(shù)。唯一特別的地方是:可以連接到信號。
槽函數(shù)可以通過 信號-槽 的連接而被任何組件調(diào)用,這與槽的訪問權(quán)限無關(guān)。這意味著private 的槽也可以被信號調(diào)用。(However, as slots, they can be invoked by any component, regardless of its access level, via a signal-slot connection.This means that a signal emitted from an instance of an arbitrary class can cause a private slot to be invoked in an instance of an unrelated class)
槽函數(shù)是普通的成員函數(shù)。也可以定義成virtual類型,非常有用。
信號-槽機(jī)制比callback機(jī)制的速度略慢,這是增加了靈活性的代價(jià),但是差異并不大。通常,發(fā)射一個(gè)信號來調(diào)用槽函數(shù),通常比直接調(diào)用函數(shù)慢10倍(10 times slower tha calling the receivers directly),時(shí)間主要用來確定接收對象,來安全的遍歷連接(例如檢查后續(xù)的信號接收者沒有被銷毀),來封送參數(shù)。
信號-槽機(jī)制消耗的時(shí)間比 new 和delete所消耗的時(shí)間更短。考慮到它的靈活性,這些實(shí)現(xiàn)消耗是值得的。
例子
#include <QObject>
? class Counter : public QObject
? {
? ? ? Q_OBJECT
? public:
? ? ? Counter() { m_value = 0; }
? ? ? int value() const { return m_value; }
? public slots:
? ? ? void setValue(int value);
? signals:
? ? ? void valueChanged(int newValue);
? private:
? ? ? int m_value;
? };
包含信號和槽的類必須滿足兩個(gè)條件:
1. 在聲明的最頂部使用 Q_OBJECT;
2.直接或間接繼承自QObject。
void Counter::setValue(int value)
? {
? ? ? if (value != m_value) {
? ? ? ? ? m_value = value;
? ? ? ? ? emit valueChanged(value);
? ? ? }
? }
Counter a, b;? ? ? QObject::connect(&a, &Counter::valueChanged,? ? ? ? ? ? ? ? ? ? ? &b, &Counter::setValue);?
a.setValue(12);? ? // a.value() == 12, b.value() == 12
? ? b.setValue(48);? ? // a.value() == 12, b.value() == 48
信號和槽的連接類型
Qt::AutoConnection
默認(rèn)連接方式。當(dāng)receiver在于發(fā)射信號的線程里時(shí)(線程親和性),使用的是Qt::DirectConnection. 多線程時(shí)則使用Qt::QueuedConnnection。連接類型在信號發(fā)射時(shí)確定(運(yùn)行時(shí),而非編譯時(shí))。
Qt::DirectConnection
信號發(fā)射時(shí)立即調(diào)用槽函數(shù)。槽在信號發(fā)射的線程里執(zhí)行。
Qt::QueuedConnnection
當(dāng)控制權(quán)回到receiver所在線程時(shí)才執(zhí)行槽函數(shù)。槽函數(shù)在receiver的線程里執(zhí)行。
Qt::BlockingQueuedConnection
和QueuedConnection類似。但是發(fā)射信號的線程會阻塞,直到槽函數(shù)返回。當(dāng)receiver和sender在同一個(gè)線程里時(shí),不可以使用該方式,否則會發(fā)生死鎖。
Qt::UniqueConnection
該類型可以和上面的類型配合,用或“|”處理即可。當(dāng)連接已經(jīng)存在時(shí),再次連接會失敗。其實(shí)就是為了保證連接的唯一性。
注意:當(dāng)使用QueuedConnection時(shí),參數(shù)類型必須是 Qt 的 meta-object system 知道的類型,因?yàn)镼t要拷貝參數(shù)。可以connect()前調(diào)用qRegisterMetaType()來注冊數(shù)據(jù)類型。
關(guān)于信號和槽在多線程中的使用,參考“QObjects和多線程”。
參考Qt官方文檔:"Signals & Slots".