関数ポインタの有用性がわかりません。場合によっては(おそらく存在しますが)役立つと思いますが、関数ポインターを使用する方が良い場合や避けられない場合は考えられません。
(CまたはC ++での)関数ポインタの適切な使用例をいくつか教えてください。
関数ポインタの有用性がわかりません。場合によっては(おそらく存在しますが)役立つと思いますが、関数ポインターを使用する方が良い場合や避けられない場合は考えられません。
(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);
別の利点は、関数ポインターを介した呼び出しよりも、関数オブジェクトの呼び出しをインライン化する方が簡単な場合があることです。これが、C ++での並べ替えがCでの並べ替えよりも高速な場合がある理由です。
まあ、私は一般的にそれらを(専門的に)ジャンプテーブルで使用します(このStackOverflow質問も参照してください)。
ジャンプテーブルは、有限状態マシンでデータ駆動型にするために一般的に(ただし、排他的ではなく)使用されます。ネストされたスイッチ/ケースの代わりに
switch (state)
case A:
switch (event):
case e1: ....
case e2: ....
case B:
switch (event):
case e3: ....
case e1: ....
あなたは関数ポインタの2D配列を作り、単に呼び出すことができます handleEvent[state][event]
例:
std::sort
のcomp
パラメータを戦略として説明します
関数ポインタの有用性の「古典的な」例は、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 ++コードで関数ポインターを長い間使用するかどうかはわかりません。
bind
か、boost function
を使用するか、関数ポインターを使用します。スマートポインターを使用するため、C ++ではポインターを使用しないと言っているようなものです。とにかく、私はつまらないです。
Cでは、古典的な使用法はqsort関数です。4番目のパラメーターは、ソート内の順序付けを実行するために使用する関数へのポインターです。C ++では、この種のことのためにファンクタ(関数のように見えるオブジェクト)を使用する傾向があります。
最近、関数ポインターを使用して抽象化レイヤーを作成しました。
組み込みシステムで実行される純粋なCで書かれたプログラムがあります。複数のハードウェアバリアントをサポートしています。実行しているハードウェアに応じて、いくつかの関数の異なるバージョンを呼び出す必要があります。
初期化時に、プログラムはそれが実行されているハードウェアを特定し、関数ポインターを設定します。プログラム内のすべての上位レベルのルーチンは、ポインターによって参照される関数を呼び出すだけです。上位レベルのルーチンに触れずに、新しいハードウェアバリアントのサポートを追加できます。
以前はswitch / caseステートメントを使用して適切な関数のバージョンを選択していましたが、プログラムがますます多くのハードウェアバリアントをサポートするようになったため、これは実用的ではなくなりました。私は至る所にケースステートメントを追加しなければなりませんでした。
また、使用する関数を特定するために中間関数層を試しましたが、それらはあまり役に立ちませんでした。新しいバリアントを追加するたびに、複数の場所でcaseステートメントを更新する必要がありました。関数ポインターを使用すると、初期化関数を変更するだけで済みます。
同様にリッチ、店が機能することを、いくつかのアドレスを参照するには、Windowsの関数ポインタのために非常に普通である、上記と述べました。
C language
Windowsプラットフォームでプログラミングする場合、基本的に一部のDLLファイルをプライマリメモリにロードし(を使用LoadLibrary
)、DLLに格納されている関数を使用するには、関数ポインタを作成してこれらのアドレスを指す必要があります(GetProcAddress
)。
参照:
それらの私の主な使用はコールバックでした:後で呼び出すために関数に関する情報を保存する必要があるとき。
ボンバーマンを書いているとしましょう。人が爆弾を投下してから5秒後に爆発します(explode()
関数を呼び出します)。
これを行うには2つの方法があります。1つの方法は、画面上のすべての爆弾を「調査」して、メインループで爆発する準備ができているかどうかを確認することです。
foreach bomb in game
if bomb.boomtime()
bomb.explode()
もう1つの方法は、コールバックをクロックシステムにアタッチすることです。爆弾が仕掛けられたら、コールバックを追加して、適切なタイミングでbomb.explode()を呼び出すようにします。
// 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()
することができ任意の関数、関数ポインタであるため、。
関数ポインタの使用
通話機能、動的にユーザの入力に基づきます。この場合、文字列と関数ポインタのマップを作成します。
#include<iostream>
#include<map>
using namespace std;
//typedef map<string, int (*)(int x, int y) > funMap;
#define funMap map<string, int (*)(int, int)>
funMap objFunMap;
int Add(int x, int y)
{
return x+y;
}
int Sub(int x, int y)
{
return x-y;
}
int Multi(int x, int y)
{
return x*y;
}
void initializeFunc()
{
objFunMap["Add"]=Add;
objFunMap["Sub"]=Sub;
objFunMap["Multi"]=Multi;
}
int main()
{
initializeFunc();
while(1)
{
string func;
cout<<"Enter your choice( 1. Add 2. Sub 3. Multi) : ";
int no, a, b;
cin>>no;
if(no==1)
func = "Add";
else if(no==2)
func = "Sub";
else if(no==3)
func = "Multi";
else
break;
cout<<"\nEnter 2 no :";
cin>>a>>b;
//function is called using function pointer based on user input
//If user input is 2, and a=10, b=3 then below line will expand as "objFuncMap["Sub"](10, 3)"
int ret = objFunMap[func](a, b);
cout<<ret<<endl;
}
return 0;
}
このように、実際の会社コードで関数ポインターを使用しました。'n'個の関数を記述し、このメソッドを使用してそれらを呼び出すことができます。
出力:
選択肢を入力してください(1.追加2.サブ3.マルチ):1 2を入力しない:2 4 6 選択肢を入力してください(1.追加2.サブ3.マルチ):2 2を入力してください:10 3 7 選択肢を入力してください(1.追加2.サブ3.マルチ):3 2を入力:3 6 18
ここに他の良い答えに加えて、別の視点:
つまり、関数を記述しますが、関数を操作することはできません。使用可能な関数の実行時表現はありません。「関数」を呼び出すことさえできません。あなたが書くとき:
my_function(my_arg);
あなたが実際に言っていることは、「my_function
指定された引数でポインタへの呼び出しを実行する」です。関数ポインターを介して呼び出しを行っています。この関数ポインタへの減衰は、次のコマンドが前の関数呼び出しと同等であることを意味します。
(&my_function)(my_arg);
(*my_function)(my_arg);
(**my_function)(my_arg);
(&**my_function)(my_arg);
(***my_function)(my_arg);
など(@LuuVinhPhucに感謝)。
したがって、値として関数ポインタをすでに使用しています。明らかに、これらの値の変数が必要になるでしょう-そして、ここで他のすべての使用法の出番です:ポリモーフィズム/カスタマイズ(qsortのような)、コールバック、ジャンプテーブルなど。
C ++では、ラムダ、およびoperator()
、さらにはstd::function
クラスを含むオブジェクトがあるため、状況は少し複雑ですが、原則はほとんど同じです。
(&my_function)(my_arg)
、(*my_function)(my_arg)
、(**my_function)(my_arg)
、(&**my_function)(my_arg)
、(***my_function)(my_arg)
...ための機能は、関数ポインタに崩壊
関数ポインターの用途の1つは、関数が呼び出されるコードを変更したくない場合です(つまり、呼び出しは条件付きであり、さまざまな条件下で、さまざまな種類の処理を行う必要があります)。ここでは、関数が呼び出される場所でコードを変更する必要がないため、関数ポインターは非常に便利です。適切な引数を持つ関数ポインタを使用して、関数を呼び出すだけです。関数ポインターは、さまざまな関数を条件付きで指すようにすることができます。(これは、初期化フェーズのどこかで行うことができます)。さらに、上記のモデルは、呼び出されるコードを変更する準備ができていない場合に非常に役立ちます(変更できないライブラリAPIであると仮定してください)。APIは、適切なユーザー定義関数を呼び出すために関数ポインターを使用します。
ここでやや包括的なリストを提供しようと思います:
コールバック:ユーザー提供のコードを使用して、いくつかの(ライブラリ)機能をカスタマイズします。主な例はですがqsort()
、イベント(ボタンをクリックしたときにコールバックを呼び出すボタンなど)を処理する場合や、スレッドを開始する場合にも必要pthread_create()
です()。
ポリモーフィズム:C ++クラスのvtableは、関数ポインターのテーブルにすぎません。また、Cプログラムは、そのオブジェクトの一部にvtableを提供することを選択する場合もあります。
struct Base;
struct Base_vtable {
void (*destruct)(struct Base* me);
};
struct Base {
struct Base_vtable* vtable;
};
struct Derived;
struct Derived_vtable {
struct Base_vtable;
void (*frobnicate)(struct Derived* me);
};
struct Derived {
struct Base;
int bar, baz;
}
のコンストラクターはDerived
、vtable
メンバー変数をグローバルオブジェクトに設定し、派生クラスのとの実装destruct
とfrobnicate
、struct Base*
単に呼び出すことになるbase->vtable->destruct(base)
クラス由来独立そのデストラクタの正しいバージョンを呼び出すことになる、base
と実際のポイントを。
関数ポインタがない場合、ポリモーフィズムは、次のようなスイッチコンストラクトの軍隊でコード化する必要があります。
switch(me->type) {
case TYPE_BASE: base_implementation(); break;
case TYPE_DERIVED1: derived1_implementation(); break;
case TYPE_DERIVED2: derived2_implementation(); break;
case TYPE_DERIVED3: derived3_implementation(); break;
}
これはかなり手に負えなくなります。
動的に読み込まれたコード:プログラムがモジュールをメモリに読み込み、そのコードを呼び出そうとするとき、関数ポインタを通過する必要があります。
私が見た関数ポインターのすべての使用法は、これらの3つの広範なクラスの1つに完全に当てはまります。