C++のデリゲートの一般的な考え方は何ですか?それらは何ですか、どのように使用され、何のために使用されますか?
最初に「ブラックボックス」の方法でそれらについて学びたいと思いますが、これらの事柄の内臓に関する少しの情報も素晴らしいでしょう。
これはC++の最も純粋な、または最もクリーンなものではありませんが、私が作業しているコードベースにはそれらが豊富にあることがわかります。私はそれらを十分に理解したいので、それらを使用するだけで、恐ろしいネストされたテンプレートの恐ろしさを詳しく調べる必要はありません。
これら2つの The Code Project の記事は、私が何を意味するかを説明していますが、特に簡潔ではありません:
C++でデリゲートを達成するには、信じられないほど多くの選択肢があります。ここに私の頭に浮かんだものがあります。
オプション1:ファンクタ:
operator()
を実装することにより、関数オブジェクトを作成できます
struct Functor
{
// Normal class/struct members
int operator()(double d) // Arbitrary return types and parameter list
{
return (int) d + 1;
}
};
// Use:
Functor f;
int i = f(3.14);
オプション2:ラムダ式( C++ 11 のみ)
// Syntax is roughly: [capture](parameter list) -> return type {block}
// Some shortcuts exist
auto func = [](int i) -> double { return 2*i/1.15; };
double d = func(1);
オプション3:関数ポインター
int f(double d) { ... }
typedef int (*MyFuncT) (double d);
MyFuncT fp = &f;
int a = fp(3.14);
オプション4:メンバー関数へのポインター(最速のソリューション)
Fast C++ Delegate(on The Code Project )を参照してください。
struct DelegateList
{
int f1(double d) { }
int f2(double d) { }
};
typedef int (DelegateList::* DelegateType)(double d);
DelegateType d = &DelegateList::f1;
DelegateList list;
int a = (list.*d)(3.14);
オプション5: std :: function
(または、標準ライブラリがサポートしていない場合はboost::function
)。遅いですが、最も柔軟性があります。
#include <functional>
std::function<int(double)> f = [can be set to about anything in this answer]
// Usually more useful as a parameter to another functions
オプション6:バインディング( std :: bind )を使用
いくつかのパラメーターを事前に設定できます。たとえば、メンバー関数を呼び出すのに便利です。
struct MyClass
{
int DoStuff(double d); // actually a DoStuff(MyClass* this, double d)
};
std::function<int(double d)> f = std::bind(&MyClass::DoStuff, this, std::placeholders::_1);
// auto f = std::bind(...); in C++11
オプション7:テンプレート
引数リストと一致する限り、何でも受け入れます。
template <class FunctionT>
int DoSomething(FunctionT func)
{
return func(3.14);
}
デリゲートは、オブジェクトインスタンスへのポインターまたは参照をラップするクラスであり、そのオブジェクトインスタンスで呼び出されるオブジェクトのクラスのメンバーメソッドであり、その呼び出しをトリガーするメソッドを提供します。
以下に例を示します。
template <class T>
class CCallback
{
public:
typedef void (T::*fn)( int anArg );
CCallback(T& trg, fn op)
: m_rTarget(trg)
, m_Operation(op)
{
}
void Execute( int in )
{
(m_rTarget.*m_Operation)( in );
}
private:
CCallback();
CCallback( const CCallback& );
T& m_rTarget;
fn m_Operation;
};
class A
{
public:
virtual void Fn( int i )
{
}
};
int main( int /*argc*/, char * /*argv*/ )
{
A a;
CCallback<A> cbk( a, &A::Fn );
cbk.Execute( 3 );
}
C++デリゲートの実装の必要性は、C++コミュニティに対する長期にわたる当惑です。すべてのC++プログラマーはそれらを持ちたいと思うので、次の事実にもかかわらず、最終的にそれらを使用します。
std::function()
はヒープ操作を使用します(深刻な組み込みプログラミングには手が届きません)。
他のすべての実装は、移植性または標準への準拠の程度に応じて、程度の差はあります(ここおよびcodeprojectでさまざまなデリゲート実装を調べて確認してください)。野生のreinterpret_castsを使用しない実装、ネストされたクラス「プロトタイプ」は、ユーザーによって渡されたものと同じサイズの関数ポインターを生成することを望んでいません。今回は、別のクラスまたは類似の日陰のテクニックを継承しています。それを構築した実装者にとっては素晴らしい成果ですが、C++の進化についての悲しい証です。
まれにしか指摘されていませんが、現在、3つのC++標準リビジョンを超えて、デリゲートが適切に対処されていません。 (または、単純なデリゲートの実装を可能にする言語機能の欠如。)
C++ 11ラムダ関数が標準で定義されている方法(各ラムダは匿名の異なる型を持っています)で、状況はいくつかのユースケースでのみ改善されています。ただし、(DLL)ライブラリAPIでデリゲートを使用するユースケースでは、ラムダaloneはまだ使用できません。ここでの一般的なテクニックは、最初にラムダをstd :: functionにパックしてから、APIに渡すことです。
非常に簡単に言えば、デリゲートは、関数ポインタがどのように動作するべきかという機能を提供します。 C++の関数ポインターには多くの制限があります。デリゲートは、舞台裏のテンプレートの厄介さを使用して、必要な方法で機能するテンプレートクラス関数ポインター型のものを作成します。
すなわち-あなたはそれらを与えられた関数を指すように設定することができ、いつでもどこでも好きなときにそれらを渡すことができます。
ここにいくつかの非常に良い例があります:
C++のデリゲートのオプションは、ここでは特に言及していませんが、関数ptrとコンテキスト引数を使用してCスタイルで行うことです。これはおそらく、この質問をする多くの人が避けようとしているのと同じパターンです。しかし、このパターンは移植可能で効率的であり、組み込みコードやカーネルコードで使用できます。
class SomeClass
{
in someMember;
int SomeFunc( int);
static void EventFunc( void* this__, int a, int b, int c)
{
SomeClass* this_ = static_cast< SomeClass*>( this__);
this_->SomeFunc( a );
this_->someMember = b + c;
}
};
void ScheduleEvent( void (*delegateFunc)( void*, int, int, int), void* delegateContext);
...
SomeClass* someObject = new SomeObject();
...
ScheduleEvent( SomeClass::EventFunc, someObject);
...
標準C++の関数オブジェクトに相当するWindowsランタイム。関数全体をパラメーターとして使用できます(実際には関数ポインターです)。主にイベントと組み合わせて使用されます。デリゲートは、イベントハンドラーが多く満たす契約を表します。関数ポインターの機能を促進します。