web-dev-qa-db-ja.com

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

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

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

88
gramm

ほとんどの例はcallbacksに要約されます:関数f()を呼び出して、別の関数のアドレスを渡しますg()、およびf()は、特定のタスクに対してg()を呼び出します。代わりにf()h()のアドレスに渡すと、f()h()をコールバックします。

基本的に、これはparametrize関数への方法です:その動作の一部はf()、しかしコールバック関数に。呼び出し元は、異なるコールバック関数を渡すことにより、f()の動作を変えることができます。クラシックは、C標準ライブラリのqsort()であり、そのソート基準を比較関数へのポインタとして使用します。

C++では、これは多くの場合function objectsを使用して行われます(ファンクタとも呼ばれます)。これらは、関数呼び出し演算子をオーバーロードするオブジェクトなので、関数のように呼び出すことができます。例:

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での並べ替えよりも高速な場合がある理由です。

107
sbi

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

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

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

2D配列または関数ポインタを作成し、handleEvent[state][event]を呼び出すだけです。

例:

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

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

10
Carl Smotricz

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

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++コードで関数ポインターを長い間使用するかどうかはわかりません。

7
Matthieu M.

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

6
Rich

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

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

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

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

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

5
myron-semack

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

5
anon

上記のRichのように、Windowsの関数ポインタが関数を格納するアドレスを参照するのはごく普通のことです。

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

参照:

3
Thomaz

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

2
dafmetal

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

Cでは、only関数ポインターのみが存在し、関数は存在しません。

つまり、関数を記述しますが、manipulate関数はできません。そのような関数の実行時表現はありません。 「関数」を呼び出すことさえできません。あなたが書くとき:

_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に感謝)。

したがって、valuesとして関数ポインタをすでに使用しています。明らかに、これらの値の変数が必要になります-ここで、他のすべての使用法の出番です:ポリモーフィズム/カスタマイズ(qsortのような)、コールバック、ジャンプテーブルなど。

C++では、ラムダ、operator()、さらには_std::function_クラスのオブジェクトがあるため、少し複雑ですが、原則はほとんど同じです。

2
einpoklum

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

ボンバーマンを書いているとしましょう。人が爆弾を投下してから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()any functionとすることができます。これは関数ポインタだからです。

2
bobobobo

関数ポインタの使用

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

#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. Add 2. Sub 3. Multi):1 
 Enter 2 no:2 4 
 6 
選択肢を入力してください(1. Add 2.サブ3.マルチ):2 
 2を入力:10 3 
 7 
選択内容を入力(1.追加2.サブ3.マルチ):3 
 2 noと入力します:3 6 
 18 
2

OO言語の場合、舞台裏で多態性の呼び出しを実行します(これは、Cが私が推測している点まで有効です)。

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

Pythonで他の多くの便利なものをジェネレーター、クロージャーなどのように有効にします。

1
stdout

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

0
Sumit Trehan

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

  • Callbacks:ユーザー提供のコードを使用して、いくつかの(ライブラリ)機能をカスタマイズします。プライムの例はqsort()ですが、イベント(ボタンがクリックされたときにコールバックを呼び出すボタンなど)を処理する場合や、スレッドを開始する必要がある場合(pthread_create())にも役立ちます。

  • Polymorphism: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つに完全に当てはまります。

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

0
TonyK