在之前的Qt項目中,我發現經常會用到槽函數只需要執行一次的情況。也就是説,槽函數執行一次後,就需要disconnect對應的連接。然而,真正操作起來實際上挺麻煩的,或者説不優雅。因為你需要把之前connect時產生的QMetaObject::Connection對象保存起來,而保存它不能用局部變量,通常需要保存到類的成員變量中,或者其他生命週期足夠長的地方,以防止在disconnect它的時候,它已經失效了。總之,需要使用者自己維護,因而增加了使用者的負擔。
如果有一個方法能夠在槽函數執行完成後自動disconnect掉連接就好了。我在網上找了一段時間,卻沒有找到合適的解決方案,相關討論也比較少,可能這不是一個很常見的需求吧。不過還是在GitHub上找到了一個相關的庫:https://github.com/misje/once,但是看它的源碼,感覺比較複雜。它針對QObject::connect函數的每種情況,寫了對應的實現,總感覺太複雜了,應該存在一種更通用的方法。
最近在閲讀了《C++ Primer》模板相關的章節後,我突然想到也許用完美轉發相關的東西可以簡化實現。於是試着寫了一下,貌似真得可以,代碼如下:
ConnectionUtil.h:
#pragma once
#include <QObject>
#include <functional>
namespace ConnectionUtil
{
typedef QMetaObject::Connection Conn;
class ReceiverObj : public QObject
{
Q_OBJECT
public:
explicit ReceiverObj(Conn *conn1, Conn *conn2) : mConn1(conn1), mConn2(conn2) {}
public slots:
void slot()
{
QObject::disconnect(*mConn1);
QObject::disconnect(*mConn2);
delete mConn1;
delete mConn2;
deleteLater();
}
private:
Conn *mConn1, *mConn2;
};
// 處理信號為SIGNAL(...)的情況
template <typename Sender, typename ...Args>
void connectOnce(Sender &&sender, const char *signal, Args &&...args)
{
Conn *conn1 = new Conn;
Conn *conn2 = new Conn;
*conn1 = QObject::connect(std::forward<Sender>(sender), signal, std::forward<Args>(args)...);
*conn2 = QObject::connect(std::forward<Sender>(sender), signal, new ReceiverObj(conn1, conn2), SLOT(slot()));
}
// 處理其他情況
template <typename Sender, typename Signal, typename ...Args>
void connectOnce(Sender &&sender, Signal &&signal, Args &&...args)
{
Conn *conn1 = new Conn;
Conn *conn2 = new Conn;
*conn1 = QObject::connect(std::forward<Sender>(sender), std::forward<Signal>(signal), std::forward<Args>(args)...);
*conn2 = QObject::connect(std::forward<Sender>(sender), std::forward<Signal>(signal), std::bind(&ReceiverObj::slot, new ReceiverObj(conn1, conn2)));
}
}
這個實現假定QObject::connect的所有重載函數前兩個參數分別是Sender和Signal,事實上確實是這樣。關鍵點就是,額外建立一個連接,在收到信號後,disconnect用户的連接。不過,我總感覺這個實現在多線程的情況下可能有bug,但在經過簡單的測試後,暫時沒有發現。使用示例如下:
ConnectionUtil::connectOnce(this, SIGNAL(bong(int)), obj, SLOT(slotBong(int)), Qt::QueuedConnection);
ConnectionUtil::connectOnce(this, &MainWindow::bong, obj, &SomeObject::slotBong, Qt::QueuedConnection);
ConnectionUtil::connectOnce(this, &MainWindow::bong, this, []() {
qDebug() << "bingo";
});
請大家批評指正。