1. 程式人生 > 其它 >Qt5 訊號-槽內容總結

Qt5 訊號-槽內容總結

技術標籤:Qtc++qtqt5

文章目錄

訊號與槽 signals & slots

訊號和槽(signals and slots)用於物件之間的通訊。訊號和槽機制是Qt的核心特性,可能也是與其他框架提供的特性最與眾不同的部分。Qt的元物件系統(meta-object system)使訊號和插槽的實現成為可能。

簡介

在GUI程式設計中,當我們更改一個小部件(widget)時,我們通常希望通知(notified)另一個小部件(widget)。更一般地說,我們希望任何型別的物件能夠相互通訊。例如,如果使用者單擊了Close按鈕,我們可能希望呼叫視窗的Close()

函式。

其他庫使用回撥(callbacks)來實現這種通訊。回撥函式是一個指向函式的指標,所以如果你想要一個處理函式通知你一些事件,你可以將一個指向另一個函式(回撥函式)的指標傳遞給處理函式。處理函式然後在適當的時候呼叫回撥函式。雖然使用這種方法的成功框架確實存在,但回撥可能不太直觀,而且在確保回撥引數的型別正確性(type-correctness)方面可能會遇到問題。

訊號與槽

在Qt中,我們有一個回撥技術的替代方案:訊號和槽。

當特定事件發生時發出(emit)訊號。Qt的小部件(widgets)有許多預定義的訊號,但是我們總是可以子類化(繼承)小部件,給它們新增我們自己的訊號。槽(slot)是響應特定訊號而呼叫的函式。Qt的小部件有許多預定義的槽,但是通常的做法是子類化小部件並新增自己的槽,這樣您就可以處理感興趣的訊號。

訊號和槽機制是型別安全(type-safe)的

訊號的簽名(signature)必須與接收槽的簽名匹配。 (事實上,槽的簽名可能比它接收到的訊號短,因為它可以忽略額外的引數。)由於簽名是相容(compatible)的,所以在使用基於函式指標(function pointer-based)的語法時,編譯器可以幫助我們檢測(detect)型別不匹配。基於字串的訊號(SIGNAL)和槽(SLOT)語法將在執行時(runtime)檢測型別不匹配。(注:優先使用基於函式指標的語法)訊號和插槽是鬆散耦合的: 發出訊號的類既不知道也不關心哪個槽接收訊號。 Qt的訊號和槽機制確保,如果你將一個訊號連線到槽,槽將在 恰當的時間 用訊號的引數呼叫。訊號和槽可以接受任何型別的任意數量的引數。它們是完全型別安全(type-safe)的。

所有從QObject或它的一個子類(例如,QWidget)繼承的類都可以包含訊號和槽。當物件以一種其他物件可能感興趣的方式改變其狀態時,就會發出訊號。這就是物件通訊所做的一切。它不知道或不關心是否有任何東西正在接收它發出的訊號。這是真正的資訊封裝(information encapsulation),並確保物件可以作為軟體元件使用。

槽可以用來接收訊號,但它們也是普通的成員函式(member functions)。就像一個物件不知道是否有任何東西接收到它的訊號一樣,槽也不知道是否有任何訊號連線到它。這確保了Qt可以建立真正獨立的元件,可以:

  1. 將任意數量的訊號連線到單個槽
  2. 將任意數量的訊號連線到任意數量的槽。
  3. 將一個訊號直接連線到另一個訊號。(這將在第一個訊號發出時立即發出第二個訊號。參考下面的例子)
class MyWidget : public QWidget
{
    Q_OBJECT
public:
    MyWidget();

signals:
    void buttonClicked();

private:
    QPushButton *myButton;
};

MyWidget::MyWidget()
{
    myButton = new QPushButton(this);
    connect(myButton, SIGNAL(clicked()),this, SIGNAL(buttonClicked()));
}

在這個例子中,MyWidget的建構函式中轉發了一個私有成員變數的訊號, 並使它在MyWidget中有了一個新的名稱。

訊號和插槽一起構成了一個強大的元件程式設計機制。

訊號 Signal

當物件的內部狀態(internal state)以物件的客戶端(client)或所有者(owner)感興趣的某種方式發生變化時,物件就會發出訊號。訊號是public函式,可以從任何地方發出,但我們建議只從定義訊號及其子類的類發出訊號。

