15

私は数年前からプログラミングをしていて、場合によっては関数ポインタを使用しています。私が知りたいのは、パフォーマンス上の理由でそれらを使用することが適切であるかどうかです。つまり、ビジネスソフトウェアではなく、ゲームのコンテキストで使用します。

関数ポインタは高速です。JohnCarmackは、QuakeとDoomのソースコードで悪用される範囲でそれらを使用しました。彼は天才だからです:)

関数ポインタをもっと使いたいのですが、最も適切な場所で使いたいです。

最近、C、C ++、C#、Javaなどの最新のCスタイル言語での関数ポインターの最良かつ最も実用的な使用法は何ですか?

4

11 に答える 11

25

関数ポインタについて特に「速い」ということはありません。実行時に指定された関数を呼び出すことができます。ただし、他の関数呼び出しから得られるものとまったく同じオーバーヘッドがあります (さらに、追加のポインター間接化)。さらに、呼び出す関数は実行時に決定されるため、通常、コンパイラは関数呼び出しをインライン化することはできません。そのため、関数ポインターは、場合によっては、通常の関数呼び出しよりも大幅に遅くなることがあります。

関数ポインターはパフォーマンスとは関係がないため、パフォーマンスを向上させるために使用しないでください。

代わりに、それらは関数型プログラミングのパラダイムに非常にわずかに同意しています。つまり、関数をパラメーターとして渡したり、別の関数で値を返したりすることができます。

簡単な例は、一般的なソート関数です。2 つの要素をどのように並べ替えるかを決定するには、それらを比較する方法が必要です。これは、ソート関数に渡される関数ポインタである可能性があり、実際、c++std::sort()はまさにそのように使用できます。より小さい演算子を定義しない型のシーケンスを並べ替えるように要求する場合は、比較を実行するために呼び出すことができる関数ポインターを渡す必要があります。

そして、これは私たちを優れた代替手段にうまく導きます. C++ では、関数ポインターに限定されません。代わりにファンクターを使用することがよくあります。つまり、 operator をオーバーロードするクラスで、()関数であるかのように「呼び出す」ことができます。ファンクターには、関数ポインターに比べていくつかの大きな利点があります。

  • それらはより柔軟性を提供します: それらは、コンストラクター、デストラクター、およびメンバー変数を備えた本格的なクラスです。それらは状態を維持でき、周囲のコードが呼び出すことができる他のメンバー関数を公開する場合があります。
  • それらはより高速です: その型が関数のシグネチャのみをエンコードする関数ポインターとは異なり (型の変数はint を取り、void を返す任意のvoid (*)(int)関数である可能性があります。どの関数かはわかりません)、ファンクターの型は正確な関数をエンコードします。これを呼び出す必要があります (ファンクターはクラスなので、C と呼びます。呼び出す関数は、常に であることがわかっています)。これは、コンパイラが関数呼び出しをインライン化できることを意味します。これは、ジェネリックを、データ型用に特別に設計されたハンドコーディングの並べ替え関数と同じくらい高速にする魔法です。コンパイラは、ユーザー定義関数を呼び出すオーバーヘッドをすべて排除できます。C::operator()std::sort
  • それらはより安全です: 関数ポインターにはほとんど型安全性がありません。それが有効な関数を指しているという保証はありません。NULL の可能性があります。また、ポインターに関する問題のほとんどは、関数ポインターにも当てはまります。それらは危険で、エラーが発生しやすいものです。

