C ++で最も単純なデリゲート

ロゴマーク C#にはデリゲートがあります。 Pythonにはデリゲートがあります。 JavaScriptにはデリゲートがあります。 Javaには、それらを実行するクロージャー関数があります。 また、C ++にはO_Oデリゲートはありません。 多くの才能あるプログラマーは、シグスロット、ブースト::関数、およびその他の貴重で必要なライブラリを開発して使用することで、この欠点にうまく対処しています。 残念ながら、ほとんどの実装は使用方法だけでなく、適用されたテンプレートマジックの壮大な複雑さも異なります。 ブースト::関数のソースを研究する際に、髪の毛が勝手に立ち上がらないように、C ++デリゲートを最も単純で不器用な方法で実装する方法を示すこの短い記事を書きました。 説明されている実装は例示であり、多くの欠点があり、深刻なプロジェクトではほとんど使用できません-しかし、それは可能な限り単純であり、3階建てのsigslotsテンプレートを分析せずに主題領域に精通することができます:)。







なぜそれが必要ですか





実践が示すように、大規模なプログラムが多数の小さくて最大限に独立した部分で構成されている場合、プログラムの開発、修復、変更が容易になります。 現代のオブジェクト指向プログラミング言語は、オブジェクトを断片として提供します。 通常、オブジェクトは、必要かつ有用な処理を行うクラスのインスタンスです。 1つのbaaaaaaaaaaa alクラス-'god object antipattern'からプログラム全体を作成できます。数年後、変更を加えると、6番目のレベルの呪いに変わります。 そして、プログラムを互いに可能な限り独立した小さなクラスに分割することができます。そうすれば、全員がドライで快適になります。 しかし、この方法でプログラムを壊すと、2番目の質問が発生します。これらのクラスはどのように相互作用しますか ここで、プログラマーは、プログラミング言語によって提供される分解の助けになります。 C ++の最も単純な分解ツールは、クラスをグローバルにし、メソッドを直接呼び出すことです。 このアプローチには欠点がないわけではありません。時間の経過とともにだれが誰を引き起こしているのかを理解することがますます難しくなり、数年後にはこのアプローチは単一のクラスを使用するのと同じ結果につながります。 Holy Inquisitionによって承認されたオプションは、通信する必要があるクラスへのクラスポインターを渡すことです。 さらに、ポインターは単純ではなく、インターフェイス上にあることが望ましいです。そうすれば、時間の経過に伴うプログラムの変更と開発がはるかに簡単になります。

ただし、インターフェースは特効薬ではありません(スプーンのような弾丸はまったくないと言う人もいます)。 たとえば、ボタンをクリックしたことをボタンに通知するために、オブジェクトが数回の対話のみを必要とする場合、これらの目的のための別個のインターフェースの実装は、目立った量のコード行を取ります。 また、1つのオブジェクトに他の複数のオブジェクトから何かを通知する必要がある場合、インターフェイスは問題を解決しません。インターフェイスに基づいてサブスクリプションシートを作成および維持することも、コードの最小行数ではありません。

動的プログラミング言語では、デリゲートはインターフェイスと競合します。 通常、デリゲートはC ++の「関数ポインタ」に非常に似ていますが、主な違いはデリゲートが任意のオブジェクトメソッドを指すことができることです。 コードの観点から見ると、デリゲートの使用は通常次のようになります。

委任の例

インターフェイスを使用した同じことは、デリゲートの必要性と利点を示しています。

インターフェースの例



C ++デリゲートはどのように見えるべきですか?





任意のクラスメソッドに関連付けられ、関数として呼び出されるものでなければなりません。 C ++では、これはオーバーロードされた呼び出し演算子を持つクラスとしてのみ実行できます(このようなクラスは通常ファンクターと呼ばれます)。 C#では、「+ = "演算子はデリゲートとメソッドを関連付けるために使用されますが、C ++ではこれは残念ながら不可能です-" + = "演算子はパラメータを1つしか取りませんが、関数へのポインタ-C ++のクラスのメンバーは2つのパラメータによって決定されます。 したがって、C ++でデリゲートを使用すると、次のようになります。

委任使用



1つの引数の最も単純な実装





この動作を実装してみましょう。 デリゲートが任意のクラスの任意のメソッドへのポインターを取得できるようにするには、独自のConnect()メソッドを明示的にボイラープレートにする必要があります。 デリゲート自体をステレオタイプにしないのはなぜですか? デリゲートを作成するときに特定のクラスを指定する必要があるため、デリゲートを任意のクラスに関連付ける機能と矛盾するためです。 また、デリゲートには、テンプレートの1つであるオーバーロードされた呼び出し演算子が必要です。これにより、デリゲートは、関連付けられたメソッドと同じタイプの引数で呼び出すことができます。 したがって、デリゲートの空白は次のようになります。

スケルトンを委任する

Connect()メソッドは、任意のクラスのメソッドへのポインターを使用して呼び出すことができ、呼び出し演算子を使用すると、デリゲート自体を任意の引数で呼び出すことができます。 ここで必要なことは、i_classクラスとi_methodメソッドへのポインターを何らかの方法で保存して、それらをcallステートメントで使用できるようにすることだけです。 これは何度も発生します。デリゲート自身はTとMについて何も知らず、フィールドとして保存する方法を知らないはずです(これらはテンプレートメソッドへの引数であり、同じデリゲートの異なるクラスとメソッドで何度も呼び出すことができます)。 どうする ちょっとしたテンプレートの魔法に目を向ける必要があります(信じてください、これらはboost:機能で使用されるものと比較して、第一レベルの呪文です)。 C ++でテンプレート引数を保存する唯一の方法は、これらの引数でパラメーター化されるテンプレートクラスのインスタンスを作成し、それに応じてそれらを記憶することです。 したがって、TとMを記憶できるテンプレートクラスが必要です。このクラスへのポインターを保存するには、テンプレートパラメーターを持たないインターフェイスから継承する必要があります。