當訊號發出時,連線到它的插槽通常立即執行,就像普通的函式呼叫一樣。當這種情況發生時,訊號和槽機制完全獨立於任何GUI事件迴圈(event loop)。一旦所有的槽都返回,emit語句之後的程式碼就會執行。當使用佇列(queued)連線時,情況略有不同; 在這種情況下,emit關鍵字後面的程式碼將立即繼續,插槽將稍後執行。

如果多個槽連線到一個訊號,當訊號發出時,槽的呼叫順序將按照它們連線的順序依次執行。訊號是由moc自動生成的,不能在.cpp檔案中實現。它們永遠不能有返回型別(即使用void)。如果成功地將訊號連線到槽,則該函式返回一個QMetaObject::Connection,它表示連線的控制代碼。如果連線控制代碼不能建立連線,那麼它將是無效的,例如,如果QObject不能驗證訊號或方法的存在,或者它們的簽名(signature)不相容。您可以通過將其轉換為bool型別來檢查控制代碼是否有效。

關於引數的注意事項: 我們的經驗表明,如果訊號和槽不使用特殊型別,它們將更易於重用。如果QScrollBar::valueChanged()使用一種特殊型別,比如假設的QScrollBar::Range,那麼它只能連線到專門為QScrollBar設計的插槽。將不同的輸入部件連線在一起是不可能的。

預設情況下,你建立的每一個連線都會發出一個訊號; 重複連線會發出兩個訊號。你可以通過一個disconnect()呼叫斷開所有這些連線。

槽 Slots

當發出連線到插槽的訊號時,槽將被呼叫。slot是普通的c++函式,可以以正常的方式呼叫; 它們唯一的特點是可以連線訊號。

因為槽是普通的成員函式,所以當直接呼叫時,它們遵循普通的c++規則。然而,作為槽,它們可以被任何元件呼叫,而不管其訪問級別,通過訊號-槽連線。這意味著從任意類的例項發出的訊號可能導致在不相關類的例項中呼叫私有槽。

還可以將插槽定義為虛擬的,我們發現這在實際使用過程中非常有用。

與回撥相比,訊號和槽稍微慢一些,因為它們提供了更大的靈活性,儘管實際應用程式的差異並不大。一般來說,當槽函式為非虛擬函式時,發射一個連線到一些槽的訊號,大約比直接呼叫接收函式慢十倍。這是定位連線物件、安全遍歷所有連線(即檢查在傳送過程中後續接收方是否被銷燬)以及以通用方式編組任何引數所需的額外開銷。雖然十倍非虛擬函式呼叫可能聽起來很多,但它的開銷比任何new或delete操作都要小得多。一旦在後臺執行需要new或delete的string、vector或list操作,訊號和槽的開銷只佔整個函式呼叫開銷的很小一部分。當你在槽中執行系統呼叫(system call)時,情況也是一樣的或間接呼叫十多個函式。

訊號和槽機制帶來的的簡單性和靈活性是非常值得的,使用者甚至不會注意到這些開銷。

請注意,當與基於qt的應用程式一起編譯時,定義了稱為訊號或槽的變數的其他庫可能會導致編譯器警告和錯誤。要解決這個問題,#undef出錯的前處理器符號。

QObject::connect() 函式

在 Qt 5 中,QObject::connect()有五個過載:

QMetaObject::Connection connect(const QObject *, const char *,
                                const QObject *, const char *,
                                Qt::ConnectionType);

QMetaObject::Connection connect(const QObject *, const QMetaMethod &,
                                const QObject *, const QMetaMethod &,
                                Qt::ConnectionType);

QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
                                const QObject *, PointerToMemberFunction,
                                Qt::ConnectionType)

QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
                                Functor);

QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
                                const QObject *, Functor ,
                                Qt::ConnectionType);
// !!! Qt 5
connect(sender,   signal,receiver, slot);

這是我們最常用的形式。connect()一般會使用前面四個引數,第一個是發出訊號的物件(QObject及其子類),第二個是傳送物件發出的訊號,第三個是接收訊號的物件(QObject及其子類),第四個是接收物件在接收到訊號之後所需要呼叫的函式。也就是說,當 sender 發出了 signal 訊號之後,會自動呼叫 receiver 的 slot 函式。

