【Qt筆記】深刻 Qt5 信號槽新語法

在前面的章節(信號槽自定義信號槽)中,咱們詳細介紹了有關 Qt 5 的信號槽新語法。因爲此次改動很大,許多之前看起來不是問題的問題接踵而來,所以,咱們用單獨的一章從新介紹一些 Qt 5 的信號槽新語法。app

 

基本用法

Qt 5 引入了信號槽的新語法:使用函數指針可以得到編譯期的類型檢查。使用咱們在自定義信號槽中設計的Newspaper類,咱們來看看其基本語法:ide

//!!! Qt5
#include <QObject>

////////// newspaper.h
class Newspaper : public QObject
{
    Q_OBJECT
public:
    Newspaper(const QString & name) :
        m_name(name)
    {
    }

    void send() const
    {
        emit newPaper(m_name);
    }

signals:
    void newPaper(const QString &name) const;

private:
    QString m_name;
};

////////// reader.h
#include <QObject>
#include <QDebug>

class Reader : public QObject
{
    Q_OBJECT
public:
    Reader() {}

    void receiveNewspaper(const QString & name) const
    {
        qDebug() << "Receives Newspaper: " << name;
    }
};

////////// main.cpp
#include <QCoreApplication>

#include "newspaper.h"
#include "reader.h"

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    Newspaper newspaper("Newspaper A");
    Reader reader;
    QObject::connect(&newspaper, &Newspaper::newPaper,
                     &reader,    &Reader::receiveNewspaper);
    newspaper.send();

    return app.exec();
}

main()函數中,咱們使用connect()函數將newspaper對象的newPaper()信號與reader對象的receiveNewspaper()槽函數聯繫起來。當newspaper發出這個信號時,reader相應的槽函數就會自動被調用。這裏咱們使用了取址操做符,取到Newspaper::newPaper()信號的地址,一樣相似的取到了Reader::receiveNewspaper()函數地址。編譯器可以利用這兩個地址,在編譯期對這個鏈接操做進行檢查,若是有個任何錯誤(包括對象沒有這個信號,或者信號參數不匹配等),編譯時就會發現。函數

有重載的信號

若是信號有重載,好比咱們向Newspaper類增長一個新的信號:ui

void newPaper(const QString &name, const QDate &date);

此時若是仍是按照前面的寫法,編譯器會報出一個錯誤:因爲這個函數(注意,信號實際也是一個普通的函數)有重載,所以不能用一個取址操做符獲取其地址。回想一下 Qt 4 中的處理。在 Qt 4 中,咱們使用SIGNALSLOT兩個宏來鏈接信號槽。若是有一個帶有兩個參數的信號,像上面那種,那麼,咱們就可使用下面的代碼:spa

QObject::connect(&newspaper, SIGNAL(newPaper(QString, QDate)),
                 &reader,    SLOT(receiveNewspaper(QString, QDate)));

注意,咱們臨時增長了一個receiveNewspaper()函數的重載,以便支持兩個參數的信號。在 Qt 4 中不存在咱們所說的錯誤,由於 Qt 4 的信號槽鏈接是帶有參數的。所以,Qt 可以本身判斷到底是哪個信號對應了哪個槽。.net

對此,咱們也給出了一個解決方案,使用一個函數指針來指明究竟是哪個信號:設計

void (Newspaper:: *newPaperNameDate)(const QString &, const QDate &) = &Newspaper::newPaper;
QObject::connect(&newspaper, newPaperNameDate,
                 &reader,    &Reader::receiveNewspaper);

這樣,咱們使用了函數指針newspaperNameDate聲明一個帶有QStringQDate兩個參數,返回值是 void 的函數,將該函數做爲信號,與Reader::receiveNewspaper()槽鏈接起來。這樣,咱們就回避了以前編譯器的錯誤。歸根結底,這個錯誤是由於函數重載,編譯器不知道要取哪個函數的地址,而咱們顯式指明一個函數就能夠了。指針

若是你以爲這種寫法很難看,想像前面同樣寫成一行,固然也是由解決方法的:code

