动态

详情 返回 返回

寫一個只觸發一次槽函數的Qt connect函數 - 动态 详情

在之前的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";
});

請大家批評指正。

Add a new 评论

Some HTML is okay.