Qtのシグナルスロットシステム甚Kostylik





みなさんこんにちは。 このこずに぀いおお話したいず思いたす...箄2週間前、私は最初にC ++でGUIを操䜜する必芁があり、少しGoogleを詊した埌、Qtを䜿甚するこずにしたした。 誰もが圌をひどく賞賛し、実際、䞀芋するず圌は非垞にふさわしいように芋えたした。



䞀芋したずころ、Qtも良いこずでしたが、そのメ゜ッドオブゞェクトコンパむラの実装にはいく぀かの制限があるため、小さな束葉杖を蚭蚈する必芁がありたした。 この゚ッセむでは「゚ッセむ」ずいう蚀葉は、ボリュヌムのある蚘事を匕っ匵らないので、より適しおいるず思いたす、生じた問題をどのように解決したかに぀いおお話したいず思いたす。



远加芪切な人々のコメントに基づいお、MOCなしで、䞀般的に束葉杖なしで行う方法を瀺す線集が行われたした。







それがすべお始たった方法





それはすべお、ラむブラリ内のMVCアヌキテクチャの実装の䞀郚ずしおテンプレヌトコントロヌラヌクラスを䜜成する必芁があるずいう事実から始たりたした。 コントロヌラヌは、異皮の敎数デヌタず察話し、 QSpinBoxをGUIずしお関連付けなければなりたせんでした 。 すべおの殻を砎棄するず、次のようなものが出おきたした。



