コンテンツにスキップ

英文维基 | 中文维基 | 日文维基 | 草榴社区

関数へのポインタ

出典: フリー百科事典『ウィキペディア(Wikipedia)』
関数ポインタから転送)

関数へのポインタ (かんすうへのポインタ、: pointer to function) あるいは関数ポインタ (かんすうポインタ、: function pointer) は、C言語, C++, D言語やその他多くのプログラミング言語におけるポインタの一種である。関数へのポインタをデリファレンス (dereference) すれば、そのポインタが指し示す関数サブルーチン)を呼び出せる。応用例としては、switch文を置き換えるテーブルジャンプを実装する、コールバック関数によるカスタマイズポイントを提供する、などといったものがある。

関数オブジェクト (: function object) は、関数へのポインタに似ているが、コード領域中のエントリポイントを指す単なるポインタである関数へのポインタと違い、データ領域上に実体を持つオブジェクトであるという点が異なっている(実装の詳細は言語や処理系により異なるが)。そのため、関数オブジェクトはデータを保持でき、クロージャを再現することもできる。ゆえに、関数オブジェクトは、「関数へのポインタ」ではなく「関数」という型と値を持つようなものと言え、より強力である。

C#Visual Basic .NETなどといった.NET Framework用の言語には、メソッドを参照する型として、デリゲートがある。P/Invokeなどの.NET相互運用において、デリゲートは関数へのポインタにマーシャリングされる[1]

Javaはバージョン8でメソッド参照を導入し、関数ポインタやデリゲート類似の機能を利用できるようになったが、バージョン7まではメソッド参照を持たず、代替としてメソッドを1つだけ持つインタフェースを利用して同等機能を実現する必要がある。

第一級オブジェクトとして関数を使用できる(第一級関数がある)言語では、関数も引数で渡したり、戻り値で返したり、他の関数から動的に作成したりできるなどデータ同様に扱えるため、関数へのポインタは必要とされない。

Cでの例

[編集]

以下の例では、関数へのポインタとしてfunc_ptrが宣言され、そこへ関数my_functionのアドレスを割り当てている。そしてfunc_ptrを通じて関数を呼び出している。

#include <stdio.h>

static int my_function(int a) {
    printf("my_function: %d\n", a);
    return 2 * a + 3;
}

int main(void) {
    int (*func_ptr)(int a) = my_function;
    /* あるいは以下でも可能 */
    /*
    int (*func_ptr)(int) = &my_function;
    */

    int x;

    x = (*func_ptr)(10);
    /* あるいは以下でも可能 */
    /*
    x = func_ptr(10);
    */

    printf("main: %d\n", x);
    return 0;
}

注1: 関数シンボルf自体は関数型 (function type) であり、関数型は関数ポインタとは異なる。単項のアドレス演算子 (address operator) &を付与した式&fは関数ポインタを返す。しかし、関数型の式fは関数へのポインタに暗黙変換されるため、アドレス演算子を適用せずとも関数へのポインタに代入可能である。

注2: 関数へのポインタfpに単項の間接演算子 (indirection operator) *を付与した式*fpは関数指示子[2] (function designator) となる。(*fp)(arg)という構文は、関数へのポインタfpを通じて関数を呼び出す構文である。しかしCではfp(arg)という構文も認められている[3]

注3: 関数ポインタの宣言における仮引数リストは、型が一致してさえいればいいので、仮引数の名前は省略できる。

次の例では、関数へのポインタを引数として他の関数に渡している。ここでは、関数my_functionが、先の例のように関数へのポインタを通じて呼び出される。関数callerは、引数として関数へのポインタと整数値を1つ取る。引数の整数値は、その関数へのポインタを通じて関数を呼び出すときに渡す引数として用いられる。そこで宣言されている関数へのポインタのプロトタイプに適合しさえすれば、callerの第1引数には、どんな関数でも渡すことが可能である。

#include <stdio.h>

static void my_function(int a) {
    printf("my_function: %d\n", a);
}

static void caller(void (*func_ptr)(int a), int p) {
    (*func_ptr)(p);
}

int main(void) {
    caller(my_function, 10);
    return 0;
}

3番目の例でも関数へのポインタを引数として他の関数に渡して用いている。関数fは、指定された区間で積分の近似を計算する関数integへ渡されている。におけるの値を求めるために、f(x)integから呼び出されている。integでは、「double型の引数を1つ取り、double型の値を返す関数」でありさえすれば、どんな関数でも計算させることが可能である。

なお、関数へのポインタを定義する際には、事前にtypedefを用いて関数型もしくは関数ポインタ型のエイリアスを定義しておくと便利である。

#include <stdio.h>
#include <math.h>
#define PI 3.14159265358979323846

typedef double (*fx_ptr_t)(double x);
/* あるいは以下でも可能 */
/*
typedef double fx_t(double x);
typedef fx_t* fx_ptr_t;
*/

