関数ポインターの有用性がわかりません。場合によっては役立つかもしれないと思いますが(結局、それらは存在します)、関数ポインターを使用する方が良い、または避けられない場合は考えられません。
関数ポインター (C または C++) の適切な使用例をいくつか挙げていただけますか?
ほとんどの例はコールバックf()
に要約されます。別の関数のアドレスを渡して関数g()
をf()
呼び出しg()
、特定のタスクを呼び出します。代わりにf()
のアドレスを渡すと、代わりにコールバックされます。h()
f()
h()
基本的に、これは関数をパラメーター化する方法です。その動作の一部は、f()
ではなく、コールバック関数にハードコードされています。呼び出し元は、f()
さまざまなコールバック関数を渡すことで、異なる動作をさせることができます。クラシックはqsort()
、ソート基準を比較関数へのポインターとして使用する C 標準ライブラリーからのものです。
C++ では、これは多くの場合、関数オブジェクト(ファンクターとも呼ばれます) を使用して行われます。これらは関数呼び出し演算子をオーバーロードするオブジェクトであるため、関数であるかのように呼び出すことができます。例:
class functor {
public:
void operator()(int i) {std::cout << "the answer is: " << i << '\n';}
};
functor f;
f(42);
この背後にある考え方は、関数ポインターとは異なり、関数オブジェクトはアルゴリズムだけでなくデータも運ぶことができるということです。
class functor {
public:
functor(const std::string& prompt) : prompt_(prompt) {}
void operator()(int i) {std::cout << prompt_ << i << '\n';}
private:
std::string prompt_;
};
functor f("the answer is: ");
f(42);
もう 1 つの利点は、関数ポインターを介した呼び出しよりも、関数オブジェクトへの呼び出しをインライン化する方が簡単な場合があることです。これが、C++ でのソートが C でのソートよりも速い場合がある理由です。
まあ、私は通常、それらを(専門的に)ジャンプテーブルで使用します(このStackOverflowの質問も参照してください)。
ジャンプ テーブルは、有限ステート マシンでデータ駆動型にするために一般的に (排他的ではありませんが) 使用されます。ネストされたスイッチ/ケースの代わりに
switch (state)
case A:
switch (event):
case e1: ....
case e2: ....
case B:
switch (event):
case e3: ....
case e1: ....
関数ポインターの2次元配列を作成して呼び出すことができますhandleEvent[state][event]
例:
関数ポインタの有用性の「典型的な」例はqsort()
、クイック ソートを実装する C ライブラリ関数です。ユーザーが思いつく可能性のあるすべてのデータ構造に対して普遍的であるためには、ソート可能なデータへのいくつかの void ポインターと、これらのデータ構造の 2 つの要素を比較する方法を知っている関数へのポインターが必要です。これにより、ジョブに選択した関数を作成することができ、実際には実行時に比較関数を選択することもできます (例: 昇順または降順)。
上記のすべてに同意し、さらに....実行時に動的に dll をロードする場合、関数を呼び出すために関数ポインタが必要になります。
ここで流れに逆らっていきます。
C では、OO がないため、関数ポインターがカスタマイズを実装する唯一の方法です。
C++ では、同じ結果に対して関数ポインターまたはファンクター (関数オブジェクト) のいずれかを使用できます。
ファンクターには、そのオブジェクトの性質により、生の関数ポインターよりも多くの利点があります。
operator()
lambda
およびbind
)個人的には、関数ポインターよりもファンクターを好みます (ボイラープレート コードにもかかわらず)。これは主に、関数ポインターの構文が複雑になりやすいためです (関数ポインター チュートリアルから)。
typedef float(*pt2Func)(float, float);
// defines a symbol pt2Func, pointer to a (float, float) -> float function
typedef int (TMyClass::*pt2Member)(float, char, char);
// defines a symbol pt2Member, pointer to a (float, char, char) -> int function
// belonging to the class TMyClass
ファンクターが使用できない場所で関数ポインターが使用されているのを見たのは、Boost.Spirit だけでした。彼らは構文を完全に悪用して、任意の数のパラメーターを単一のテンプレート パラメーターとして渡しました。
typedef SpecialClass<float(float,float)> class_type;
しかし、可変個引数テンプレートとラムダがすぐ近くにあるため、純粋な C++ コードで関数ポインターを長い間使用するかどうかはわかりません。
最近、関数ポインターを使用して抽象化レイヤーを作成しました。
組み込みシステムで実行される純粋な C で書かれたプログラムがあります。複数のハードウェア バリアントをサポートします。実行しているハードウェアによっては、いくつかの関数の異なるバージョンを呼び出す必要があります。
初期化時に、プログラムは実行中のハードウェアを特定し、関数ポインターを設定します。プログラム内のすべての高レベル ルーチンは、ポインターによって参照される関数を呼び出すだけです。上位レベルのルーチンに触れることなく、新しいハードウェア バリアントのサポートを追加できます。
以前は switch/case ステートメントを使用して適切な関数バージョンを選択していましたが、プログラムがますます多くのハードウェア バリアントをサポートするようになるにつれて、これは非現実的になりました。ケースステートメントをあちこちに追加する必要がありました。
また、中間機能層を使用してどの機能を使用するかを調べてみましたが、あまり役に立ちませんでした。新しいバリアントを追加するたびに、複数の場所で case ステートメントを更新する必要がありました。関数ポインターを使用すると、初期化関数を変更するだけで済みます。
C では、古典的な使用法はqsort 関数です。4番目のパラメーターは、並べ替え内で順序付けを実行するために使用する関数へのポインターです。C++ では、このような目的でファンクタ (関数のように見えるオブジェクト) を使用する傾向があります。
それらの私の主な用途はコールバックです:後で呼び出すために関数に関する情報を保存する必要があるとき。
あなたがボンバーマンを書いているとしましょう。人が爆弾を落とした5秒後、爆弾が爆発するはずです(explode()
関数を呼び出します)。
今それを行う2つの方法があります。1つの方法は、画面上のすべての爆弾を「プローブ」して、メインループで爆発する準備ができているかどうかを確認することです。
foreach bomb in game
if bomb.boomtime()
bomb.explode()
もう1つの方法は、クロックシステムにコールバックをアタッチすることです。爆弾が仕掛けられたら、コールバックを追加して、適切なタイミングで爆弾を呼び出すようにします。
// user placed a bomb
Bomb* bomb = new Bomb()
make callback( function=bomb.explode, time=5 seconds ) ;
// IN the main loop:
foreach callback in callbacks
if callback.timeToRun
callback.function()
これは関数ポインタであるため、任意の関数callback.function()
にすることができます。
Richが上で述べたように、Windows の関数ポインタが関数を格納するアドレスを参照するのはごく普通のことです。
C language
Windows プラットフォームでプログラミングする場合、基本的に DLL ファイルをプライマリ メモリにロードし ( を使用LoadLibrary
)、DLL に格納された関数を使用するには、関数ポインタを作成し、これらのアドレスをポイントする必要があります ( を使用GetProcAddress
)。
参考文献:
関数ポインターを C で使用して、プログラミング対象のインターフェイスを作成できます。実行時に必要な特定の機能に応じて、別の実装を関数ポインターに割り当てることができます。
1バイトのオペコードを持つマイクロプロセッサをエミュレートするために、関数ポインタを広く使用しています。256個の関数ポインタの配列は、これを実装するための自然な方法です。
関数ポインターの 1 つの使用法は、関数が呼び出されるコードを変更したくない場合です (つまり、呼び出しが条件付きであり、さまざまな条件下でさまざまな種類の処理を行う必要があることを意味します)。ここでは、関数が呼び出される場所でコードを変更する必要がないため、関数ポインターは非常に便利です。適切な引数を指定した関数ポインタを使用して関数を呼び出すだけです。関数ポインタは、条件付きで異なる関数を指すようにすることができます。(これは、初期化フェーズのどこかで実行できます)。さらに、上記のモデルは、呼び出されるコードを変更する立場にない場合に非常に役立ちます (変更できないライブラリ API であるとします)。API は、適切なユーザー定義関数を呼び出すために関数ポインターを使用します。