QObject::connect(&newspaper,
                 (void (Newspaper:: *)(const QString &, const QDate &))&Newspaper::newPaper,
                 &reader,
                 &Reader::receiveNewspaper);

這是一種換湯不換藥的作法:咱們只是聲明瞭一個匿名的函數指針,而以前咱們的函數指針是有名字的。不過,咱們並不推薦這樣寫,而是但願如下的寫法:對象

QObject::connect(&newspaper,
                 static_cast<void (Newspaper:: *)(const QString &, const QDate &)>(&Newspaper::newPaper),
                 &reader,
                 &Reader::receiveNewspaper);

對比上面兩種寫法。第一個使用的是 C 風格的強制類型轉換。此時,若是你改變了信號的類型,那麼你就會有一個潛在的運行時錯誤。例如,若是咱們把(const QString &, const QDate &)兩個參數修改爲(const QDate &, const QString &),C 風格的強制類型轉換就會失敗,而且這個錯誤只能在運行時發現。而第二種則是 C++ 推薦的風格,當參數類型改變時,編譯器會檢測到這個錯誤。

注意,這裏咱們只是強調了函數參數的問題。若是前面的對象都錯了呢?好比,咱們寫的newspaper對象並非一個Newspaper,而是Newspaper2?此時,編譯器會直接失敗,由於connect()函數會去尋找sender->*signal,若是這兩個參數不知足,則會直接報錯。

帶有默認參數的槽函數

Qt 容許信號和槽的參數數目不一致:槽函數的參數數目能夠比信號的參數少。這是由於,咱們信號的參數實際是做爲一種返回值。正如普通的函數調用同樣,咱們能夠選擇忽略函數返回值,可是不能使用一個並不存在的返回值。若是槽函數的參數數目比信號的多,在槽函數中就使用到這些參數的時候,實際這些參數並不存在(由於信號的參數比槽的少,所以並無傳過來),函數就會報錯。這種狀況每每有兩個緣由:一是槽的參數就是比信號的少,此時咱們能夠像前面那種寫法直接鏈接。另一個緣由是,信號的參數帶有默認值。好比

void QPushButton::clicked(bool checked = false)

就是這種狀況。

然而,有一種狀況,槽函數的參數能夠比信號的多,那就是槽函數的參數帶有默認值。好比,咱們的NewspaperReader有下面的代碼:

// Newspaper
signals:
    void newPaper(const QString &name);
// Reader
    void receiveNewspaper(const QString &name, const QDate &date = QDate::currentDate());

雖然Reader::receiveNewspaper()的參數數目比Newspaper::newPaper()多,可是因爲Reader::receiveNewspaper()後面一個參數帶有默認值,因此該參數不是必須提供的。可是,若是你按照前面的寫法,好比以下的代碼:

QObject::connect(&newspaper,
                 static_cast<void (Newspaper:: *)(const QString &)>(&Newspaper::newPaper),
                 &reader,
                 static_cast<void (Reader:: *)(const QString &, const QDate & =QDate::currentDate())>(&Reader::receiveNewspaper));

你會獲得一個斷言錯誤:

The slot requires more arguments than the signal provides.

咱們不能在函數指針中使用函數參數的默認值。這是 C++ 語言的限制:參數默認值只能使用在直接地函數調用中。當使用函數指針取其地址的時候,默認參數是不可見的!

固然,此時你能夠選擇 Qt 4 的鏈接語法。若是你仍是想使用 Qt 5 的新語法,目前的辦法只有一個:Lambda 表達式。不要擔憂你的編譯器不支持 Lambda 表達式,由於在你使用 Qt 5 的時候,可以支持 Qt 5 的編譯器都是支持 Lambda 表達式的。因而,咱們的代碼就變成了:

QObject::connect(&newspaper,
                 static_cast<void (Newspaper:: *)(const QString &)>(&Newspaper::newPaper),
                 [=](const QString &name) { /* Your code here. */ });
相關文章
相關標籤/搜索