Qt信号槽到底可不可以有返回值呢?問了下身邊的同事,有的人說可以,有的人說不可以。 在實際項目中,确實沒看到過有人使用帶返回值的信号槽,可以說存在感很低。平時大家工作也比較忙,所以也沒有時間去較真信号槽到底能不能有返回值。今天就一起帶大家較真一下,看一看信号槽能否有返回值。如果可以有返回值,那麼又有哪些限制導緻大家都不用它呢?
提示:本文代碼略多,但是都很簡單,請耐心閱讀。
上代碼新建一個Qt Widgets Application,在Qt為我們生成的主窗口.h文件中,添加信号槽的聲明,分别是
完整代碼如下:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui { class MainWindow; }
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
SIGNALs:
// 用于獲取當前時間的信号
QString sigCurrentTime();
private slots:
// 用于獲取當前時間的槽
QString slotCurrentTime();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
在.cpp文件中,建立信号槽連接,實現槽函數,發送信号并打印返回值。完整代碼如下:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDateTime>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(this, SIGNAL(sigCurrentTime()), this, SLOT(slotCurrentTime()));
qDebug() << "Current Time:" << sigCurrentTime();
}
MainWindow::~MainWindow()
{
delete ui;
}
QString MainWindow::slotCurrentTime()
{
return QDateTime::currentDateTime().toString("yyyyMMdd hh:mm:ss");
}
代碼很簡單,運行結果如下:
運行結果1
答案很明确:「信号槽可以有返回值。」 即槽的返回值會傳遞給信号,作為信号的返回值返回。 得出上述結論隻是第一步。
下面開始我們的第二問: 是否所有情況下,信号槽返回值都可以正常工作呢?
不同機制下的運行情況在确保信号槽正常連接,并能夠成功發送接收的前提下,影響信号槽工作的因素就隻有在connect信号槽時指定的、信号槽的連接方式。 我們知道信号槽的連接方式有以下幾種:
嚴格來說,最後一個UniqueConnection是用來保證信号槽一對一連接的,從實現機制上并不算是一種連接方式。所以我們依次測試前四種連接方式,得到了不同的結果。
默認連接方式,輸出和前面的輸出相同。
運行結果-AutoConnection
即代碼改成:
connect(this, SIGNAL(sigCurrentTime()), this, SLOT(slotCurrentTime()), Qt::DirectConnection);
此種為直接連接方式,相當于函數調用,輸出結果為
運行結果-DirectConnection
可以看到,返回值可以正常返回。
即代碼改成:
connect(this, SIGNAL(sigCurrentTime()), this, SLOT(slotCurrentTime()), Qt::QueuedConnection);
此種為異步消息方式連接,用于将消息發向具有消息循環的線程,以消息的方式傳遞信号,信号槽既可以在同一個線程,也可以在不同的線程。這裡在主線程進行了測試,輸出結果為
運行結果-QueuedConnection
線程間信号槽返回值測試結果相同。可以看到異步消息方式,信号槽返回值無法工作。
此種方式用于多線程環境下,一個線程向另外一個線程發送信号,并阻塞等待槽執行完成。 下面是多線程的測試代碼,添加了一個新的QObject子類:ObjectInSubThread,用于在子線程中運行。代碼如下(想要直接看結果的同學可直接跳過):
ObjectInSubThread.h頭文件
#ifndef OBJECTINSUBTHREAD_H
#define OBJECTINSUBTHREAD_H
#include <QObject>
class ObjectInSubThread : public QObject
{
Q_OBJECT
public:
explicit ObjectInSubThread(QObject *parent = nullptr);
private slots:
// 用于獲取當前時間的槽
QString slotCurrentTime();
};
#endif // OBJECTINSUBTHREAD_H
ObjectInSubThread.cpp實現文件
#include "ObjectInSubThread.h"
#include <QDateTime>
ObjectInSubThread::ObjectInSubThread(QObject *parent) : QObject(parent)
{
}
QString ObjectInSubThread::slotCurrentTime()
{
return QDateTime::currentDateTime().toString("yyyyMMdd hh:mm:ss");
}
同時修改mainwindow類的代碼:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "ObjectInSubThread.h"
#include <QThread>
namespace Ui { class MainWindow; }
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
// 用于獲取當前時間的信号
QString sigCurrentTime();
private:
Ui::MainWindow *ui;
ObjectInSubThread *m_obj_in_subthread; /* 子線程中的對象 */
QThread m_sub_thread; /* 子線程 */
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDateTime>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, m_obj_in_subthread(0)
{
ui->setupUi(this);
// 創建子線程對象
m_obj_in_subthread = new ObjectInSubThread;
// 移動到子線程中
m_obj_in_subthread->moveToThread(&m_sub_thread);
// 啟動子線程消息循環
m_sub_thread.start();
// 建立信号槽連接
connect(this, SIGNAL(sigCurrentTime()), m_obj_in_subthread, SLOT(slotCurrentTime()), Qt::BlockingQueuedConnection);
qDebug() << "Current Time:" << sigCurrentTime();
}
MainWindow::~MainWindow()
{
delete ui;
m_sub_thread.quit();
m_sub_thread.wait();
if (m_obj_in_subthread) delete m_obj_in_subthread;
}
輸出結果:
運行結果-BlockingQueuedConnection
表明信号槽返回值功能可以正常工作。
「綜上所述:」
❝
從不同的信号槽連接機制的角度看,不管是在多線程還是單線程,隻要是異步非阻塞方式連接的信号槽,都無法獲取返回值;隻要是同步阻塞方式連接的信号槽,都可以獲取返回值。 原理上也比較容易理解。非阻塞方式連接的信号槽,信号返回時,槽還沒有返回,如何将返回值傳遞回去呢,邏輯上是行不通的。隻有在同步阻塞的情況下,由響應槽先返回,将返回值傳遞給信号以後,信号再返回,這樣便實現了信号槽的返回值功能。
❞
不同使用方式下的運行情況上面講解了不同連接機制下的運行情況。在實際使用時,也有一些情況需要考慮。 比如,一個帶返回值的信号,連接到多個帶相同類型返回值的槽,最後的返回值是什麼? 新建Qt Widgets Application測試,下面是測試代碼:
mainwindow.h文件
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui { class MainWindow; }
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
// 用于獲取當前時間的信号
QString sigCurrentTime();
private slots:
// 用于獲取當前時間的槽
QString slotCurrentTime1();
// 用于獲取當前時間的槽
QString slotCurrentTime2();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp文件
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDateTime>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// sigCurrentTime分别連接到槽slotCurrentTime1()和槽slotCurrentTime2()
connect(this, SIGNAL(sigCurrentTime()), this, SLOT(slotCurrentTime1()));
connect(this, SIGNAL(sigCurrentTime()), this, SLOT(slotCurrentTime2()));
qDebug() << "Current Time:" << sigCurrentTime();
}
MainWindow::~MainWindow()
{
delete ui;
}
QString MainWindow::slotCurrentTime1()
{
return QDateTime::currentDateTime().toString("yyyyMMdd hh:mm:ss") " slotCurrentTime1";
}
QString MainWindow::slotCurrentTime2()
{
return QDateTime::currentDateTime().toString("yyyyMMdd hh:mm:ss") " slotCurrentTime2";
}
如上所示,信号sigCurrentTime分别連接到了槽slotCurrentTime1()和槽slotCurrentTime2()。 輸出結果為:
運行結果-一信号對多槽連接
可見,最後輸出結果隻有一個,另外一個結果丢失或被覆蓋,這是符合邏輯的,因為返回值隻能有一個,最後的結果會覆蓋前面的結果。
結語本文從實現機制角度、應用場景角度對信号槽返回值功能進行了實驗和分析,基本涵蓋了工作中的各種使用情形,弄清楚了在不同的情況下,信号槽返回值功能的表現情況。一句話總結就是:
❝
隻有同步阻塞連接時才能使用信号槽返回值,且不管有幾個槽隻能有一個返回值。
❞
正是因為信号槽返回值限制太多,所以才幾乎沒有人使用。但是我們的研究沒有白費,通過研究的過程也加深了我們對信号槽機制的理解。
關于信号槽,有太多話題可以聊,後面會推出更多相關文章,敬請關注!
本文首發于微信公衆号“Qt未來工程師”。
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!