double integ(double a, double b, fx_ptr_t fp) {
    double sum = 0.0;
    double x;
    int n;

    /* 積分 {a,b} f(x) dx の計算 */
    for (n = 0; n <= 100; ++n) {
        x = (n / 100.0) * (b - a) + a;
        sum += (*fp)(x) * (b - a) / (100.0 + 1.0);
    }
    return sum;
}

int main(void) {
    int i;

    struct pair {
        fx_ptr_t fp;
        const char* name;
    } pairs[3] = {
        { cos, "cos" },
        { sin, "sin" },
        { tan, "tan" },
    };

    printf("From 0 to pi/4:\n");

    for (i = 0; i < 3; ++i) {
        printf("\t" "Integral of %s = %g\n", pairs[i].name, integ(0, PI/4, pairs[i].fp));
    }

    return 0;
}

関数ポインタを引数として渡すことで、処理を外部から組み込む(カスタマイズする)ことが可能である。関数ポインタを受け取って関数を呼び出す側は、その関数内で何が実行されるかを関知する必要がない。引数として渡される関数は、コールバック関数と呼ばれることもある。主にイベント処理や、判断を外部に任せて処理を行なうときなどに用いられる。

C++での例

[編集]

C++において、以下の関数へのポインタは、Cの関数へのポインタとの互換性があり、呼び出し規約が同じであれば直接の相互運用が可能である。

  • クラスに属さない名前空間レベルのグローバル関数(フリー関数)
  • クラスの静的メンバー関数

一方で、クラスの非静的メンバー関数へのポインタは互換性がない。また、非静的メンバー関数へのポインタを経由した関数の呼び出しには、そのクラスのインスタンスが必要となる。

C++では関数への参照を定義することもできるが、非静的メンバー関数への参照を定義することはできない[4]

#include <cstdio>

void GlobalFunc1() { puts("GlobalFunc1"); }

class MyClass {
public:
    static void ClassFunc1() { puts("MyClass::ClassFunc1"); }
    void InstanceFunc1() { puts("MyClass::InstanceFunc1"); }
};

int main() {
    void(*fpGlobalFunc1a)() = GlobalFunc1;
    void(*fpGlobalFunc1b)() = &GlobalFunc1;
    void(&frGlobalFunc1)() = GlobalFunc1;
    void(*fpClassFunc1a)() = MyClass::ClassFunc1;
    void(*fpClassFunc1b)() = &MyClass::ClassFunc1;
    void(&frClassFunc1)() = MyClass::ClassFunc1;

    fpGlobalFunc1a();
    fpGlobalFunc1b();
    frGlobalFunc1();
    (*fpGlobalFunc1a)();
    (*fpGlobalFunc1b)();
    (*frGlobalFunc1)();
    fpClassFunc1a();
    fpClassFunc1b();
    frClassFunc1();
    (*fpClassFunc1a)();
    (*fpClassFunc1b)();
    (*frClassFunc1)();

    void(MyClass::*fpInstanceFunc1)() = &MyClass::InstanceFunc1;

    MyClass obj;
    (obj.*fpInstanceFunc1)();

    return 0;
}

C++では関数テンプレートの述語 (predicate) として通例関数オブジェクト (functor) あるいはラムダ式 (lambda expression) が渡されるが、関数ポインタを渡すことも可能である。ただし、関数オブジェクトやラムダ式のほうがコンパイラ最適化によるインライン化が期待できるため好まれる。なお、ラムダ式はC++11以降で標準化された機能であるが、Boost C++ライブラリのBoost.Lambdaを利用することで、C++03以前でもラムダ式を利用することができる。

#include <iostream>
#include <vector>
#include <algorithm>

bool CompareFuncGreater(const int& a, const int& b) {
    return a > b;
}

void PrintLine(int x) {
    std::cout << x << std::endl;
}

int main() {
    // 降順にソート。
    std::vector<int> data { 5, 24, 1, -12, 0, 8, -7 };
#if 0
    std::sort(data.begin(), data.end(), std::greater<int>());
    std::for_each(data.begin(), data.end(), [](int x) { std::cout << x << std::endl; });
#else
    std::sort(data.begin(), data.end(), CompareFuncGreater);
    std::for_each(data.begin(), data.end(), PrintLine);
#endif
    return 0;
}

また、C++11以降ではtypedefの代わりにusingを使うこともできる。typedefよりもusingのほうがいくらか分かりやすい構文になっている。

typedef double (*fx_ptr_t)(double x);

下記は上記の糖衣構文である。

using fx_ptr_t = double (*)(double x);

脚注

[編集]
  1. ^ 既定のマーシャリングの動作 | Microsoft Docs
  2. ^ あるいは関数指定子とも。IBM Knowledge Center - 左辺値と右辺値
  3. ^ Summit, Steve; 北野 欽一 (1996年2月26日). “C FAQ 4”. 4.12: 関数を呼ぶのに、ポインターを通す方法をみたことがある。どうなってるの。. 2008年10月14日閲覧。
  4. ^ ISO/IEC 14882 | 8.3.3 Pointers to members

関連項目

[編集]

外部リンク

[編集]