人々は通常、いくつかのパターンのいずれかを使用します。
継承。つまり、コールバックを含む抽象クラスを定義します。次に、それへのポインタ/参照を取得します。つまり、誰でもこのコールバックを継承して提供できます。
class Foo {
virtual void MyCallback(...) = 0;
virtual ~Foo();
};
class Base {
std::auto_ptr<Foo> ptr;
void something(...) {
ptr->MyCallback(...);
}
Base& SetCallback(Foo* newfoo) { ptr = newfoo; return *this; }
Foo* GetCallback() { return ptr; }
};
また継承。つまり、ルート クラスは抽象であり、ユーザーは具体的なクラスと専用のコールバック オブジェクトを持つのではなく、それを継承してコールバックを定義します。
class Foo {
virtual void MyCallback(...) = 0;
...
};
class RealFoo : Foo {
virtual void MyCallback(...) { ... }
};
さらに継承 - 静的。このように、テンプレートを使用してオブジェクトの動作を変更できます。これは 2 番目のオプションに似ていますが、実行時ではなくコンパイル時に機能するため、コンテキストに応じてさまざまなメリットとデメリットが生じる可能性があります。
template<typename T> class Foo {
void MyCallback(...) {
T::MyCallback(...);
}
};
class RealFoo : Foo<RealFoo> {
void MyCallback(...) {
...
}
};
メンバー関数ポインターまたは通常の関数ポインターを取得して使用できます
class Foo {
void (*callback)(...);
void something(...) { callback(...); }
Foo& SetCallback( void(*newcallback)(...) ) { callback = newcallback; return *this; }
void (*)(...) GetCallback() { return callback; }
};
関数オブジェクトがあります-それらは operator() をオーバーロードします。現在 std::/boost:: 関数で提供されている機能的なラッパーを使用または作成する必要がありますが、ここでは単純なものも示します。これは最初の概念に似ていますが、実装を隠し、他のさまざまなソリューションを受け入れます。私は通常、これをコールバック メソッドとして使用しています。
class Foo {
virtual ... Call(...) = 0;
virtual ~Foo();
};
class Base {
std::auto_ptr<Foo> callback;
template<typename T> Base& SetCallback(T t) {
struct NewFoo : Foo {
T t;
NewFoo(T newt) : t(newt) {}
... Call(...) { return t(...); }
};
callback = new NewFoo<T>(t);
return this;
}
Foo* GetCallback() { return callback; }
void dosomething() { callback->Call(...); }
};
適切なソリューションは、主にコンテキストによって異なります。C スタイルの API を公開する必要がある場合は、関数ポインターが唯一の方法です (ユーザー引数には void* を思い出してください)。実行時に変更する必要がある場合 (たとえば、プリコンパイル済みライブラリのコードを公開する場合)、ここでは静的継承を使用できません。
簡単なメモ: 私はそのコードを手作業で作成したため、完全ではなく (関数のアクセス修飾子など)、バグがいくつか含まれている可能性があります。これは例です。