ConnectionType 引數說明

enum ConnectionType
{
    AutoConnection,
    DirectConnection,
    QueuedConnection,
    BlockingQueuedConnection,
    UniqueConnection =  0x80
};

這個enum描述了signal和slot之間的連線型別。具體來說,它確定特定的訊號是立即傳送到槽,還是排隊等待稍後的時間傳送。

  • Qt::AutoConnection (預設的)如果receiver存在與訊號發射的執行緒,Qt::DirectConnection將被使用。其他情況Qt::QueuedConnection將被使用。連線型別是在訊號發出時確定的。
  • Qt::DirectConnection 訊號發射後槽將被立即呼叫。槽將執行在訊號發射的執行緒。
  • Qt::QueuedConnection 當控制權返回到接收方執行緒的事件迴圈(event loop)時將呼叫槽。槽在接收方的執行緒中執行。
  • Qt::BlockingQueuedConnectionQt::QueuedConnection類似,但是訊號執行緒將阻塞到槽函式返回。因此接受方不能存活在於訊號發射的執行緒。如果存在,則將會導致死鎖。
  • Qt::UniqueConnection 這是一個標記,可以使用位操作(OR)與上述任何一種連線型別結合使用。當Qt::UniqueConnection被設定,如果連線已經存在(同一對物件的相同訊號已經連線到相同的槽),再次使用QObject::connect()將會失敗。連線將返回一個無效的QMetaObject::Connection

注意:

  1. Qt::UniqueConnections 不適用於的 lambdas,非成員函式和functor;它們只適用於連線到成員函式。
  2. 對於排隊(queued)連線,引數必須是Qt元物件系統(meta-object system)已知的型別,因為Qt需要複製引數,將它們儲存在後臺的事件中。如果嘗試使用佇列連線並獲取錯誤訊息:
QObject::connect: Cannot queue arguments of type 'MyType'
(Make sure 'MyType' is registered using qRegisterMetaType().)

在建立連線前,呼叫 qRegisterMetaType() 註冊這一資料型別,在多執行緒中使用singals 和 slots 的細節資訊參考Threads-and-Qobjects

字串風格的 connect()

QMetaObject::Connection connect(const QObject *, const char *,
                                const QObject *, const char *,
                                Qt::ConnectionType);

sender 型別是const QObject *,signal 的型別是const char *,receiver 型別是const QObject *,slot 型別是const char *。這個函式將 signal 和 slot 作為字串處理。例子:

    QLabel *label = new QLabel;
    QScrollBar *scrollBar = new QScrollBar;
    QObject::connect(scrollBar, SIGNAL(valueChanged(int)),label,  SLOT(setNum(int)));

這個例子確保 label 總是在顯示當前滾動條的值。注意!訊號和槽的引數不能包含任何變數的名稱。例如,下面的例子將不能工作,並返回錯誤。

// WRONG
QObject::connect(scrollBar, SIGNAL(valueChanged(int value)),label, SLOT(setNum(int value)));

QMetaMethod風格的 connect()

QMetaObject::Connection connect(const QObject *, const QMetaMethod &,
                                const QObject *, const QMetaMethod &,
                                Qt::ConnectionType);

sender 和 receiver 同樣是const QObject *,但是 signal 和 slot 都是const QMetaMethod &。我們可以將每個函式看做是QMetaMethod的子類。因此,這種寫法可以使用QMetaMethod進行型別比對。

仿函式風格的connect()

QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
                                const QObject *, PointerToMemberFunction,
                                Qt::ConnectionType)

sender 和 receiver 也都存在,都是const QObject *,但是 signal 和 slot 型別則是PointerToMemberFunction。看這個名字就應該知道,這是指向成員函式的指標, 例子:

QLabel *label = new QLabel;
QLineEdit *lineEdit = new QLineEdit;
QObject::connect(lineEdit, &QLineEdit::textChanged, label, &QLabel::setText);

支援槽為非成員函式的connect()


QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
                                Functor);

前面兩個引數沒有什麼不同,最後一個引數是Functor型別。這個型別可以接受 static 函式、全域性函式以及 Lambda 表示式。

全域性函式:

void someFunction();
QPushButton *button = new QPushButton;
QObject::connect(button, &QPushButton::clicked, someFunction);

