C ++でコールバック関数を実装するときも、Cスタイルの関数ポインターを使用する必要があります。
void (*callbackFunc)(int);
またはstd :: functionを使用する必要があります:
std::function< void(int) > callbackFunc;
C ++でコールバック関数を実装するときも、Cスタイルの関数ポインターを使用する必要があります。
void (*callbackFunc)(int);
またはstd :: functionを使用する必要があります:
std::function< void(int) > callbackFunc;
回答:
要するに、std::function
やむを得ない理由がない限り使用してください。
関数ポインタには、一部のコンテキストをキャプチャできないという欠点があります。たとえば、ラムダ関数をいくつかのコンテキスト変数をキャプチャするコールバックとして渡すことはできません(ただし、何もキャプチャしなくても機能します)。オブジェクト(- this
ポインター)をキャプチャする必要があるため、オブジェクトのメンバー変数(つまり、非静的)を呼び出すこともできません。(1)
std::function
(C ++ 11以降)は主に関数を格納することです(関数を渡すために関数を格納する必要はありません)。したがって、たとえばメンバー変数にコールバックを格納する場合は、おそらくそれが最良の選択です。ただし、保存しない場合は、「最初の選択」として適切です。ただし、呼び出されたときにオーバーヘッドが(非常に小さい)発生するという欠点があります(パフォーマンスが非常に重要な状況では問題になる可能性がありますが、ほとんどの場合それはすべきではありません)。これは非常に「普遍的」です。一貫性があり、読みやすいコードを重視し、選択したすべてのことを考えたくない場合(つまり、単純に保ちたい場合)は、std::function
渡すすべての関数します。
3番目のオプションについて考えます。提供されたコールバック関数を介して何かを報告する小さな関数を実装しようとしている場合は、テンプレートパラメーターを検討します。テンプレートパラメーターは、呼び出し可能なオブジェクト、つまり関数ポインター、ファンクター、ラムダ、 astd::function
、...ここの欠点は、あなたの(外側)関数は、テンプレートとなり、したがって、ヘッダに実装する必要があるということです。一方、コールバックへの呼び出しはインライン化できるという利点があります。これは、(外部)関数のクライアントコードが、コールバックへの呼び出しが利用可能な正確な型情報を「認識する」ためです。
テンプレートパラメータを使用したバージョンの例(C ++ 11以前の&
代わりに記述&&
):
template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
...
callback(...);
...
}
次の表に示すように、これらにはすべて長所と短所があります。
+-------------------+--------------+---------------+----------------+
| | function ptr | std::function | template param |
+===================+==============+===============+================+
| can capture | no(1) | yes | yes |
| context variables | | | |
+-------------------+--------------+---------------+----------------+
| no call overhead | yes | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be inlined | no | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be stored | yes | yes | no(2) |
| in class member | | | |
+-------------------+--------------+---------------+----------------+
| can be implemented| yes | yes | no |
| outside of header | | | |
+-------------------+--------------+---------------+----------------+
| supported without | yes | no(3) | yes |
| C++11 standard | | | |
+-------------------+--------------+---------------+----------------+
| nicely readable | no | yes | (yes) |
| (my opinion) | (ugly type) | | |
+-------------------+--------------+---------------+----------------+
(1)この制限を克服するための回避策があります。たとえば、(外部)関数に追加のパラメーターとして追加のデータを渡すと、myFunction(..., callback, data)
が呼び出されますcallback(data)
。これはCスタイルの「引数付きのコールバック」です。これは、C ++で可能です(WIN32 APIで頻繁に使用されています)が、C ++にはより優れたオプションがあるため、使用しないでください。
(2)クラステンプレート、つまり、関数を格納するクラスがテンプレートである場合を除きます。しかし、それは、クライアント側では、関数のタイプがコールバックを格納するオブジェクトのタイプを決定することを意味します。これは、実際のユースケースではほとんど決してオプションではありません。
(3)C ++ 11より前の場合は、 boost::function
std::function
と、すぐに外部に格納する必要がある場合に、型消去されたものに変換できる呼び出し可能な効率的なテンプレート関数を作成できます。コンテキストと呼ばれます)。
void (*callbackFunc)(int);
Cスタイルのコールバック関数である可能性がありますが、これはひどく使用できない設計の悪さです。
適切に設計されたCスタイルのコールバックは次のようになりますvoid (*callbackFunc)(void*, int);
-void*
機能を超えて状態を維持するためにコールバックを行うコードを許可します。これを行わないと、呼び出し元は状態をグローバルに保存することになります。これは失礼です。
std::function< int(int) >
int(*)(void*, int)
ほとんどの実装では、呼び出しよりも少し高価になります。ただし、一部のコンパイラではインライン化が困難です。があるstd::function
関数ポインターの呼び出しのオーバーヘッド(「可能な限り最速のデリゲート」などを参照)に匹敵するクローン実装、それらがライブラリーに入ります。
現在、コールバックシステムのクライアントは、リソースをセットアップして、コールバックが作成および削除されたときにそれらを破棄し、コールバックの存続期間を認識する必要があることがよくあります。 void(*callback)(void*, int)
これは提供されません。
これは、コード構造(コールバックの有効期間が限られている)または他のメカニズム(コールバックの登録解除など)を介して利用できる場合があります。
std::function
限定的なライフタイム管理の手段を提供します(オブジェクトの最後のコピーは、忘れられると消えます)。
一般的に、std::function
パフォーマンスの問題が明らかにならない限り、私はを使用します。もしそうなら、私は最初に構造的な変更を探します(ピクセルごとのコールバックではなく、渡されたラムダに基づいてスキャンラインプロセッサを生成するのはどうですか?これは、関数呼び出しのオーバーヘッドを些細なレベルに減らすのに十分なはずです)。 )。次に、それが続く場合は、delegate
場合は、可能な限り最速のデリゲートを基にして、パフォーマンスの問題が解消するかどうかを確認します。
私は主に、レガシーAPI、または異なるコンパイラー生成コード間で通信するためのCインターフェースを作成するために関数ポインターのみを使用します。ジャンプテーブルや型消去などを実装するときに、内部実装の詳細としても使用しました。生成と消費の両方を行っており、クライアントコードが使用するために外部に公開しておらず、関数ポインターが必要なすべてを実行している場合。
適切なコールバックライフタイム管理インフラストラクチャがあると想定しstd::function<int(int)>
て、をint(void*,int)
スタイルコールバックに変換するラッパーを作成できることに注意してください。したがって、Cスタイルのコールバックライフタイム管理システムのスモークテストとして、ラッピングがstd::function
適切に機能することを確認します。
void*
どこから来たのですか?機能を超えて状態を維持したいのはなぜですか?関数には、必要なすべてのコード、すべての機能を含める必要があります。必要な引数を渡して、変更して何かを返します。外部状態が必要な場合、なぜfunctionPtrまたはコールバックがその荷物を運ぶのでしょうか?コールバックは不必要に複雑だと思います。
this
。それは、メンバー関数が呼び出されるケースを考慮する必要があるためthis
ですか?それで、オブジェクトのアドレスを指すポインターが必要ですか?もし私がそれについて多くを見つけることができないので、私が間違っているなら、私がこれについてのより多くの情報を見つけることができる場所へのリンクを私に与えることができます。前もって感謝します。
void*
ランタイム状態の送信を許可するためにa を取ります。void*
とvoid*
引数を持つ関数ポインターは、オブジェクトへのメンバー関数呼び出しをエミュレートできます。「Cコールバックメカニズム101の設計」をたどるリソースがわかりません。
this
。そういう意味です。とにかくありがとう。
std::function
任意の呼び出し可能なオブジェクトを格納するために使用します。これにより、ユーザーはコールバックに必要なコンテキストを提供できます。単純な関数ポインタではできません。
何らかの理由で(おそらくC互換のAPIが必要なため)プレーンな関数ポインターを使用する必要がある場合は、void * user_context
引数を追加して、(不便ではありますが)直接渡されていない状態にアクセスできるようにします。関数。
回避する唯一の理由std::function
は、C ++ 11で導入された、このテンプレートのサポートがないレガシーコンパイラのサポートです。
C ++ 11以前の言語をサポートする必要がない場合は、を使用std::function
すると、コールバックを実装する際の選択肢が増え、「プレーン」な関数ポインタよりも優れたオプションになります。APIのユーザーにより多くの選択肢を提供すると同時に、コールバックを実行するコードの実装の詳細を抽象化します。