ホーム » スタッフ » 斉藤徹 » 関数ポインタとコールバック

2014年1月
« 12月   2月 »
 1234
567891011
12131415161718
19202122232425
262728293031  

最近の投稿(電子情報)

アーカイブ

カテゴリー

関数ポインタとコールバック

例年より授業の進度が速いので、 全体の総括の前に、追加ネタを加える。

関数ポインタは、オブジェクト指向の仮想関数の実装であったり、 イベント駆動型のプログラミングで重要なテクニックなので紹介する。

関数ポインタの基礎

基本的には、関数ポインタは以下のように使用する。 以下のサンプルプログラムでは、(*f)( 2 , 3 ) にて、fに代入されている関数を、 引数(2,3) にて呼び出す。

int fadd( int x , int y ) {
return x + y ;
}
int fmul( int x , int y ) {
return x * y ;
}
void main() {
// 型は2つのintを引数を持つintを返り値とする関数へのポインタ
int (*f)( int , int ) ;
f = fadd ; // 関数名の後ろに丸カッコがない点に注目
printf( "%d¥n" , (*f)( 2 , 3 ) ) ; // 2+3=5
f = fmul ;
printf( "%d¥n" , (*f)( 2 , 3 ) ) ; // 2*3=6
}

関数ポインタは、ライブラリを利用する際に、自分なりの変更を加えたい部分があったら、 その処理を関数で記述しておき、その関数のポインタをライブラリに渡す。 ライブラリは、必要に応じて関数ポインタを使って、関数を呼び出す。 このように、ライブラリの中から自分の渡した関数を呼び出してもらうのは、 「コールバック関数」と呼ばれる。

以下に簡単な例として、配列の最大データの添字を返すプログラムの例を示す。 この方法であれば、異なるデータの型でもコールバック関数を専用に作るだけで良い。

int intcmp( int* x , int* y ) { // 整数比較関数
if ( *x > *y )
return 1 ;
else if ( *x < *y )
return -1 ;
else
return 0 ;
}
int vmax( void* array ,    // 配列先頭アドレス
int size ,       // 配列データ件数
int sizeofdata , // 1件あたりのbyte数
int(*f)( void*,void* ) ) { // 比較関数
int i , max = 0 ;
for( i = 0 ; i < size ; i++ )
if ( (*f)( array + max * sizeofdata ,
array + i   * sizeofdata ) < 0 )
max = i ;
return max ;
}
int idata[ 4 ] = { 11 , 33 , 22 , 44 } ;
char sdata[ 4 ][ 4 ] = { "ab" , "bc" , "aa" , "c" } ;
void main() {
int m ;
// intcmp関数を使って、idata から最大値を探す
m = vmax( idata , 4 , sizeof(int) , intcmp ) ;
printf( "%d" , idata[ m ] ) ;
// strcmp関数を使って、sdata から最大値を探す
m = vmax( sdata , 4 , sizeof(sdata[0]) , strcmp ) ;
printf( "%s" , sdata[ m ] ) ;
}

この方式をそのまま使った便利な関数が、C言語のクイックソートの標準関数 qsort( void* , sizt_t , size_t , int(*)( void*,void* ) ) である。この関数を使えば、データ比較用のコールバック関数を書くだけで、 配列の高速ソートが可能となる。

// LLVMなどの新しいC言語では、無名関数みたいな
// block pointer ってぇのが使えるらしい。
int (^f)( int , int ) ;
f = ^( int x , int y ) {
return x + y ;
} ;
printf( "%d¥n" , f( 2 , 3 ) ) ;
f = ^( int x , int y ) {
return x * y ;
} ;
printf( "%d¥n" , f( 2 , 3 ) ) ;

オブジェクト指向での仮想関数

関数ポインタは、オブジェクト指向での仮想関数機能の実現に利用されている。

// 純粋仮想基底クラス
class A {
public:
virtual void print() { printf( "A¥n" ) ; }
} ;
// 派生クラス A_int
class A_int : public A {
private:
int data ;
public:
A_int( int x ) { data = x ; }
virtual void print() { printf( "A_int : %d¥n" , data ) ; }
} ;
// 派生クラス A_str
class A_str : public A {
private:
const char* data ;
public:
A_str( const char* x ) { data = x ; }
virtual void print() { printf( "A_str : %s¥n" , data ) ; }
} ;
int main() {
A      aa ;
A_int  ai( 10 ) ;
A_str  as( "hello" ) ;
// array[] には、異なる型のデータ(同じ基底クラス)が入ってる。
A* array[] = { &aa , &ai , &as } ;
for( int i = 0 ; i < 3 ; i++ ) {
array[ i ]-> print() ;
}
// 実行結果
// A
// A_int : 10
// A_str : hello
return 0 ;
}

JavaScriptと無名関数

JavaScript では、イベント駆動のプログラムを実現するために、無名関数を利用することが多い。

var func ;
func = function( x , y ) {
return x + y ;
} ;
func( 2 , 3 ) ; // 答えは5
function foo( func ) {
alert( func( 2 , 3 ) ) ;
}
foo( function(int x,int y) { return x * y ; } ) ;
// alertで6が表示される