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++ としてはだいぶアレな感じ。きもちわるい。