std :: function(boost :: function)を記述します

std :: functionおよびboost :: functionクラスは、関数および関数オブジェクトの高レベルラッパーです。 このようなクラスのオブジェクトを使用すると、コールバック呼び出しを作成する場合などに便利な、特定の署名を使用して関数とファンクターを格納および呼び出すことができます(たとえば、複数のハンドラーを登録でき、これは通常の関数または特定の演算子=を持つオブジェクトのいずれかです)



この機能の実装方法に興味がある場合は、してください





簡単な紹介、使用例



boost ::関数とstd ::関数に慣れていない場合は、 ここここでそれらに慣れることができます。

std ::関数はc ++ 11言語標準の一部であり、gcc-4.7およびmsvc-2012コンパイラーがサポートしています(おそらく以前のバージョンもサポートしています)。 原則として、この記事のフレームワークでは、boostからの実装と標準の実装を完全に同一と見なすことができるため、どの実装でも使用できます。



実際の使用例:

int func1() { return 0; } struct callable { int operator() () { return 1; } }; ///... boost::function<int (void)> x; x = func1; int res = x(); //  0    callable c; x = c; res = x(); //  1   
      
      







実装自体に渡します



実装自体はいくつかの段階で行われます。



最も単純な実装、タイプ消去の概念


このクラスの実装は、 Type Erasureパターンに基づいています。詳細はこちらをご覧ください。その目的は、同様の機能(呼び出しなど)を提供する1つのインターフェースの背後にあるさまざまなエンティティ(オブジェクト、ポインターなど)を「隠す」ことです3つの引数を持つ関数)。 型消去は、実行時ポリモーフィズムとコンパイル時ポリモーフィズムをリンクするブリッジと考えることもできます。



したがって、実装に移ります。

C ++ 11標準の可変長テンプレートを使用します。 たとえば、gccはバージョン4.3からすでにこの機能をサポートしているため、安全に使用できます。



オリジナルではなく、クラス関数を呼び出します。 明らかに、クラスはテンプレートであり、1つのテンプレートパラメーター-呼び出された関数の署名(タイプ)を持つことも明らかです。 テンプレートの一般的な実装はありません。すべての作業は、テンプレートの部分的な専門化で行われます。 引数のタイプを使用し、署名から値を返すことができるように、部分的な特殊化が必要です。

実装自体:

 template <typename UnusedType> class function; template <typename ReturnType, typename ... ArgumentTypes> class function <ReturnType (ArgumentTypes ...)> { public: function() : mInvoker() {} template <typename FunctionT> function(FunctionT f) : mInvoker(new free_function_holder<FunctionT>(f)) {} ReturnType operator ()(ArgumentTypes ... args) { return mInvoker->invoke(args ...); } private: class function_holder_base { public: function_holder_base() {} virtual ~function_holder_base() {} virtual ReturnType invoke(ArgumentTypes ... args) = 0; }; typedef std::auto_ptr<function_holder_base> invoker_t; template <typename FunctionT> class free_function_holder : public function_holder_base { public: free_function_holder(FunctionT func) : function_holder_base(), mFunction(func) {} virtual ReturnType invoke(ArgumentTypes ... args) { return mFunction(args ...); } private: FunctionT mFunction; }; invoker_t mInvoker; };
      
      





関数クラスは、関数シグネチャに対応する演算子()を定義し、function_holder_baseクラスのinvokeメソッドに制御を渡します。 このクラスには仮想呼び出し関数があり、指定された署名にも一致します(暗黙のthisパラメーターを除く)。

関数クラスには、1つの引数を取るテンプレートコンストラクターもあります;このコンストラクターでは、function_holder_baseクラスの子孫free_function_holderが作成されます。 この子孫はテンプレートクラスです-渡された引数(通常はファンクターまたは関数へのポインター)を格納します。 また、指定された引数で保存されたファンクターを呼び出すinvokeメソッドを定義します。



ここでは、C ++のテンプレートのいくつかの機能に注意する必要があります。





原則として、std :: functionとboost :: functionの実行可能な類似物を得たので、次のコードを記述できます。
 int func2(const int * x, int y) { return (*x) + y; } ///... typedef function<int (const int * , int)> int_function_with_two_args_t; int_function_with_two_args_t f2(func2); int x = 10; cout << "calling function with signature int (const int * , int): " << f2(&x, 20) << endl;
      
      





クラスの改善に進みます。



通常の関数ポインターの動作をコピーする-代入演算子とコピーコンストラクター


オブジェクトをコピーまたは割り当てるためには、基本クラスfunction_holder_baseへのポインターをコピー(クローン)できなければなりません。 これを行うには、このクラスのインターフェイスを次のように拡張します。

  class function_holder_base { public: function_holder_base() {} virtual ~function_holder_base(){} virtual ReturnType invoke(ArgumentTypes ... args) = 0; virtual std::auto_ptr<function_holder_base> clone() = 0; private: function_holder_base(const function_holder_base & ); void operator = (const function_holder_base &); };
      
      





