Qt4.8 の slot に関数オブジェクトやラムダ式を渡せるようなラッパーを書いてみた

Qt の connect は便利なんですが slot で簡単な処理を行う場合にわざわざ QObject を継承したクラスのメンバ関数で処理を書かなければいけなかったり、Q_OBJECT を付ける必要があったり、クラスは独立したヘッダーファイルで定義しなければいけなかったりと地味使いづらいです。
と、いう事でラムダ式を渡せるようなラッパーを書いてみました。
※ただし、引数型を設定するのは無理です。

[lambda.hpp]

#ifndef QK_LAMBDA_H
#define QK_LAMBDA_H

#include <QtCore/QObject>
#include <type_traits>
#include <functional>

namespace qk{

namespace detail{

struct handler : QObject{
    Q_OBJECT
public:
    typedef std::function<void()> func_type;
    handler(func_type func)
        : func(func)
    {}

    func_type func;
public Q_SLOTS:
    void
    signaled(){
        func();
    }

};

}  // namespace detail


template<typename Uniequ, typename Sender, typename Signal, typename Func>
bool
connect(Sender&& sender, Signal&& signal, Func&& func,
    Qt::ConnectionType type = Qt::AutoConnection
){
    static detail::handler receiver(std::forward<Func>(func));
    return QObject::connect(
        std::forward<Sender>(sender),
        std::forward<Signal>(signal),
        &receiver,
        SLOT(signaled()),
        type
    );
};


template<typename Unique, typename Sender, typename Signal, typename Func>
bool
connect(Unique, Sender&& sender, Signal&& signal, Func&& func,
    Qt::ConnectionType type = Qt::AutoConnection
){
    return connect<Unique>(
        std::forward<Sender>(sender),
        std::forward<Signal>(signal),
        std::forward<Func>(func),
        type
    );
}


template<typename Sender, typename Signal, typename Func>
bool
connect_lambda(Sender&& sender, Signal&& signal, Func&& func,
    Qt::ConnectionType type = Qt::AutoConnection
){
    return connect<Func>(
        std::forward<Sender>(sender),
        std::forward<Signal>(signal),
        std::forward<Func>(func),
        type
    );
}

}  // namespace qk

#include "moc_lambda.cpp"


#endif /* QK_LAMBDA_H */

[main.cpp]

#include <iostream>
#include <string>
#include <QtGui/QApplication>
#include <QtGui/QPushButton>
#include <QtCore/QDebug>
#include <QtGui/QLabel>
#include <boost/lexical_cast.hpp>
#include "lambda.hpp"

struct print_debug{
    print_debug(std::string const& text)
         : text(text){}
    void
    operator ()(){
        qDebug() << text.c_str();
    }
    std::string text;
};

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

    int count = 0;

    QPushButton button("Quit");
    button.show();

    qk::connect_lambda(&button, SIGNAL(clicked()), [&]{
        ++count;
    });
    qk::connect_lambda(&button, SIGNAL(clicked()), [&]{
        if( count >= 5 ){
            button.setFont(QFont("Times", 30, QFont::Bold));
        }
    });
    qk::connect_lambda(&button, SIGNAL(clicked()), [&]{
        if( count >= 8 ){
            app.quit();
        }
    });
    
    // static オブジェクトを使用する必要があるのでユニークな型を設定する必要がある
    qk::connect<struct unique_type>(&button, SIGNAL(clicked()), print_debug("clicked"));

    // ユニークな型としてラムダ式を使用
    qk::connect([]{}, &button, SIGNAL(clicked()), print_debug("clicked2"));


    QLabel label;
    label.show();
    label.setText(boost::lexical_cast<std::string>(count).c_str());
    qk::connect_lambda(&button, SIGNAL(clicked()), [&]{
        label.setText(boost::lexical_cast<std::string>(count).c_str());
    });

    return app.exec();
}


引数が渡せないのはしょうがないんですがそれでも使い勝手はだいぶいいですね。
static オブジェクトを使用しているので c_function と同じような問題があるんですが、まぁラムダ式を使用するのであれば問題ないかなぁ。


ビルドは moc ファイルを出力する必要があるので qmake を使用する必要があります。
引数も渡せるようにしたかったんですが、Qt力が低くて諦めました…。
Qt の実装分からん。


なお、Qt5 ではここら辺の縛りが緩和されており connect にラムダ式や関数オブジェクト、メンバ関数ポインタを設定できるようになっているみたいです。


これでかなり使い勝手がよくなっていると思います。
早く使いたい。



ちなみに Qt の機能を駆使すると引数も渡せるみたいです。with-libffi/
Qt力高い…。


あと本件とは関係ないんですが Web で Qt のコードとかを調べていると new しっぱなしで delete を行なっていないコードばかり目にするんですが(いろんない意味で)大丈夫なんですかね…。
Qt 的に出来に大丈夫だとしても C++ としてはだいぶアレな感じ。きもちわるい。