Lambda 表示式:

  QByteArray page = ...;
  QTcpSocket *socket = new QTcpSocket;
  socket->connectToHost("qt-project.org", 80);
  QObject::connect(socket, &QTcpSocket::connected, [=] () {
          socket->write("GET " + page + "\r\n");
      });

如果sender被銷燬,連線將自動斷開。但是,應該注意在發出訊號時,函式內使用的任何物件仍然是存活的。

QObject + Functor 的connect()

QMetaObject::Connection connect(const QObject *, PointerToMemberFunction ,
                                const QObject *, Functor ,
                                Qt::ConnectionType )

Creates a connection of a given type from signal in sender object to functor to be placed in a specific event loop of context, and returns a handle to the connection.

建立一個指定型別的signal連線,將functor放置在receiver的事件迴圈中。

注意: Qt::UniqueConnections 不能在lambdas,非成員函式和functor 上使用;它們只適用於連線到成員函式。訊號必須是在標頭檔案中宣告為訊號的函式。槽函式可以是任何可以連線到訊號的函式或仿函式(functor)。訊號和槽中相應引數的型別之間必須存在隱式轉換。

// function:

  void someFunction();
  QPushButton *button = new QPushButton;
  QObject::connect(button, &QPushButton::clicked, this, someFunction, Qt::QueuedConnection);

// Lambda:

  QByteArray page = ...;
  QTcpSocket *socket = new QTcpSocket;
  socket->connectToHost("qt-project.org", 80);
  QObject::connect(socket, &QTcpSocket::connected, this, [=] () {
          socket->write("GET " + page + "\r\n");
      }, Qt::AutoConnection);

由此我們可以看出,connect()函式,sender 和 receiver 沒有什麼區別,都是QObject指標;主要是 signal 和 slot 形式的區別。具體到我們的示例,我們的connect()函式顯然是使用的第五個過載,最後一個引數是QApplication的 static 函式quit()。也就是說,當我們的 button 發出了clicked()訊號時,會呼叫QApplication的quit()函式,使程式退出。

如果訊號槽不符合,或者根本找不到這個訊號或者槽函式的話,比如我們改成:

QObject::connect(&button, &QPushButton::clicked, &QApplication::quit2);
由於 QApplication 沒有 quit2 這樣的函式的,因此在編譯時,會有編譯錯誤:

'quit2' is not a member of QApplication

這樣,使用成員函式指標,我們就不會擔心在編寫訊號槽的時候會出現函式錯誤。

帶有預設引數的訊號-槽

訊號和槽的簽名可以包含引數,引數可以有預設值。考慮QObject::destroyed():

void destroyed(QObject* = 0);

當一個QObject被刪除時,它會發出QObject::destroyed()訊號。
We want to catch this signal, wherever we might have a dangling reference to the deleted QObject, so we can clean it up.
我們希望捕捉這個訊號,無論在哪裡,只要有一個指向已刪除QObject的懸空引用,我們就可以清除它。

一個合適的槽可能是:

  void objectDestroyed(QObject* obj = 0);

為了將訊號連線到插槽,我們使用QObject::connect()。有幾種方法連線訊號和插槽。

第一種方法是使用函式指標:

    connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);

將QObject::connect()與函式指標一起使用有幾個優點:

  • 它允許編譯器檢查訊號的實參是否與槽的實參相容。
  • 如果需要,引數也可以由編譯器隱式轉換。
  • 你也可以連線functor或c++ 11 lambdas:
connect(sender, &QObject::destroyed, [=](){
     this; m_objects.remove(sender);
});

另一種將訊號連線到槽的方法是使用 字串風格的QObject::connect()。關於在SIGNAL()和SLOT()巨集中是否包含引數,如果引數有預設值,傳遞給SIGNAL()巨集的簽名的引數必須不少於傳遞給SLOT()巨集的簽名的引數。

所有這些都可以:

connect(sender, SIGNAL(destroyed(QObject*)),
        this, SLOT(objectDestroyed(QObject*)));
connect(sender, SIGNAL(destroyed(QObject*)),
        this, SLOT(objectDestroyed()));
connect(sender, SIGNAL(destroyed())),
        this, SLOT(objectDestroyed()));

但這個行不通:

