関数ポインタ(サブルーチンポインタまたはプロシージャポインタとも呼ばれる)は、データではなく実行可能コードを参照するポインタです。関数ポインタを逆参照すると、参照先の関数が得られ、通常の関数呼び出しと同様に、その関数を呼び出して引数を渡すことができます。このような呼び出しは、関数が固定の識別子やアドレスを介して直接呼び出されるのではなく、変数を介して間接的に呼び出されるため、「間接呼び出し」とも呼ばれます。
関数ポインタを使用すると、実行時に異なるコードを実行できます。また、関数に渡してコールバックを有効にすることもできます。
関数ポインタは、第3世代プログラミング言語(PL/I、COBOL、Fortran、[ 1 ] dBASE dBL 、Cなど)およびオブジェクト指向プログラミング言語( C++、C#、Dなど)でサポートされています。[ 2 ]
関数(またはサブルーチン)ポインタの最も単純な実装は、実行可能メモリ内の関数のアドレスを含む変数として実装することです。PL /IやCOBOLなどの古い第3世代言語、およびPascalやCなどのより新しい言語は、一般的にこの方法で関数ポインタを実装しています。[ 3 ]
関数ポインタはC#バージョン 9.0で導入されましたdelegate*。[ 4 ]
以下のCプログラムは、2つの関数ポインタの使用方法を示しています。
func11 つの倍精度 (double) パラメータを受け取り、別の double を返します。また、センチメートルをインチに変換する関数に割り当てられます。func2定数文字配列へのポインタと整数を受け取り、文字へのポインタを返します。また、文字配列内の指定された文字の最初の出現箇所へのポインタを返すC文字列処理関数に割り当てられます。#include <stdio.h> #include <string.h>double cm_to_inches ( double cm ) { return cm / 2.54 ; }// "strchr" は C 言語の文字列処理の一部です (つまり、宣言は不要です) // https://en.wikipedia.org/wiki/C_string_handling#Functions を参照してくださいint main ( void ) { double ( * func1 )( double ) = cm_to_inches ; char * ( * func2 )( const char * , int ) = strchr ; printf ( "%f %s" , func1 ( 15.0 ), func2 ( "Wikipedia" , 'p' )); // "5.905512 pedia" と出力return 0 ; }次のプログラムは、関数ポインタを使用して、別の関数 ( 、関数のリーマン積分sinの近似値を計算 ) から 2 つの関数 (または) のいずれかを間接的に呼び出します。このプログラムは、関数 が関数 を 2 回呼び出し、 1 回目はライブラリ関数へのポインタを、 2 回目は関数 へのポインタを渡すことで動作します。関数は、関数ポインタ引数を複数回逆参照することで、2 つの関数のいずれかを間接的に呼び出し、呼び出された関数が返す値を合計し、結果の合計を返します。2 つの合計は、 によって標準出力に書き込まれます。coscompute_summaincompute_sumsincoscompute_sumfuncpmain
#include <math.h>#include <stdio.h>// 関数ポインタを引数として受け取る関数double compute_sum ( double ( * funcp )( double ), double lo , double hi ) {double sum = 0.0 ;// 指し示す関数 '*funcp' が返す値を追加しますfor ( int i = 0 ; i <= 100 ; i ++ ) {// 関数ポインタ「funcp」を使用して関数を呼び出すdouble x = i / 100.0 * ( hi - lo ) + lo ;double y = funcp ( x );sum += y ;}return sum / 101.0 * ( hi - lo );}ダブルスクエア(ダブルx ){x * xを返す;}int main ( void ) {2倍の合計;// 参照先の関数として標準ライブラリ関数 'sin()' を使用しますsum = compute_sum ( sin , 0.0 , 1.0 );printf ( "sum(sin): %g \n " , sum );// 参照先の関数として標準ライブラリ関数 'cos()' を使用しますsum = compute_sum ( cos , 0.0 , 1.0 );printf ( "sum(cos): %g \n " , sum );// 参照先の関数としてユーザー定義関数 'square()' を使用しますsum = compute_sum ( square , 0.0 , 1.0 );printf ( "sum(square): %g \n " , sum );0を返す;}ファンクタ、または関数オブジェクトは、関数ポインタに似ており、同様の方法で使用できます。ファンクタは、関数呼び出し演算子を実装するクラス型のオブジェクトであり、関数呼び出しと同じ構文を使用して式内でオブジェクトを使用できます。ファンクタは、独自のデータ値を保持でき、プログラマがクロージャをエミュレートできるため、単純な関数ポインタよりも強力です。また、メンバ関数をコールバック関数として使用する必要がある場合は、コールバック関数としても使用されます。[ 5 ]
多くの「純粋な」オブジェクト指向言語は関数ポインタをサポートしていません。ただし、これらの言語でも、単一のメソッド(メンバ関数)を定義するインターフェースへの参照を使用することで、同様の機能を実装できます。C #やVisual Basic .NETなどのCLI 言語は、デリゲートを使用して型安全な関数ポインタを実装しています。
第一級関数をサポートする他の言語では、関数はデータとして扱われ、他の関数によって直接渡されたり、返されたり、動的に作成されたりするため、関数ポインタは不要になります。
関数ポインタを多用して関数を呼び出すと、最新のプロセッサではコードの実行速度が低下する可能性があります。これは、分岐予測器がどこに分岐すべきかを判断できない場合があるためです(分岐予測器は実行時の関数ポインタの値に依存します)。ただし、この影響は、インデックスなしのテーブル参照が大幅に削減されることで十分に相殺されることが多いため、過大評価されている可能性があります。
C++ はオブジェクト指向プログラミングをサポートしているため、クラスはメソッド(通常はメンバ関数と呼ばれます) を持つことができます。非静的メンバ関数 (インスタンスメソッド) は、操作対象のオブジェクトへのポインタである暗黙のパラメータ ( thisポインタ) を持ちます。そのため、オブジェクトの型は関数ポインタの型の一部として含める必要があります。メソッドは、そのクラスのオブジェクトに対して、メンバへのポインタ演算子 (オブジェクトまたはオブジェクトへのポインタの場合) のいずれかを使用して実行され.*ます->*。
C および C++ の関数ポインタは単純なアドレスとして実装できますが、sizeof(Fx) == sizeof(void*)C++ のメンバポインタは、仮想メソッドや仮想継承を扱うために、単純な関数ポインタの 2 倍または 3 倍のサイズの「ファットポインタ」として実装されることがあります。
C++では、Cで使用される方法に加えて、 C++標準ライブラリのクラステンプレートstd::functionを使用することもできます。このクラスのインスタンスは関数オブジェクトです。
import std ;static double derivative ( const std :: function < double ( double ) > & f , double x0 , double eps ) { double eps2 = eps / 2 ; double lo = x0 - eps2 ; double hi = x0 + eps2 ; return ( f ( hi ) - f ( lo )) / eps ; }static double f ( double x ) { return x * x ; }int main () { double x = 1 ; std :: println ( "d/dx(x^2) [@ x = {}] = {}" , x , derivative ( f , x , 1e-5 )); return 0 ; }これは、C++ がクラスや構造体のメンバ関数を扱う際に関数ポインタを使用する方法です。これらはオブジェクトポインタまたは this 呼び出しを使用して呼び出されます。型が安全であるため、そのクラス (またはその派生クラス) のメンバは、その型のポインタを使用してのみ呼び出すことができます。この例では、簡潔にするためにメンバ関数へのポインタにtypedefを使用する方法も示しています。静的メンバ関数への関数ポインタは、this 呼び出しにオブジェクトポインタが必要ないため、従来の C スタイルで行われます。
import std ;class Foo { public : [[ nodiscard ]] static int add ( int i , int j ) noexcept { return i + j ; }[[ nodiscard ]] static int mult ( int i , int j ) noexcept { return i * j ; }[[ nodiscard ]] static int negate ( int i ) noexcept { return - i ; } };int bar1 ( int i , int j , Foo * pFoo , int ( Foo ::* pfn )( int , int )) { return ( pFoo ->* pfn )( i , j ); }typedef int ( Foo ::* Foo_pfn )( int , int );int bar2 ( int i , int j , Foo * pFoo , Foo_pfn pfn ) { return ( pFoo ->* pfn )( i , j ); }typedef auto ( * PFN )( int ) -> int ; // C++ のみ、以下と同じ: typedef int(*PFN)(int);int bar3 ( int i , PFN pfn ) { return pfn ( i ); }int main () { Foo foo ; std :: println ( "Foo::add(2,4) = {}" , bar1 ( 2 , 4 , & foo , & Foo :: add )); std :: println ( "Foo::mult(3,5) = {}" , bar2 ( 3 , 5 , & foo , & Foo :: mult )); std :: println ( "Foo::negate(6) = {}" , bar3 ( 6 , & Foo :: negate )); return 0 ; }上記のCおよびC++構文はtypedef、すべての教科書で使用されている標準的な構文ですが、読みにくく説明しにくいものです。上記の例でもこの構文が使用されています。しかし、すべてのCおよびC++コンパイラは、関数ポインタを宣言するためのより明確で簡潔なメカニズムをサポートしています。それは、`<input type="function_pointer">` を使用するもののtypedef、ポインタを定義の一部として格納しないというものです。この種の `<input type="function_pointer">` が実際に使用できる唯一の方法はtypedefポインタを使用することですが、これはポインタの性質を強調するものです。
// これは、'char' 型を受け取り 'int' 型を返す関数 'F' を宣言します。定義は別の場所にあります。int F ( char c );// これは、'char' 型を受け取り 'int' 型を返す関数 'Fn' を定義します。typedef int Fn ( char c );// これは、'Fn' へのポインタ型の変数 'fn' を定義し、'F' のアドレスをそれに割り当てます。Fn * fn = & F ; // 注: '&' は必須ではありませんが、何が行われているかを強調します。// これは 'fn' を使用して 'F' を呼び出し、結果を変数 'a' に代入しますint a = fn ( 'A' );// これは、'Fn' へのポインタを受け取り、それを呼び出し、結果を返す関数 'Call' を定義します。int Call ( Fn * fn , char c ) { return fn ( c ); } // Call(fn, c)// これは関数 'Call' を呼び出し、'F' を渡して結果を 'call' に代入しますint call = Call ( & F , 'A' ); // ここでも '&' は不要です// レガシー: 既存のコードベースを維持するために、上記の定義スタイルを最初に使用することもできます。//その後、新しいスタイルを使用して、元の型をそのスタイルに基づいて定義できます。// これは、型 Fn へのポインタの型である 'PFn' を定義します。typedef Fn * PFn ;// 'PFn' は 'Fn *' が使える場所ならどこでも使えますPFn pfn = F ; int CallP ( PFn fn , char c );これらの例では、上記の定義を使用しています。特に、上記の定義はFnメンバ関数へのポインタ定義で使用できることに注意してください。
// これは、同様の静的関数とメンバ関数を持つクラス「C」を定義し、// 次に「c」というインスタンスを作成します。class C { public : static int Static ( char c ); int Member ( char c ); } c ; // C// これは「C」へのポインタ「p」を定義し、「c」のアドレスをそれに割り当てますC * p = & c ;// これは 'fn' に 'Static' へのポインタを割り当てます。// 'this' がないため、'Fn' は正しい型であり、上記のように 'fn' を使用できます。fn = & C :: Static ;// これは、型が 'Fn' の 'C' のメンバへのポインタ 'm' を定義し、// 'C::Member' のアドレスをそれに割り当てます。// すべてのポインタと同様に、右から左に読むことができます。// "'m' は、型 'Fn' のクラス 'C' のメンバへのポインタです" Fn C ::* m = & C :: Member ;// これは 'm' を使用して 'c' 内の 'Member' を呼び出し、その結果を 'cA' に代入しますint cA = ( c . * m )( 'A' );// これは 'm' を使用して 'p' 内の 'Member' を呼び出し、その結果を 'pA' に代入しますint pA = ( p ->* m )( 'A' );// これは、'C' への参照、// 'Fn' 型の 'C' のメンバへのポインタ、および 'char' を受け取り、// 関数を呼び出して結果を返す関数 'Ref' を定義します。 int Ref ( C & r , Fn C ::* m , char c ) { return ( r . * m )( c ); } // Ref(r, m, c)// これは、'C' へのポインタ、// 'Fn' 型の 'C' のメンバへのポインタ、および 'char' を受け取り、// 関数を呼び出して結果を返す関数 'Ptr' を定義します。 int Ptr ( C * p , Fn C ::* m , char c ) { return ( p ->* m )( c ); } // Ptr(p, m, c)// レガシー: 既存のコードベースを維持するために、上記の定義スタイルを最初に使用することもできます。//その後、新しいスタイルを使用して、元の型をそのスタイルに基づいて定義できます。// これは、型 'Fn' のクラス 'C' のメンバーへのポインタの型である 'FnC' を定義します。typedef Fn C ::* FnC ;// 'FnC' は 'Fn C::*' が使用できる場所ならどこでも使用できますFnC fnC = & C :: Member ; int RefP ( C & p , FnC m , char c );PL/Iプロシージャはネストすることができ、つまり、プロシージャ A はプロシージャ B を含み、プロシージャ B はさらに C を含むことができます。B は、B で宣言されたデータに加えて、定義を上書きしない限り、A で宣言された任意のデータを参照することもできます。同様に、C は A と B の両方のデータを参照できます。したがって、PL/I ENTRY 変数にはコンテキスト[ 6 ]を含める必要があり、プロシージャ C が呼び出された時点での B と A のデータの値のアドレスをプロシージャ C に提供する必要があります。
関数ポインタは、関数のアドレスを指すポインタ、つまり変数です。
重要な注意:関数ポインタは常に特定のシグネチャを持つ関数を指します。したがって、同じ関数ポインタで使用したいすべての関数は、同じパラメータと戻り値の型を持つ必要があります。
メンバー関数をコールバック関数として使用する場合は、メンバー関数を呼び出す前に、そのメンバー関数をクラスのオブジェクトに関連付ける必要があります。この場合、ファンクタを使用できます [このページに例があります]。