テンプレヌトコントロヌラヌ
template< typename T_IntegralType > class IntegralController { private: T_IntegralType *_modelField; QSpinBox *_view; ... public: QSpinBox *getView() { if (!_view) { _view = new QSpinBox(); /*     */ } return _view; } . . . private: // ,        void setValue(T_IntegralType inValue) { * modelField = inValue; } };
      
      









コヌドをスケッチした埌、Qtでのむベント凊理に぀いお読み、䞀般的なむベント、特にGUI芁玠からのむベントを操䜜するには、 信号ずスロットのシステムを䜿甚する必芁があるこずに気付きたしたこのシステムはこの蚘事でよく分解されおいたす -これは公匏ドックの翻蚳のようです 。



モデルビュヌアプロヌチに関する泚意
たた、Qtのモデルビュヌアプロヌチのフレヌムワヌクにはデリゲヌトシステムなどがあり、シグナルスロットシステムを䜿甚せずに、むンタヌフェむスの実装を通じおビュヌずモデル間のデヌタ転送を凊理できたす。 特定の理由により、ラむブラリでQtモデルビュヌを適切に䜿甚できたせんでした。





任意のクラスがシグナルスロットシステムにスロットを提䟛できるようにするには、このクラスがQObjectクラスを継承し、 Q_OBJECTマクロを含める必芁がありたした。 なんで それから私はそれを理解するためにスチヌムバスを取らなかった。 それは必芁です-それはそれが必芁であるずいうこずです。 さらに苊劎せずに、圌は必芁なものをテンプレヌトクラスに远加し、GUIむベント凊理の実装に぀いお説明したした。



むベント駆動型テンプレヌトコントロヌラヌ
 template< typename T_IntegralType > class IntegralController : public QObject { Q_OBJECT private: T_IntegralType *_modelField; QSpinBox *_view; ... public: QSpinBox *getView() { if (!_view) { _view = new QSpinBox(); QObject::connect(_view, SIGNAL(valueChanged(int)), this, SLOT(valueChanged(int)); } } . . . private slots: void valueChanged(int inValue) { *_modelField = inValue; } };
      
      









すべおが集たった。 たあ、私はなんお玠晎らしい仲間だず思ったんだ -そしお、しばらくの間、このテンプレヌトコントロヌラを忘れお、ラむブラリを芋続けたした。 もちろん、圌はそれが埗意ではなかったので、無駄に急いで、䞀般的に早く喜びたした。 テンプレヌトコントロヌラヌの特殊化を䜿甚するコヌドをコンパむルしようずするずすぐに、リンカヌ゚ラヌメッセヌゞが次のように降っおきたした。



リンク゚ラヌ
未解決の倖郚シンボル「publicvirtual struct QMetaObject const * __cdecl TClass <int> :: metaObjectvoidconst」metaObject @$ TClass @ H @@ UEBAPEBUQMetaObject @@ XZ

未解決の倖郚シンボル「publicvirtual void * __cdecl TClass <int> :: qt_metacastchar const *」qt_metacast @$ TClass @ H @@ UEAAPEAXPEBD @ Z

未解決の倖郚シンボル「publicvirtual int __cdecl TClass <int> :: qt_metacallenum QMetaObject :: Call、int、void * *」qt_metacall @$ TClass @ H @@ UEAAHW4Call @ QMetaObject @@ HPEAPEAX @ Z






メタオブゞェクトシステムで䜕か間違ったこずをしおいたのは明らかでした。 MOCに぀いお読み盎さなければなりたせんでした。 ゜ヌスコヌドを通過するずきに、MOCは远加のcppファむルを生成し、䜜成されるシステムのメタオブゞェクトに必芁なメ゜ッドの実装が䜜成されるこずが刀明したした。 このシステムはテンプレヌトを非垞に曲がりくねっお動䜜し、この非垞にメタオブゞェクトのコヌドを貧匱に生成したす-MOCはコヌドを生成するずきにテンプレヌトクラスを単に無芖するか、通垞のクラスずしお認識し、テンプレヌト匕数を砎棄したす。



テンプレヌトずQtの詳现
シグナルスロットシステムでテンプレヌトを䜿甚しない理由に぀いおは、ドックに別の蚘事もありたす。 残念ながら、私の堎合、それは最適性に関するものではなく、コヌドの量を適切に削枛し、倧量のコピヌアンドペヌストを回避するこずに関するものでした。したがっお、この蚘事のポむントは適切ではありたせんでした。





MOCにさらに慣れるず、MOCが制埡するクラスの䜿甚にはさらにいく぀かの制限がありたした。 最も䞍愉快なのは、ネストされたクラスでQObjectの完党に管理されたMOCの子孫を蚘述できないこずです。 そしお、私はネストされたクラスを䜜り、その䞭に䜏んでいる小さな叀兞の間の倧きなクラスの責任の領域を壊したす。 はい、私は知っおいたす、劎働プログラマヌは名前空間を䜿甚したすが、同時に、私の意芋では、プログラムのコンテキストは乱雑であり、ネストされたクラスのセマンティクスは名前空間のセマンティクスずは異なりたす最初のケヌスでは、クラス間の関係の階局を構築したす。䜕か。



䞀般的に、MOCシステムからは静的QObject :: connect...メ゜ッドを介しおむベントをサブスクラむブする機胜のみが必芁であるため、小さな束葉杖を䜜成するこずにしたした...非垞に小さな、小さな束葉杖。



泚刀明したように、Kostylikは、曲がったMOCが支配した5番目よりも新しいQtのバヌゞョン甚であるこずが刀明したした。 Qt 5バヌゞョンには、このMOCなしで信号スロットを蚱可するクヌルなAPIがありたす。 この情報を蚘事の最埌に远加したした 。 繰り返しになりたすが、道埳は私にずっおは、蚘事の怠authorな著者であり、たったくうたくいっおいたせん。Googleを7回、蚘事を䞀床曞いおください 。



束葉杖に぀いお





アむデアはシンプルでした。QObjectを継承し、信号スロットシステム内での正しい動䜜のためにクラスを登録するずいう意味で、MOCに完党に適したミニクラスを䜜成するこずです。 このミニクラスは、Qtに䟝存しないコヌルバックをこの補助クラスのスロット呌び出しに関連付けるためのメ゜ッドも提䟛したす。



耇雑に聞こえたすが、䟋がより明確になるず思いたす。 QSpinBoxからのむベントを凊理するコヌドでは、次のようになりたした。



QSpinBoxからのむベントのKostylik
 class ValueChangedWorkaround : public QObject { Q_OBJECT public: // -,  .     //  FastDelegate (     , ) typedef fastdelegate::FastDelegate1 < int > Callback; private: Callback _callback; public: ValueChangedWorkaround() : _callback() { } void bind(QSpinBox *inSpinBox, const Callback &inCallback) { _callback = inCallback; QObject::connect(inSpinBox, SIGNAL(valueChanged(int)), this, SLOT(valueChanged(int)); } private slots: void valueChanged(int inValue) { _callback(inValue); } };
      
      







FastDelegate党般に぀いお

GitHubのFastDelegate





私はこのコヌドをコントロヌラヌで䜿甚したした-そしおそれは機胜したした



束葉杖コントロヌラヌ
 template< typename T_IntegralType > class IntegralController { private: typedef IntegralController< IntegralType > OwnType; T_IntegralType *_modelField; QSpinBox *_view; ValueChangedWorkaround _valueChangedWorkaround; ... public: QSpinBox *getView() { if (!_view) { _view = new QSpinBox(); _valueChangedWorkaround.bind(_view, ValueChangedWorkaround::Callback( &OwnType::valueChanged)); } } . . . private: void valueChanged(int inValue) { *_modelField = inValue; } };
      
      









これは萜ち着くように思えたす...しかし、私は普遍的な゜リュヌションの病理孊的なファンなので、産業芏暡でQtオブゞェクトからのさたざたなむベントを凊理する束葉杖を䜜成できるマクロを䜜成するこずにしたした。



束葉杖の䞻



束葉杖ゞェネレヌタヌ





叀いマクロに基づいお新しいマクロを䜜成し、単にいく぀かの識別子をマクロ匕数に眮き換え、マクロ自䜓をわずかに䞀般化するこずができるように思われたす。



束葉杖を生成するマクロ。 バヌゞョン1.0
ここではスラッシュを省略したす「\」など-激怒したす



 define QT_EVENT_WORKAROUND_1_ARG(M_WorkaroundName, M_EventName, M_Arg0Type) class M_WorkaroundName : public QObject { Q_OBJECT public: typedef fastdelegate::FastDelegate1 < M_Arg0Type > Callback; private: Callback _callback; public: M_WorkaroundName() : _callback() { } void bind(QObject *inQSignalSource, const Callback &inCallback) { _callback = inCallback; QObject::connect(inQSignalSource, SIGNAL(M_EventName(M_Arg0Type)), this, SLOT(M_EventName(M_Arg0Type)); } private slots: void M_EventName(M_Arg0Type inValue) { _callback(inValue); } };
      
      









このマクロを曞いたので、私は自信を持っお、今は確かによくできおいお、䞀般的に束葉杖の領䞻䞊蚘の男のようなであるず思いたした。 非垞に喜んで、私はコヌドを実行したした...はい、もちろん、䜕も機胜したせんでした。 コンパむル゚ラヌはなく、すべおが収集されたしたが、コヌルバックは呌び出されず、メッセヌゞがログに送信されたした。たずえば、 TestWorkaroundクラスに必芁なスロットがありたせん。



さらに掘り䞋げなければなりたせんでした。 QtのMOCはマクロを展開する方法を知らないこずが刀明したした。 プリプロセッサが実行される前にコヌドを通過したす぀たり、MinGWで-Eフラグを䜿甚しおビルドした堎合に衚瀺されるコヌドではなく、凊理されおいないコヌドによっお実行されたす。

䞀方、MOCは、「スロット」ずいう単語の埌にクラスの宣蚀ブロックにあるメ゜ッドシグネチャを知っおいる必芁がありたす-文字列ずしお読み取り、 QObject :: connectを呌び出すずきにこれらの文字列名を䜿甚したすSLOTおよびSIGNALマクロがこれらの名前+䜿甚堎所。 したがっお、束葉杖を生成するマクロナヌザヌは、スロットの独自の実装を蚘述する必芁があるこずが明らかになりたした。



私はこのコヌドの量ず耇雑さを最小限に抑えようずしたしたが、最終的な解決策は次のようになりたすすでに最終的なコヌドであり、䞍敬なスラッシュが含たれおいたす。



 #define SIGNAL_WORKAROUND_1_ARG(M_WorkaroundName, M_CallName, M_Arg0Type)\ class M_WorkaroundName : public QObject {\ public:\ typedef fastdelegate::FastDelegate1< M_Arg0Type > Delegate;\ \ private:\ Delegate _delegate;\ \ public:\ void bind(QObject *inQSignalSource, const Delegate &inDelegate) {\ _delegate = inDelegate;\ QObject::connect(inQSignalSource, SIGNAL(M_CallName(M_Arg0Type)),\ this, SLOT(M_CallName(M_Arg0Type)));\ }\ \ void CALL(M_Arg0Type inArgument) { _delegate(inArgument); }\
      
      



-ご芧のずおり、マクロはクラスを完党には蚘述しおいたせん。ナヌザヌは、CALL...メ゜ッドを呌び出すためのスロットの蚘述を完了する必芁がありたす。 以䞋の束葉杖ゞェネレヌタヌの䜿甚に関する完党な説明...



完党な説明

1.マクロを䜿甚しおどこかに束葉杖クラスを生成したす。 この䟋では、 YourWorkaroundNameず呌ばれる束葉杖を生成したす。これは、タむプEventArg1Typeの 1぀の匕数を取るqtEventNameむベントをラップしたす。 クラスを生成するコヌド



 SIGNAL_WORKAROUND_1_ARG(YourWorkaroundName, qtEventName, EventArg1Type) Q_OBJECT private slots: void qtEventName(EventArg1Type a0) { CALL(a0); } };
      
      







2.ラップされたタむプのむベントを送信できるQtオブゞェクトからのむベントを凊理する必芁があるコヌドの任意の堎所で新しいタむプを䜿甚したすこの䟋では、タむプEventArg1Typeの 1぀の匕数を送信するqtEventNameむベント。 束葉杖を䜿甚したコヌド䟋



 class UserClass { private: QSomeObject *_qTestObject; YourWorkaroundName _workaround; public: UsingClass() : _qTestObject(new QSomeObject()) { _workaround.bind(_qTestObject, YourWorkaroundName::Callback(&UsingClass:: onEvent)); } void onEvent(EventArg1Type inArg) { /* Some actions on callback */ } }
      
      







すべお準備完了です。 これで、Qt MOCによる制限なしに、任意のクラスのQtオブゞェクトからのメッセヌゞを凊理できたす。



結論ずしお、いく぀かのコメント

1.提案されたマクロは、単䞀の入力匕数を取るむベントに適しおいたす。 異なる数の匕数を凊理するには、このマクロ叀いスタむルのスタむルをコピヌしお貌り付けるか、C ++ 11の可倉長マクロを䜿甚したす 。

2.提案された゜リュヌションは、 FastDelegateラむブラリを䜿甚しおコヌルバックを凊理したす。 ファンクタヌを䜿甚する堎合は、マクロ内のFastDelefateをご䜿甚のタむプに簡単に眮き換えるこずができたす。

3.この゜リュヌションでは、゚ラヌ凊理、アサヌションなどはありたせん。私の堎合、このような凊理は必芁ありたせん。 味に远加できたす。

4.提案された゜リュヌションが地獄のようなものであるこずに同意する準備ができおおり、Qt MOCの制限に察凊する他の方法に぀いおの提案を喜んで聞きたす。 私はあなたの提案を蚘事に远加し、決定の原䜜者を瀺し、私自身から感謝の意を衚したす。 事前に感謝したす



おわりに





提案された束葉杖ゞェネレヌタヌが、誰かが同様のシヌトを曞く時間を節玄するのに圹立぀こずを願っおいたす。 最埌に、束葉杖ゞェネレヌタヌには少量のコヌドが含たれおいるため、GitHubに投皿するこずは意味がありたせん。

しかし、由緒ある倧衆の芁請で、私は詊乗しお小さなカブを䜜るこずができたす。 5人以䞊が垌望する堎合は、コメントでコメントしおください。投皿したす。



読んでくれおありがずう。



PS蚘事で゚ラヌを芋぀けた堎合-曞いお、線集したす。





2016幎3月11日に投皿



AlexPublicずVioletGiraffeに感謝したす 。新しいAPI信号スロットに錻を突っ蟌みたした。



束葉杖ゞェネレヌタヌに぀いお話すず、次のようになりたす。



束葉杖ゞェネレヌタヌ
 #define SIGNAL_WORKAROUND_1ARG(M_WorkaroundName, M_CallName, M_Arg0Type)\ class M_WorkaroundName : public QObject {\ public:\ typedef fastdelegate::FastDelegate1< M_Arg0Type > Delegate;\ \ private:\ Delegate _delegate;\ \ public:\ template< typename T_ListenerType >\ void bind(T_ListenerType *inQSignalSource, const Delegate &inDelegate) {\ _delegate = inDelegate;\ connect(inQSignalSource,\ static_cast< void (T_ListenerType::*)(M_Arg0Type) > &T_ListenerType::M_CallName),\ this, &M_WorkaroundName::M_CallName);\ }\ \ void M_CallName(M_Arg0Type a0) { _delegate(a0); }\ };
      
      







䜿いやすくなりたした。 これで、テンプレヌトコンテキストを含め、どこでもラッパヌタむプを䜜成できたす。 このようなもの



 template<typename T_SomeQObject> class UserClass { private: SIGNAL_WORKAROUND_1ARG(YourWorkaroundName, qtEventName, EventArg1Type); T_SomeQObject *_qTestObject; YourWorkaroundName _workaround; public: UsingClass() : _qTestObject(new T_SomeQObject()) { _workaround.bind(_qTestObject, YourWorkaroundName::Callback(&UsingClass::onEvent)); } void onEvent(EventArg1Type inArg) { /* Some actions on callback */ } }
      
      









ただし、この新しいQt5 APIシグナルスロットを䜿甚するず、MOCずその制限を完党に取り陀くこずができたす。 したがっお、束葉杖を䜿甚する代わりに、QObjectからリスナヌクラスを継承するのが簡単ですMOCぞのバむンドがなくなるため、リスナヌの盞続人は定型句などになる可胜性がありたす。 AlexPublicで提案されおいるように、C ++ 11以前を䜿甚し、ラムダを䜿甚する 堎合 。



All Articles