connect(sender, SIGNAL(destroyed()),
        this, SLOT(objectDestroyed(QObject*)));

因為槽需要一個QObject*作為引數將但是訊號沒有傳送。此連線將報告一個執行時錯誤。

注意!當使用字串風格的QObject::connect()過載時,編譯器不會檢查訊號和槽引數。

字串風格和仿函式風格的connect的區別

字串風格仿函式風格
型別檢查發生時刻執行時編譯時
執行隱式型別轉換
連線訊號至lambda表示式
signal的引數可以少於slot(有預設引數)
連線C++函式和QML函式

下面幾節將詳細解釋這些區別,並演示如何使用每種連線語法特有的特性。

型別檢查和隱式型別轉換

基於字串的連線型別檢查,在執行時比較字串。這種方法有三個限制:

  1. 只有在程式開始執行後才能檢測到連線錯誤。
  2. 引數不能在訊號和槽之間進行隱式轉換。
  3. 不能解析型別定義和名稱空間。

限制2和3的存在是因為string comparator不能訪問c++型別資訊,所以它依賴於精確的字串匹配。

相反,基於仿函式的連線由編譯器檢查。編譯器在編譯時捕獲錯誤,啟用相容型別之間的隱式轉換,並識別同一型別的不同名稱。

例如,只有基於仿函式的語法可以用於將攜帶int的訊號連線到接受double的槽。QSlider儲存一個int值,而QDoubleSpinBox儲存一個double值。下面的程式碼片段展示瞭如何保持它們同步:

    auto slider = new QSlider(this);
    auto doubleSpinBox = new QDoubleSpinBox(this);

    // OK: The compiler can convert an int into a double
    connect(slider, &QSlider::valueChanged,
            doubleSpinBox, &QDoubleSpinBox::setValue);

    // ERROR: The string table doesn't contain conversion information
    connect(slider, SIGNAL(valueChanged(int)),
            doubleSpinBox, SLOT(setValue(double)));

下面的示例說明了缺少名稱解析的原因。QAudioInput::stateChanged()用一個型別為QAudio::State的引數宣告。因此,基於字串的連線還必須指定QAudio::State,即使State已經可見。這個問題不會發生於基於仿函式的連線,因為實參型別不是連線的一部分。

auto audioInput = new QAudioInput(QAudioFormat(), this);
auto widget = new QWidget(this);

// OK
connect(audioInput, SIGNAL(stateChanged(QAudio::State)),
        widget, SLOT(show()));

// ERROR: The strings "State" and "QAudio::State" don't match
using namespace QAudio;
connect(audioInput, SIGNAL(stateChanged(State)),
        widget, SLOT(show()));

連線到 Lambda 表示式

基於仿函式的連線語法可以將訊號連線到c++ 11 lambda表示式,這是一種有效的內聯槽。此特性不適用於基於字串的語法。

在下面的示例中,TextSender類發出一個帶有QString引數的textCompleted()訊號。

class TextSender : public QWidget {
    Q_OBJECT

    QLineEdit *lineEdit;
    QPushButton *button;

signals:
    void textCompleted(const QString& text) const;

public:
    TextSender(QWidget *parent = nullptr)
    : QWidget(parent)
    {
        line Edit = new QLineEdit(this);
        button = new QPushButton("Send", this);

        connect(button, &QPushButton::clicked, [=]{
            emit textCompleted(lineEdit->text());
        })

        // ...
    }
  };

在本例中,儘管QPushButton::clicked()TextSender::textCompleted()的引數不相容,lambda函式還是使連線變得簡單。相反,基於字串的實現則需要額外的程式碼。

注意:基於仿函式的連線語法接受指向所有函式的指標,包括獨立函式和常規成員函式。然而,為了便於閱讀,訊號應該只連線到插槽、lambda表示式和其他訊號。

連線C++物件到QML物件

基於字串的語法可以將c++物件連線到QML物件,但是基於仿函式的語法不能。這是因為QML型別是在執行時解析的,所以c++編譯器無法使用它們。

在下面的示例中,單擊QML物件將使c++物件列印一條訊息,反之亦然。下面是QML型別(在QmlGui.qml中)

