この記事には複数の問題があります。改善
やノートページでの議論にご協力ください。この項目では、プログラミングにおけるコールバックについて説明しています。電話回線におけるコールバックについては「コールバック」をご覧ください。
同期的なコールバック方式では、ある関数の引数として渡されたコールバック関数は、その関数内でのみ使われ、関数が終了した後は使われない。非同期的なコールバック方式では、最初にコールバック関数を登録し、後で必要になったときに呼び出す。
コールバック(英: callback)とは、コンピュータプログラミングにおいて、あるサブルーチン(関数など)を呼び出す際に別のサブルーチンを途中で実行するよう指定する手法のこと[1]。呼び出し側(caller)が事前に用意・登録したサブルーチンを、呼び出し先(callee)のコードが「呼び出し返す」ように動作することから、電話回線におけるコールバック(callback)のアナロジーとして命名された手法である。これにより、下位レベル(フレームワーク)の抽象化層が上位レベルの層(アプリケーション)で定義されたサブルーチン(関数など)を呼び出せるようになる。このとき、他の関数の引数として渡される関数は、コールバック関数(callback function)と呼ばれる。関数が第一級オブジェクトである言語において、コールバック関数を引数として受け取る関数は高階関数である。
一般に、まず上位レベルのコードが下位レベルのコードにある関数fnを呼び出すときに、別の関数fcへのポインタや参照を引数として渡す。このとき渡されるのは、言語によっては関数オブジェクトやデリゲートやクロージャなどの形でデータとサブルーチンをカプセル化したものである場合もある。コールバック関数のよくある使い方のひとつは、下位レベルの関数fnを実行中に、引数として渡されたコールバック関数fcを所望の回数呼び出して、部分タスクを実行するというものである。関数fnを抜けた後は、そのコールバック関数fcはもはや使われることはない。別の方式では、下位レベル関数は渡されたコールバック関数を「ハンドラ」(handler)として登録(メモリ上のどこかに保存)し、下位レベルの層で非同期的に(何らかのシグナルやイベントに対する反応の一部として)後で呼び出すのに使う。コールバック関数を一度登録すると、登録解除するまで何度も使われる可能性がある。
コールバックは、ポリモーフィズムとジェネリックプログラミングの単純化された代替手法であり、ある関数の正確な(実際の)動作は、その下位レベル関数に渡される関数ポインタ(ハンドラ)によって変わってくる。これは、コード再利用の非常に強力な技法と言える。構造や作法がよく似ているプログラムであれば、共通部分をフレームワークによって記述してしまい、フレームワークが用意したカスタマイズポイントに適合するコードのみをアプリケーション側でコールバック関数として記述するだけで済むので、アプリケーションごとにすべてのコードを最初から最後まで書き下す必要がなくなる。 コールバックを使う意義を理解するため、連結リスト上の各要素に対して様々な処理を行うという問題を考える。ひとつの手法として、リスト上でのイテレータで各オブジェクトについて処理をするという方法がある。これは実際、最も一般的な手法だが、理想的な方法というわけではない。イテレータを制御するコード(例えば for 文)はリストを辿る処理が存在すると、その度に複製が必要となる。さらに、リストの更新が非同期プロセスで行われている場合、イテレータでリストを辿っている間に要素を飛ばしてしまったり、リストを辿れなくなったりする可能性がある。 代替手法として、新たにライブラリ関数を作り、適当な同期を施して必要な処理を行うようにする。この手法でもリストを辿る必要が生じる度に同様の関数を呼び出す必要がある。この方式は様々なアプリケーションで使われる汎用ライブラリにはふさわしくない。ライブラリ開発ではあらゆるアプリケーションのニーズを予測することはできないし、アプリケーション開発ではライブラリの実装の詳細を知る必要がないのが望ましい。 コールバックが、この問題の解決策となる。リストを辿るプロシージャを書くとき、そのプロシージャがアプリケーションが各要素についての処理を行うコードを提供するようにする。これにより、柔軟性を損なわずに明確にライブラリとアプリケーションを区別することができる。 コールバックは実行時束縛の一種と見ることもできる。 静的型付け言語の場合は、コールバック関数のシグネチャ(引数の数およびデータ型の順序)や戻り値の型といった呼び出しインターフェイスがコンパイル時に確定する。渡せる関数の名前は不問だが、この呼び出しインターフェイスに静的に適合するコールバック関数のみを渡すことができる。動的型付け言語の場合は、コールバック関数の引数の数のみが一致していればよい。 以下のC言語コードは、配列を検索して 5 より大きい値を持つ最初の要素を探す処理を行うものである。まず、イテレータを使った直接的なコードを示す。#include <stdio.h>static void find(const int array[], int length) { int i; for (i = 0; i < length; ++i) { if (array[i] > 5) { break; } } if (i < length) { printf("Item at index %d\n", i); } else { printf("Not found.\n"); }}int main(void) { int array[] = { 5, -6, 1, 8, 10 }; find(array, (int)(sizeof(array) / sizeof(*array))); return 0;} 次に、コールバックを使った間接的なコードを示す。/* ライブラリヘッダー (library.h) */#ifndef MY_LIBRARY_HEADER_ALREADY_INCLUDED#define MY_LIBRARY_HEADER_ALREADY_INCLUDEDtypedef int TraverseCallbackFunctionType(int index, int item, void *param);/* ライブラリ関数のプロトタイプ宣言 */extern int traverseWith(const int array[], int length, TraverseCallbackFunctionType *callback, void *param);#endif/* ライブラリコード (library.c) */#include "library.h"int traverseWith(const int array[], int length, TraverseCallbackFunctionType *callback, void *param) { int exitCode = 0; int i; for (i = 0; i < length; ++i) { exitCode = callback(i, array[i], param); if (exitCode) { break; } } return exitCode;}/* アプリケーションコード (app.c) */#include <stdio.h>#include "library.h"/* コールバック関数の実装 */static int compare(int index, int item, void *param) { if (item > 5) { *(int *)param = index; return 1; } else { return 0; }}/* ライブラリ関数を呼び出す本体 */static void find(const int array[], int length) { int index; int found; found = traverseWith(array, length, compare, &index); if (found) { printf("Item at index %d\n", index); } else { printf("Not found.\n"); }}int main(void) { int array[] = { 5, -6, 1, 8, 10 }; find(array, (int)(sizeof(array) / sizeof(*array))); return 0;} コールバック関数compareのif文の条件を変更すれば、「5より大きい」以外の要素を検索するのにも使える。traverseWith関数には、コールバックが自身の目的のために受け取る追加の引数paramがある点に注意されたい。通常のコールバックでは、そのような引数をスコープ外のアプリケーションデータへのポインタに利用する。これは静的スコープ方式の言語(C や C++)でのみ必要とされる(ただし、C++ を含めたオブジェクト指向言語には別の解決策がある)。動的スコープの言語(関数型言語など)ではクロージャによって自動的にアプリケーションデータへのアクセスが可能となる。例として同じプログラムを LISP で書いた場合を示す。 ; ライブラリコード (defun traverseWith (array callback) (let ((exitCode nil) (i 0)) (while (and (not exitCode) (< i (length array))) (setq exitCode (callback i (aref array i))) (setq i (+ i 1))) exitCode)) ; アプリケーションコード (let (index found) (setq found (traverseWith array (lambda (idx item) (if (<= item 5) nil (setq index idx) t))))) この場合、コールバック関数は使う時点で定義されており、"index" を名前で参照している。これらの例では同期に関する考慮は省略されているが、traverseWith 関数を同期できるように対処するのは容易である。さらに重要なことは、同期するかしないかをその関数の修正だけで対処できる点である。 コールバックの形式はプログラミング言語によって異なる。
背景
例
実装
C言語とC++では、他の関数に関数へのポインタを引数として渡すことができる。
Size:29 KB
出典: フリー百科事典『ウィキペディア(Wikipedia)』
担当:undef