関数ポインタの使い方?
今回は「関数ポインタ」に関してなんとなく。
まず一言。
C++、Java等の
・「関数のオーバーロード」
・「関数のオーバーライド」
あたりが使えない・理解できないなら、
使わない方がいい、というか使ってはいけない。
なぜなら、関数ポインタを使わなくても、大抵の場合は実現可能だから。
ただ、使った方がスマートにまとまることもあるかなと、それだけの話なので。
と、以上をふまえた上で以下続きへ。
で、関数ポインタってどう使うの?という話。
先述の例でわかる人はもうピンと来ている人も居ると思うが、
・「同一の機能」だが「処理内容が違う」場合
とかに
実際に実行する部分を関数ポインタにしておいて差し替えて使うと
キレイにまとまったりすることがある。
例えば、「書く」と言っても「(鉛筆で)書く」とか「(ペンで)書く」とかいろいろあるわけで、
これをプログラムチックに書くとすると、
// 「書く」 Write() { ... if( use_pencil == true ) { WritePencil(); } else { WritePen(); } ... }
こんな感じになったり、はたまた引数(パラメータ)を使って、
// 「書く」 Write( int tool ) { ... if( tool == PENCIL ) { // 鉛筆処理 } else { // 何か別の処理 } ... }
こんな感じにしてやれば、一応一つにまとめられて、
呼び出し側も引数を気にするだけで済む。
しかし、関数ポインタを使ってやれば、
void *pWrite(); ... // どこかで処理内容を設定(←objの生成時とかその辺が理想?) pWrite = WritePencil; ... // 「書く」 (*pWrite)(); ...
どこかで処理内容を設定してやれば、呼び出し側は処理内容を気にせず使えると。
そんな感じ。
(…まぁ、その「どこか」が問題になったりするんだけど…(汗))
で、抽象的な話ばっかりしてても仕方がないので、具体例。
僕の場合は「ループ処理の差し替え」なんかに使ったりしてます。
以下、実際のソースの一部。
/*===================================== メイン関数 (ループ切り替え・Escによる強制終了など) =====================================*/ int main() { int (*pfunc[LOOP_VAR_NUM])() = { Title, GameMain, Replay, Config, Clear, GameOver }; int (*process)() = (pfunc[0]); int process_return = 0, process_num = 0; extern int g_wait_method; // 画面更新の仕方を調べる g_wait_method = ReadConfigData( "DirectGraphic CONFIG", "VSYNC wait" ); // 「1000」ならタイマも加味 if( g_wait_method == 1000 ) { g_wait_method = 0; } // キーコンフィグ反映 SetKeyConfig(); // ループ処理 while( process_return >= 0 ) { // 各メインループへ飛ぶ process_return = (*process)(); // 異常な返り値でなければ次のループをセット if( process_return >= 0 ) { process_num = process_return; process = pfunc[process_num]; } // もしEscでの脱出なら終了させる if( GetAsyncKeyState( VK_ESCAPE ) ) { break; } } return 0; }
こんな感じであらかじめ処理したい項目を配列で持っておいて、
次のループは「今回のループの返り値の処理」をするようにしておくと、
追加・削除・差し替え等が楽なのでオススメ。
ただ、関数ポインタには大きな弱点がありまして。
それは「引数・返り値の変更ができない」こと。
コレをやるには、C++の純粋仮想関数とかにまで手を出さないといけないわけですが。
…といっても、機能としては同一なわけだから、
パラメータの種類が変更できないのは当たり前…か?