Rectangle {
    width: 100; height: 100

    signal qmlSignal(string sentMsg)
    function qmlSlot(receivedMsg) {
        console.log("QML received: " + receivedMsg)
    }

    MouseArea {
        anchors.fill: parent
        onClicked: qmlSignal("Hello from QML!")
    }
}

下面是 C++ 程式碼:

class CppGui : public QWidget {
    Q_OBJECT

    QPushButton *button;

signals:
    void cppSignal(const QVariant& sentMsg) const;

public slots:
    void cppSlot(const QString& receivedMsg) const {
        qDebug() << "C++ received:" << receivedMsg;
    }

public:
    CppGui(QWidget *parent = nullptr) : QWidget(parent) {
        button = new QPushButton("Click Me!", this);
        connect(button, &QPushButton::clicked, [=] {
            emit cppSignal("Hello from C++!");
        });
    }
};

下面的程式碼展示瞭如何連線signal-slot:

auto cppObj = new CppGui(this);
auto quickWidget = new QQuickWidget(QUrl("QmlGui.qml"), this);
auto qmlObj = quickWidget->rootObject();

// Connect QML signal to C++ slot
connect(qmlObj, SIGNAL(qmlSignal(QString)),
        cppObj, SLOT(cppSlot(QString)));

// Connect C++ signal to QML slot
connect(cppObj, SIGNAL(cppSignal(QVariant)),
        qmlObj, SLOT(qmlSlot(QVariant)));

注意: QML中的所有JavaScript函式都接受var型別的引數,它對映到c++中的QVariant型別。

當點選QPushButton時,控制檯列印:'QML received: ’ Hello from c++ ! ’ ‘。同樣,當單擊矩形時,控制檯列印:’ c++ received: ’ Hello from QML! ’ '。

帶有預設引數的slot

通常,只有當槽的引數數量與訊號相同(或更少),並且所有引數型別都相容時,才能建立連線。

基於字串的連線語法為該規則提供了一個解決方案:如果槽有預設引數,這些引數可以從訊號中省略。當發出的訊號的引數比槽少時,Qt使用預設引數值執行槽。

基於仿函式的連線不支援此功能。

假設有一個名為DemoWidget的類,它有一個槽printNumber(),它有一個預設引數:

public slots:
    void printNumber(int number = 42)
    {
        qDebug() << "Lucky number" << number;
    }

使用基於字串的連線,DemoWidget::printNumber()可以連線到QApplication::aboutToQuit(),即使後者沒有引數。

基於仿函式的連線將產生一個編譯時錯誤:

DemoWidget::DemoWidget(QWidget *parent)
 : QWidget(parent) {

    // OK: printNumber() will be called with a default value of 42
    connect(qApp, SIGNAL(aboutToQuit()),
            this, SLOT(printNumber()));

    // ERROR: Compiler requires compatible arguments
    connect(qApp, &QCoreApplication::aboutToQuit,
            this, &DemoWidget::printNumber);
}

選擇 signal 和 slot 的過載函式

使用基於字串的語法,引數型別是顯式指定的。因此,過載訊號或槽的例項是明確的。相反,在基於仿函式的語法中,必須對過載訊號或槽進行型別轉換,以告訴編譯器使用哪個例項。

例如,QLCDNumber有三個版本的display()槽位:

  1. QLCDNumber::display(int)
  2. QLCDNumber::display(double)
  3. QLCDNumber::display(QString)

要將int版本連線到QSlider::valueChanged(),有以下兩種語法:

auto slider = new QSlider(this);
auto lcd = new QLCDNumber(this);

// String-based syntax
connect(slider, SIGNAL(valueChanged(int)),
        lcd, SLOT(display(int)));

// Functor-based syntax, first alternative
connect(slider, &QSlider::valueChanged,
        lcd, static_cast<void (QLCDNumber::*)(int)>(&QLCDNumber::display));

// Functor-based syntax, second alternative
void (QLCDNumber::*mySlot)(int) = &QLCDNumber::display;
connect(slider, &QSlider::valueChanged,
        lcd, mySlot);

// Functor-based syntax, third alternative
connect(slider, &QSlider::valueChanged,
        lcd, QOverload<int>::of(&QLCDNumber::display));

// Functor-based syntax, fourth alternative (requires C++14)
connect(slider, &QSlider::valueChanged,
        lcd, qOverload<int>(&QLCDNumber::display));