関数ポインター (C) またはファンクター (C++) またはデリゲート (C#) はすべて同じ問題を解決しますが、洗練度と柔軟性のレベルは異なります。これらを使用すると、関数をファーストクラスの値として扱い、通常どおりに渡すことができます。その他の変数。関数を別の関数に渡すと、指定した時間に関数が呼び出されます (タイマーの期限が切れたとき、ウィンドウの再描画が必要なとき、または配列内の 2 つの要素を比較する必要があるとき)。

私が知る限り (そして、私は長い間 Java を扱っていないので、間違っている可能性もあります)、Java には直接の同等物はありません。代わりに、インターフェイスを実装し、関数を定義するクラスを作成する必要があります (Execute()たとえば、それを呼び出します)。次に、ユーザー提供の関数 (関数ポインター、ファンクター、またはデリゲートの形で) を呼び出す代わりに、 を呼び出しますfoo.Execute()。原則として C++ 実装に似ていますが、C++ テンプレートの一般性がなく、関数ポインターとファンクターを同じように扱うことができる関数構文がありません。

そこで、関数ポインタを使用します。より洗練された代替手段が利用できない場合 (つまり、C で行き詰まっている場合)、ある関数を別の関数に渡す必要がある場合。最も一般的なシナリオはコールバックです。X が発生したときにシステムが呼び出す関数 F を定義します。したがって、F を指す関数ポインターを作成し、それを問題のシステムに渡します。

本当に、John Carmack のことは忘れて、彼のコードをコピーすれば魔法のようにコードが改善されると思い込まないでください。彼が関数ポインタを使用したのは、あなたが言及したゲームが C で書かれており、優れた代替手段が利用できないためであり、単に存在するだけでコードの実行が高速化される魔法の要素だからではありません。

于 2009-03-20T19:48:22.883 に答える
6

C#でイベントハンドラーまたはデリゲートを使用するときはいつでも、関数ポインターを効果的に使用しています。

いいえ、速度についてではありません。関数ポインタは便利さに関するものです。

ジョナサン

于 2009-03-20T18:40:44.403 に答える
6

多くの場合、関数ポインタはコールバックとして使用されます。用途の 1 つは、並べ替えアルゴリズムの比較関数として使用することです。したがって、カスタマイズされたオブジェクトを比較しようとしている場合は、そのデータの処理方法を認識している比較関数への関数ポインターを提供できます。

そうは言っても、私の元教授から得た引用を提供します。

C++ の新しい機能は、装填された自動兵器を混雑した部屋で扱うように扱ってください。見た目が良いという理由だけで使用しないでください。結果を理解するまで待って、かわいそうにならないで、知っていることを書いて、自分が書いたものを知ってください。

于 2009-03-20T18:41:20.640 に答える
5

私の個人的な経験によると、コードの大幅な行を節約するのに役立ちます。

次の条件を考慮してください。

switch(sample_var)
{

case 0:
          func1(<parameters>);
          break;

case 1:
          func2(<parameters>);
          break;



up to case n:
          funcn(<parameters>);
          break;

}

ここでfunc1()...funcn()は同じプロトタイプを持つ関数です。arrFuncPoint関数のアドレスを含む 関数ポインタの配列を宣言しますfunc1()funcn()

次に、スイッチケース全体が

*arrFuncPoint[sample_var];

于 2012-03-26T15:37:19.107 に答える
5

最近では、最新の C スタイル言語での整数の最適かつ最も実用的な使用法は何ですか?

于 2009-03-20T18:44:06.670 に答える
4

C++ より前の暗黒の時代に、私がコードで使用した一般的なパターンがありました。これは、(通常) 何らかの方法でその構造体を操作し、特定の動作を提供する一連の関数ポインターを使用して構造体を定義するというものでした。C++ の用語では、vtable を構築しているだけです。違いは、実行時に構造体に副作用を与えて、必要に応じて個々のオブジェクトの動作をオンザフライで変更できることです。これは、安定性とデバッグの容易さを犠牲にして、より豊富な継承モデルを提供します。ただし、最大の代償は、このコードを効果的に記述できる人物が 1 人しかいないことでした。それは私です。

これを UI フレームワークで多用し、オブジェクトの描画方法やコマンドのターゲットなどをオンザフライで変更できるようにしました。

このプロセスを OO 言語で形式化することは、あらゆる点で優れています。

于 2009-03-20T20:22:35.110 に答える
3

関数ポインタは、貧乏人が機能しようとする試みです。関数ポインタを使用すると高階関数を記述できるため、関数ポインタを使用すると言語が機能するという議論をすることもできます。

クロージャと簡単な構文がなければ、それらはかなり粗雑です。したがって、あなたはそれらを望ましいものよりはるかに少なく使用する傾向があります。主に「コールバック」機能用。

時々、オブジェクト指向デザインは、必要な関数を渡すためにインターフェイスタイプ全体を作成する代わりに、関数を使用して回避します。

C#にはクロージャがあるため、関数ポインター(実際にはオブジェクトを格納するため、生の関数だけでなく、型指定された状態も格納されます)の方がはるかに使いやすくなっています。

編集 コメントの1つは、関数ポインターを使用した高階関数のデモンストレーションが必要であると述べています。コールバック関数を受け取る関数はすべて高階関数です。たとえば、EnumWindows

BOOL EnumWindows(          
    WNDENUMPROC lpEnumFunc,
    LPARAM lParam
);

最初のパラメーターは、渡す関数であり、非常に簡単です。しかし、Cにはクロージャがないため、次の素敵な2番目のパラメーターを取得します。「コールバック関数に渡されるアプリケーション定義の値を指定します。」このアプリ定義の値を使用すると、型指定されていない状態を手動で渡して、クロージャの不足を補うことができます。

.NETFrameworkも同様のデザインで満たされています。たとえば、IAsyncResult .AsyncState:"非同期操作に関する情報を修飾または含むユーザー定義オブジェクトを取得します。" IARは、クロージャなしでコールバックで取得するすべてであるため、後でキャストできるように、一部のデータを非同期操作にプッシュする方法が必要です。

于 2009-03-20T22:17:24.990 に答える
3

関数ポインタを使用すると処理が高速化される場合があります。長い switch ステートメントや if-then-else シーケンスの代わりに、単純なディスパッチ テーブルを使用できます。

于 2009-03-23T09:51:45.350 に答える
3

C# といえば、C# 全体で関数ポインタが使われています。デリゲートとイベント (およびラムダなど) はすべて内部の関数ポインターであるため、ほぼすべての C# プロジェクトが関数ポインターでいっぱいになります。基本的に、すべてのイベント ハンドラー、ほぼすべての LINQ クエリなどで、関数ポインターが使用されます。

于 2009-03-20T18:41:35.837 に答える
1

関数ポインタは速い

どのような文脈で?に比べ?

関数ポインターを使用するために関数ポインターを使用したいだけのようです。それは悪いでしょう。

関数へのポインターは、通常、コールバックまたはイベント ハンドラーとして使用されます。

于 2009-03-20T18:43:49.497 に答える