C言語で関数ポインタを配列に格納することによって処理を振り分ける

C言語での関数へのポインタについて
でも記述されていますが、C言語における関数へのポインタがよく使用されるケースは以下の2つです。
・関数をコールする際に、引数として関数へのポインタを渡し、呼び出した関数の中でポインタが指している関数を実行する
・関数へのポインタを配列に格納、テーブル化しておくことによりif文やswitch文の条件分岐等をなくしプログラムを簡素化する

今回は後者の例を紹介したいと思います。

作業環境
OS: Windows7
エディタ: Microsoft Visual C++ 2010 Express
(プロジェクト:Win32コンソールアプリケーション)

・function_pointer_sample.cpp(ソースファイル)

#include "stdafx.h"
#include "Cmd.h"

int g_arg1;
int g_arg2;
int g_arg3;

typedef struct {
  int (*pCmd)(int, int);    // コマンド関数へのポインタ
  int arg1;                 // 引数1
  int arg2;                 // 引数2
} CMD_T;

// コマンド登録リスト
// コマンドテーブルと同じ順番にすること
enum {
    CMD01 = 0,
    CMD02,
    CMD03,
    CMD_MAX,
};

// コマンドテーブル
// ここにコマンドを登録する
CMD_T CmdTable[CMD_MAX] = {
    {Cmd01, 1234, 5678},
    {Cmd02, g_arg1, g_arg2},
    {Cmd03, g_arg1, g_arg3},
};

// コマンド実行
int ExecuteCmd(int (*pf)(int, int), int arg1, int arg2)
{
    int ret;

    // コマンドを実行
    ret = (*pf)(arg1, arg2);
    if (ret == 0) {
      // 正常終了の処理
    } else {
      // 異常終了の処理
    }
    return ret;
}

// コマンド検索
int SearchCmd(char *cp)
{
    // 内容省略
    // ここではCMD02が該当したとしている
    return CMD02;
}

// メイン関数
int _tmain(int argc, _TCHAR* argv[])
{
   int ret;
   int nIdx;

   // コマンド検索を行う
   nIdx = SearchCmd("C0002");
   // 登録コマンドかどうか
   if ((CMD01 <= nIdx) && (nIdx < CMD_MAX)) {
	// 登録ありの場合、コマンドを実行
	ret = ExecuteCmd(
           CmdTable[nIdx].pCmd, // コマンド本体
	   CmdTable[nIdx].arg1, // 引数1
	   CmdTable[nIdx].arg2  // 引数2
	);
	if (ret == 0) {
          return 0;
        } else {
          return ret;
        }
    // 登録なし(未定義)
    } else {
      return -1;
    }
}

・Cmd.h(ヘッダーファイル)

#include "stdafx.h"

// プロトタイプ宣言
int Cmd01(int arg1, int arg2);
int Cmd02(int arg1, int arg2);
int Cmd03(int arg1, int arg2);

・Cmd.cpp(ソースファイル)

#include "stdafx.h"

int Cmd01(int arg1, int arg2)
{
    // コマンド01の実装をここへ記述
    return 0;
}

int Cmd02(int arg1, int arg2)
{
    // コマンド02の実装をここへ記述
    return 0;
}

int Cmd03(int arg1, int arg2)
{
    // コマンド03の実装をここへ記述
    return 0;
}

このプロジェクトは次の関数と関数テーブルから構成されています。

_tmain メイン関数
SearchCmd コマンド検索処理
ExecuteCmd コマンド実行処理
CmdXX XXコマンド処理
CmdTable コマンド登録テーブル

状況としては、上位側からコマンド実行要求を受けた時に要求コマンドが下位側で登録されていれば、実行するという流れになります。登録されているか否かは、コマンド登録テーブルに要求コマンドのIndexが存在しているかで判断します。

この例では、コマンド"C0002"を上位側から要求されたと想定しています。
その際にSearchCmdでコマンドの検索を行い、該当するコマンドあれば、0~2の範囲でIndexを返します。
メイン関数では、Indexの範囲チェックをしたのちに、コマンドテーブルに登録されているコマンドを関数ポインタ経由で呼び出していま(ExecuteCmd)。この時に引数も同時に渡せるように、コマンドテーブルが定義されています。

このようにして、実行させるべき処理が状況によっていくつも変わる場合や、後で追加されることが予想される場合、実装を関数化し、関数ポインタをテーブルにすることによって、呼び出し側の処理を簡略化させます。他にもキューに関数ポインタを登録させることで、各処理を順番に実行させるというやり方も良くあります。

C++では、関数をクラスに置き換えることで、実装をさらにシンプル化させることも可能です。

以上になります。


参考:
C言語での関数へのポインタについて
【C言語】関数ポインタを利用して呼び出す関数を動的に変更する