関数ポインタのポイントは何ですか?


94

関数ポインタの有用性がわかりません。場合によっては(おそらく存在しますが)役立つと思いますが、関数ポインターを使用する方が良い場合や避けられない場合は考えられません。

(CまたはC ++での)関数ポインタの適切な使用例をいくつか教えてください。


1
この関連するSOの質問では、関数ポインターに関するかなりの議論を見つけることができます。
itsmatt 2010

20
@itsmatt:そうでもない。「テレビはどのように機能しますか?」「テレビで何をするの?」とはまったく違う質問です
sbi

6
C ++では、おそらく代わりにファンクタ(en.wikipedia.org/wiki/Function_object#In_C_and_C.2B.2B)を使用します。
kennytm 2010

11
C ++がCに「コンパイル」された昔の暗黒時代には、仮想メソッドがどのように実装されているかを実際に見ることができました。
sbk 2010

1
マネージC ++またはC#でC ++を使用する場合に非常に重要です。つまり、デリゲートとコールバック
Maher

回答:


108

ほとんどの例はコールバックに要約されます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での並べ替えよりも高速な場合がある理由です。


1
1、さらに別の例については、この答えを参照してください。stackoverflow.com/questions/1727824/...
sharptooth

あなたは仮想関数を忘れました、本質的にそれらは同様に関数ポインターです(コンパイラーが生成するデータ構造と結びついています)。さらに、純粋なCでは、これらの構造を自分で作成して、LinuxカーネルのVFSレイヤー(および他の多くの場所)に見られるオブジェクト指向コードを作成できます。
フロリアン

2
@krynr:仮想関数はコンパイラーの実装者への関数ポインターのみであり、それらが何に適しているかを尋ねる必要がある場合、おそらく(うまくいけば!)コンパイラーの仮想関数メカニズムを実装する必要がないと思われます。
sbi

@sbi:もちろんです。ただし、抽象化の内部で何が行われているのかを理解するのに役立つと思います。また、Cで独自のvtableを実装し、オブジェクト指向のコードを作成すると、非常に優れた学習体験が得られます。
Florian

生命、宇宙、およびすべてに加えて、OPが求めたものへの答えを提供するためのブラウニーポイント
シンプルネーム

41

まあ、私は一般的にそれらを(専門的に)ジャンプテーブルで使用します(このStackOverflow質問も参照してください)。

ジャンプテーブルは、有限状態マシンでデータ駆動型にするために一般的に(ただし、排他的ではなく)使用されます。ネストされたスイッチ/ケースの代わりに

  switch (state)
     case A:
       switch (event):
         case e1: ....
         case e2: ....
     case B:
       switch (event):
         case e3: ....
         case e1: ....

あなたは関数ポインタの2D配列を作り、単に呼び出すことができます handleEvent[state][event]


24

例:

  1. カスタムの並べ替え/検索
  2. さまざまなパターン(戦略、オブザーバーなど)
  3. コールバック

1
ジャンプテーブルは、その重要な用途の1つです。
アシッシュ

機能する例がいくつかあった場合、これは私の賛成票を獲得します。
ドナルフェロー

1
戦略とオブザーバーは、C ++が利用可能な場合は、仮想関数を使用して実装するほうがよいでしょう。それ以外の場合は+1。
ビリーONeal

関数ポインターを賢く使用すると、オブザーバーをよりコンパクトで軽量にできると思います
Andrey

@BillyONeal JavaFが漏えいしてGoFの定義に固執する場合のみ。私はstd::sortcompパラメータを戦略として説明します
Caleth

10

関数ポインタの有用性の「古典的な」例は、qsort()クイックソートを実装するCライブラリ関数です。ユーザーが思いつく可能性のあるすべてのデータ構造に対して普遍的であるためには、ソート可能なデータへのいくつかのvoidポインターと、これらのデータ構造の2つの要素を比較する方法を知っている関数へのポインターが必要です。これにより、ジョブに最適な関数を作成でき、実際に実行時に比較関数を選択することもできます(昇順または降順など)。


7

上記すべてに同意します。さらに、実行時にDLLを動的にロードするときは、関数を呼び出すための関数ポインターが必要になります。


1
私はこれを常にWindows XPをサポートするために行っていますが、それでもWindows 7の機能を使用しています。+1。
Billy ONeal

7

ここで流れに逆らうつもりです。

Cでは、OOがないため、カスタマイズを実装する唯一の方法は関数ポインターです。

C ++では、同じ結果に対して関数ポインターまたはファンクター(関数オブジェクト)のいずれかを使用できます。

ファンクタには、そのオブジェクトの性質により、生の関数ポインタよりも多くの利点があります。

  • 彼らはいくつかの過負荷を提示することがあります operator()
  • 彼らは状態/既存の変数への参照を持つことができます
  • 彼らはその場で構築することができます(lambdabind

(ボイラープレートコードにもかかわらず)私は個人的に関数ポインタよりもファンクタを好みます。これは主に、関数ポインタの構文が(関数ポインタチュートリアルから)簡単に乱雑になる可能性があるためです。

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 ++ではポインターを使用しないと言っているようなものです。とにかく、私はつまらないです。
フローリアン

3
@krynr:私は丁寧に反対します。重要なのは、表示入力の内容、つまり、使用する構文です。それがすべて舞台裏でどのように機能するかはあなたには関係ありません:これは何ですか抽象化の
Matthieu M.

5

Cでは、古典的な使用法はqsort関数です。4番目のパラメーターは、ソート内の順序付けを実行するために使用する関数へのポインターです。C ++では、この種のことのためにファンクタ(関数のように見えるオブジェクト)を使用する傾向があります。


2
@KennyTM:C標準ライブラリでこれ以外の唯一のインスタンスを指摘していました。あなたが引用する例は、サードパーティのライブラリの一部です。
Billy ONeal

5

最近、関数ポインターを使用して抽象化レイヤーを作成しました。

組み込みシステムで実行される純粋なCで書かれたプログラムがあります。複数のハードウェアバリアントをサポートしています。実行しているハードウェアに応じて、いくつかの関数の異なるバージョンを呼び出す必要があります。

初期化時に、プログラムはそれが実行されているハードウェアを特定し、関数ポインターを設定します。プログラム内のすべての上位レベルのルーチンは、ポインターによって参照される関数を呼び出すだけです。上位レベルのルーチンに触れずに、新しいハードウェアバリアントのサポートを追加できます。

以前はswitch / caseステートメントを使用して適切な関数のバージョンを選択していましたが、プログラムがますます多くのハードウェアバリアントをサポートするようになったため、これは実用的ではなくなりました。私は至る所にケースステートメントを追加しなければなりませんでした。

また、使用する関数を特定するために中間関数層を試しましたが、それらはあまり役に立ちませんでした。新しいバリアントを追加するたびに、複数の場所でcaseステートメントを更新する必要がありました。関数ポインターを使用すると、初期化関数を変更するだけで済みます。


3

同様にリッチ、店が機能することを、いくつかのアドレスを参照するには、Windowsの関数ポインタのために非常に普通である、上記と述べました。

C languageWindowsプラットフォームでプログラミングする場合、基本的に一部のDLLファイルをプライマリメモリにロードし(を使用LoadLibrary)、DLLに格納されている関数を使用するには、関数ポインタを作成してこれらのアドレスを指す必要があります(GetProcAddress)。

参照:


2

関数ポインタをCで使用して、プログラミング対象のインターフェイスを作成できます。実行時に必要な特定の機能に応じて、異なる実装を関数ポインターに割り当てることができます。


2

それらの私の主な使用はコールバックでした:後で呼び出すために関数に関する情報を保存する必要があるとき。

ボンバーマンを書いているとしましょう。人が爆弾を投下してから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()することができ任意の関数、関数ポインタであるため、。


質問は[C]と[C ++]でタグ付けされており、他の言語タグは付けられていません。したがって、別の言語でコードスニペットを提供することは、少し話題から外れています。
cmaster-

2

関数ポインタの使用

通話機能、動的にユーザの入力に基づきます。この場合、文字列と関数ポインタのマップを作成します。

#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

2

ここに他の良い答えに加えて、別の視点:

Cでは、(直接)関数ではなく、関数ポインターのみを使用します。

つまり、関数を記述しますが、関数を操作することはできません。使用可能な関数の実行時表現はありません。「関数」を呼び出すことさえできません。あなたが書くとき:

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クラスを含むオブジェクトがあるため、状況は少し複雑ですが、原則はほとんど同じです。


2
さらに興味深いのは、あなたのように関数を呼び出すことができ(&my_function)(my_arg)(*my_function)(my_arg)(**my_function)(my_arg)(&**my_function)(my_arg)(***my_function)(my_arg)...ための機能は、関数ポインタに崩壊
phuclv

1

OO言語の場合、舞台裏でポリモーフィックな呼び出しを実行します(これはCにも当てはまります)。

さらに、実行時に別の動作を別の関数(foo)に注入するのに非常に役立ちます。これにより、関数fooが高階関数になります。柔軟性に加えて、「if-else」という追加のロジックを引き出せるので、fooコードが読みやすくなります。

それはジェネレーター、クロージャーなどのようなPythonの他の多くの便利なことを可能にします。


0

1バイトのオペコードを持つマイクロプロセッサをエミュレートするために、関数ポインタを幅広く使用しています。256の関数ポインタの配列は、これを実装する自然な方法です。


0

関数ポインターの用途の1つは、関数が呼び出されるコードを変更したくない場合です(つまり、呼び出しは条件付きであり、さまざまな条件下で、さまざまな種類の処理を行う必要があります)。ここでは、関数が呼び出される場所でコードを変更する必要がないため、関数ポインターは非常に便利です。適切な引数を持つ関数ポインタを使用して、関数を呼び出すだけです。関数ポインターは、さまざまな関数を条件付きで指すようにすることができます。(これは、初期化フェーズのどこかで行うことができます)。さらに、上記のモデルは、呼び出されるコードを変更する準備ができていない場合に非常に役立ちます(変更できないライブラリAPIであると仮定してください)。APIは、適切なユーザー定義関数を呼び出すために関数ポインターを使用します。


0

ここでやや包括的なリストを提供しようと思います:

  • コールバック:ユーザー提供のコードを使用して、いくつかの(ライブラリ)機能をカスタマイズします。主な例はですが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;
    }

    のコンストラクターはDerivedvtableメンバー変数をグローバルオブジェクトに設定し、派生クラスのとの実装destructfrobnicatestruct 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つに完全に当てはまります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.