クラスをコピー不可にし(privateセクションで対応する演算子とコンストラクターを宣言します)、cloneメソッドを追加して、相続人自身が正しいクローニング戦略を決定するようにします。



また、代入演算子とコピーコンストラクターが関数クラスに追加されます。

  function(const function & other) : mInvoker(other.mInvoker->clone()) {} function & operator = (const function & other) { mInvoker = other.mInvoker->clone(); }
      
      





ここでは、auto_ptrとその破壊的な割り当てを使用します。



後継者-free_function_holderにcloneメソッドの実装を記述することは残っています:

  typedef free_function_holder<FunctionT> self_type; virtual invoker_t clone() { return invoker_t(new self_type(mFunction)); }
      
      







以上で、クラスは通常の関数ポインターのように動作するようになりました。これを行うことができます。

 int func1() { return 0; } ///... typedef function<int (void)> int_function_t; int_function_t f1(func1); cout << "calling function with signature int (void): " << f1() << endl; int_function_t f2; f2 = f1; cout << "calling function after assignment operator with signature int (void): " << f2() << endl; int_function_t f3(f2); cout << "calling function after copying ctor with signature int (void): " << f3() << endl;
      
      







最後の部分に進みます。

メンバー関数ポインターのサポートを追加します


メソッドポインターのサポートはかなり制限されていました:メソッドポインターが適用されるオブジェクトは、値(参照(constではなくconst)およびポインター(constではなくconst)でのみ)に渡すことができますが、基本的にはこの例で十分です。



boost(std)と関数の実装を使用する場合、最初の引数はそれぞれメソッドへのポインターが適用されるオブジェクトでなければならないという規則に従います。引数は、オブジェクト自体とメソッド引数の2つのタイプに分かれています。 したがって、引数の数が厳密に0より大きいことを保証しており、これをさらに使用します。

  template <typename FunctionType, typename ClassType, typename ... RestArgumentTypes> class member_function_holder : public function_holder_base { public: typedef FunctionType ClassType::* member_function_signature_t; member_function_holder(member_function_signature_t f) : mFunction(f){} virtual ReturnType invoke(ClassType obj, RestArgumentTypes ... restArgs) { return (obj.*mFunction)(restArgs ...); } virtual invoker_t clone() { return invoker_t(new member_function_holder(mFunction)); } private: member_function_signature_t mFunction; };
      
      





メソッドへのポインタを取るコンストラクターの実装は簡単です。

  template <typename FunctionType, typename ClassType> function(FunctionType ClassType::* f) : mInvoker(new member_function_holder<FunctionType, ArgumentTypes ...>(f)) {}
      
      





メソッドへのポインタ用に、function_holder_baseの子孫をもう1つ作成しました。 ここでは、可変長テンプレート機能が使用されます:可変長の型(楕円で指定)を固定部分に分割し、可変長の残りの部分(固定型のサイズ分だけ型の数が少なくなる)に分割できます。 コンストラクターでは、1つの型(関数の署名)と任意の部分(残りはすべての引数の型)で構成される固定部分をテンプレートパラメーターに渡します。member_function_holderの実装では、固定部分が2つの要素(関数、クラスの署名)で構成される必要があります。メソッドの場所、および呼び出しの直接の引数(ここでは、上記を使用するだけで、すべての引数の数が厳密に0より大きいことを保証します)。 したがって、コンストラクターにメソッドへのポインターを保存し、invokeメソッドの実装で呼び出します。



型を「メソッドへのポインタ」と宣言する非常に不便で非直感的な方法と、そのようなメソッドを呼び出す方法について別々に言いたいと思います。 この機会に、 C ++ FAQには、メソッドへのポインタを操作するときに頭痛の量を最小限に抑える方法に関する警告があります(この式を正しく書く方法を30分間グーグルで調べました)




以下に使用例を示します。

 struct Foo { int smth(int x) { return x + 1; } }; ///... typedef function<int (Foo, int)> member_function_t; member_function_t f1 = &Foo::smth; Foo foo; cout << "calling member function with signature int (int): " << f1(foo, 5) << endl;
      
      







おわりに



可変個引数テンプレートのサポートのおかげで、関数の実装はかなり簡潔であることが判明しました。同じブーストで、古いコンパイラ(可変個テンプレートを持たない)をサポートする必要があるため、この機能はboost.preprocessorを使用して実装されます(引数の数に制限があり、デフォルトでは10に変更できます)適切な定義を定義することにより:BOOST_FUNCTION_MAX_ARGS)。 大まかに言って、実装は1つの引数を持つ関数に対して行われ、その後、前処理マジックを使用してより多くの引数に「クローン」されました。



完全な例はこちらです。



All Articles