コールバック(call back)関数の必要性 , C言語
昔の自分のコードを見ていて、こうしたらいいのになってのがあったので、メモメモ。
例えば、↓みたいなコードがあったとします。
/**************************** コールバックとは関係無い関数 ******************************/ int32_t func1(void) { int32_t rc; // 何か適当に処理 return (rc); } /************************ いろいろ処理したい関数 *************************/ void func_task(void) { int32_t ret_code; ret_code = func1(); switch (ret_code) { case (1): // 処理A break; case (2): // 処理B break; case (3): // 処理C break; default: // 処理D break; } }
まぁ、func_task()みたいな関数は普通にありえる関数かと思います。
ここで、func_task()の呼び出し側で処理Aの内容をいろいろ使い分けたい場合が発生したとします。
func_task()のような関数を複数作っても良いのですが、処理Aの内容が変わるだけなので、もっと良い方法がありそうです。
処理Aの変更内容が複雑でないなら、func_task()に引数を与える程度で対応可能かもしれませんが、処理Aの内容が複雑で多岐にわたるなら、コールバック(call back)などを使うと効果的かと思います。
ではどうやって使うのか。
まずは、関数を指す関数ポインタの理解から始まります。
例えば下記のようなfunc2の関数ポインタは以下のように記せます。
/********************* コールバック用の関数のポインタ **********************/ int8_t (*p_func)(int32_t); /********************* コールバック用の関数 **********************/ int8_t func2(int32_t) { int8_t retval; // それなりの処理 return (retval); }
これでp_funcは関数ポインタなので、p_func = func2
とできます。
ここで注意しないといけないのはp_funcの型は戻り値がint8_tで引数がint32_tの型にのみ正常に対応できるということです。
もし、戻り値がint32_tの関数をp_funcに指させたら、コンパイルエラーになるか正常に動作しない可能性があります。気をつけましょう。
逆に型が一致すれば良いので、使い分けたい関数を複数作れば、いろいろな状況に対応できるはずです。↓みたいな感じで。
/* わかりやすいようにtypedefして使う */ typedef int8_t (*p_func_type)(int32_t); /**************************** コールバックとは関係無い関数 ******************************/ int32_t func1(void) { int32_t rc; // 何か適当に処理 return (rc); } /************************ いろいろ処理したい関数 *************************/ void func_task(p_func_type p_func, int32_t func_arg, int8_t *func_ret) { int32_t ret_code; ret_code = func1(); switch (ret_code) { case (1): // 処理A *func_ret = p_func(func_arg); break; case (2): // 処理B break; case (3): // 処理C break; default: // 処理D break; } } /************************ コールバック用関数1 *************************/ int8_t func_callback1(int32_t val) { int8_t rc = 0; if( 0 == (val%3) ) { rc = 1; } return (rc); } /************************ コールバック用関数2 *************************/ int8_t func_callback2(int32_t val) { int8_t rc = 0; if( 1000 < (val*val) ) { rc = 1; } return (rc); } /************************ main関数 *************************/ int main (void) { p_func_type p_func; int8_t rc; p_func = func_callback1; func_task(p_func, 10, &rc); // rcにコールバックの戻り値が入る p_func = func_callback2; func_task(p_func, 10, &rc); // rcにコールバックの戻り値が入る return (0); }
とまぁこんな感じの使い方じゃ有効性はあまり示せないかと思うけど、頭の片隅においておけば、使いどころで思い出すかと・・・。
ちなみにtypedefせずにダイレクトで関数ポインタ入れてもOKかと・・・。