コンテナで委任する

最初の近似では、これは任意のクラスのメソッドに対してConnect()を呼び出し、引数を記憶するのに十分です。 しかし、思い出すことは戦いの半分です。 後半は、引数を渡してストアドメソッドを呼び出すことです。 呼び出しには多少の困難があります-クラスメソッドへのポインターをIContainerインターフェイスとして保存しました-ユーザーが演算子()に渡した任意の型パラメーターでクラスメソッドを呼び出す方法は?

引数パス問題

最も簡単な方法は、クラスメソッドへのポインターに対して行ったのと同じ方法でコンテナーに渡された引数を覚え、m_containerに引数「inside」でコンテナーを渡し、次にdynamic_cast <>()を使用してコンテナーから引数を「取り出す」ことです。 怖いですが、コードはとてもシンプルです:

引数パスソリューションドラフト

動作中のデリゲートへの途中の最後の問題は、コンテナから引数を取得することです。 これを行うには、引数のタイプを知る必要があります。 しかし、結局のところ、クラスメソッドへのポインターを格納するコンテナー内では、引数の型がわかりませんか? 引数のタイプはわかりませんが、メソッドのシグネチャ、保存するポインタはわかります。 したがって、必要なのは、署名から引数の型を抽出することだけです。 これを行うには、私の記事で説明されているテンプレートの部分的な特殊化を使用するトリックを使用する必要があります。 次のようになります。

引数渡しのトリック

実際には、それだけです。 結果のデリゲートは、任意のクラスのメソッドへのポインターを保存し、関数呼び出しの構文で呼び出すことができます。



任意の数の引数の実装





上記の実装には1つの欠点があります。引数が1つだけのメソッドでのみ機能します。 しかし、メソッドが引数を取らない場合はどうでしょうか? または、2つまたは3つの引数を取りますか? boost、sigslots、およびQtでC ++に使用されるソリューションは非常に簡単です。サポートされているすべてのケースについて、コードの対応する部分をコピーして貼り付けます。 通常、2つのオブジェクトをリンクするときに4つ以上の引数を渡す必要がある場合は、アーキテクチャに問題があり、おそらくWinAPI feat CreateWindow()O_Oを繰り返しているため、0〜4つの引数をサポートします。 最大2つの引数をサポートする既製の実装コードと使用例を以下に示します。 私はそれが実例であり、非常に単純化されていることを思い出します。 多くのチェックが欠落しており、変数名はコンパクトさなどのために寄付されます。 実稼働環境では、boost :: function :)のようなものを使用することをお勧めします



#include <assert.h> //     2- . struct NIL {}; class IArguments { public: virtual ~IArguments() {} }; template< class T1 = NIL, class T2 = NIL > class Arguments : public IArguments { public: Arguments() {} public: Arguments( T1 i_arg1 ) : arg1( i_arg1 ) {} public: Arguments( T1 i_arg1, T2 i_arg2 ) : arg1( i_arg1 ), arg2( i_arg2 ) {} public: T1 arg1; T2 arg2; }; //      . class IContainer { public: virtual void Call( IArguments* ) = 0; }; template< class T, class M > class Container : public IContainer {}; //     . template< class T > class Container< T, void (T::*)(void) > : public IContainer { typedef void (T::*M)(void); public: Container( T* c, M m ) : m_class( c ), m_method( m ) {} private: T* m_class; M m_method; public: void Call( IArguments* i_args ) { (m_class->*m_method)(); } }; //      . template< class T, class A1 > class Container< T, void (T::*)(A1) > : public IContainer { typedef void (T::*M)(A1); typedef Arguments<A1> A; public: Container( T* c, M m ) : m_class( c ), m_method( m ) {} private: T* m_class; M m_method; public: void Call( IArguments* i_args ) { A* a = dynamic_cast< A* >( i_args ); assert( a ); if( a ) (m_class->*m_method)( a->arg1 ); } }; //       template< class T, class A1, class A2 > class Container< T, void (T::*)(A1,A2) > : public IContainer { typedef void (T::*M)(A1,A2); typedef Arguments<A1,A2> A; public: Container( T* c, M m ) : m_class( c ), m_method( m ) {} private: T* m_class; M m_method; public: void Call( IArguments* i_args ) { A* a = dynamic_cast< A* >( i_args ); assert( a ); if( a ) (m_class->*m_method)( a->arg1, a->arg2 ); } }; //  . class Delegate { public: Delegate() : m_container( 0 ) {} ~Delegate() { if( m_container ) delete m_container; } template< class T, class U > void Connect( T* i_class, U i_method ) { if( m_container ) delete m_container; m_container = new Container< T, U >( i_class, i_method ); } void operator()() { m_container->Call( & Arguments<>() ); } template< class T1 > void operator()( T1 i_arg1 ) { m_container->Call( & Arguments< T1 >( i_arg1 ) ); } template< class T1, class T2 > void operator()( T1 i_arg1, T2 i_arg2 ) { m_container->Call( & Arguments< T1, T2 >( i_arg1, i_arg2 ) ); } private: IContainer* m_container; }; class Victim { public: void Foo() {} void Bar( int ) {} }; int main() { Victim test_class; Delegate test_delegate; test_delegate.Connect( & test_class, & Victim::Foo ); test_delegate(); test_delegate.Connect( & test_class, & Victim::Bar ); test_delegate( 10 ); return 0; }
      
      










All Articles