ページ 11

関数ポインタテーブルとswitch文

Posted: 2013年9月14日(土) 23:53
by KENNY

コード:

typedef enum {
    FUNC_EVENT0,
    FUNC_EVENT1,
    FUNC_EVENT2,
} FUNC_EVENT;
typedef void (*FUNC_POINTER)(void*);

typedef struct {
    FUNC_EVENT     event;
    FUNC_POINTER   p_func;
} ST_FUNC_TBL;

/* 関数テーブル */
static ST_FUNC_TBL fnc_tbl[] = {
    {FUNC_EVENT0, func_event_0},
    {FUNC_EVENT1, func_event_1},
    {FUNC_EVENT2, func_event_2},
};
#define fnc_tbl_size   (sizeof(fnc_tbl)/sizeof(fnc_tbl[0]))

/* プロトタイプ */
static void func_event_tbl(FUNC_EVENT event);
static void func_event_switch(FUNC_EVENT event);
static void func_event_0(void);
static void func_event_1(void);
static void func_event_2(void);

int void main(void)
{
    /* 関数ポインタテーブルで呼び出し */
    func_event_tbl(FUNC_EVENT0);
    
    /* Switch文で呼び出し */
    func_event_switch(FUNC_EVENT0);
}

static void func_event_tbl(FUNC_EVENT event)
{
    int i;
    
    for(i = 0; i < fnc_tbl_size; i++){
        if(fnc_tbl[i].event == event){
            fnc_tbl[i].p_func();
        }
    }
}
static void func_event_switch(FUNC_EVENT event)
{
    switch(event){
    case FUNC_EVENT0:
        func_event_0();
        break;
    case FUNC_EVENT1:
        func_event_1();
        break;
    case FUNC_EVENT2:
        func_event_2();
        break;
    default:
        break;
    }
}

static void func_event_0(void)
{
    /* FUNC_EVENT0 に対応した関数 */
}
static void func_event_1(void)
{
    /* FUNC_EVENT1 に対応した関数 */
}
static void func_event_2(void)
{
    /* FUNC_EVENT2 に対応した関数 */
}
こんばんは。初めての投稿です。
上記のように、イベントコマンドに対応した関数を呼び出すプログラムで、二通りの記述方法をよく目にします。
一つは、関数ポインタテーブルを作り、実行するやり方(上記サンプルのfunc_event_tblを使うやり方)。
一つは、switch文でイベントを分類して、実行するやり方(上記サンプルのfunc_event_switchを使うやり方)。

これら二つのやり方の長所・短所はなんでしょうか?
後々イベントを増やす場合は関数ポインタテーブルを使ったほうがメンテナンスが楽なように思いますが、パフォーマンス的には違うのでしょうか?

コメントお願いいたします。

Re: 関数ポインタテーブルとswitch文

Posted: 2013年9月15日(日) 00:03
by KENNY

コード:

typedef enum {
    FUNC_EVENT0,
    FUNC_EVENT1,
    FUNC_EVENT2,
} FUNC_EVENT;
typedef void (*FUNC_POINTER)(void);
 
typedef struct {
    FUNC_EVENT     event;
    FUNC_POINTER   p_func;
} ST_FUNC_TBL;
 
/* プロトタイプ */
static void func_event_tbl(FUNC_EVENT event);
static void func_event_switch(FUNC_EVENT event);
static void func_event_0(void);
static void func_event_1(void);
static void func_event_2(void);
 
/* 関数テーブル */
static ST_FUNC_TBL fnc_tbl[] = {
    {FUNC_EVENT0, func_event_0},
    {FUNC_EVENT1, func_event_1},
    {FUNC_EVENT2, func_event_2},
};
#define fnc_tbl_size   (sizeof(fnc_tbl)/sizeof(fnc_tbl[0]))

int main(void)
{
    /* 関数ポインタテーブルで呼び出し */
    func_event_tbl(FUNC_EVENT0);
    
    /* Switch文で呼び出し */
    func_event_switch(FUNC_EVENT0);
}
 
static void func_event_tbl(FUNC_EVENT event)
{
    int i;
    
    for(i = 0; i < fnc_tbl_size; i++){
        if(fnc_tbl[i].event == event){
            fnc_tbl[i].p_func();
        }
    }
}
static void func_event_switch(FUNC_EVENT event)
{
    switch(event){
    case FUNC_EVENT0:
        func_event_0();
        break;
    case FUNC_EVENT1:
        func_event_1();
        break;
    case FUNC_EVENT2:
        func_event_2();
        break;
    default:
        break;
    }
}
 
static void func_event_0(void)
{
    /* FUNC_EVENT0 に対応した関数 */
}
static void func_event_1(void)
{
    /* FUNC_EVENT1 に対応した関数 */
}
static void func_event_2(void)
{
    /* FUNC_EVENT2 に対応した関数 */
}
すみません、サンプルソースコードにいくつか間違いがありました。
申し訳ありませんでした。
修正いたしました。

よろしくお願いいたします。

Re: 関数ポインタテーブルとswitch文

Posted: 2013年9月15日(日) 00:25
by softya(ソフト屋)
速度的なものは最適化されればさほど気にする物でもありません。
※ 一秒間に数百万回とか呼び出すなら気にしたほうが良いですが、イベントコマンドではそのようなことはないと思います。
なので、違いは読みやすさや組み易さ、メンテナンス性に集約されます。

分岐が少なければswitchが読みやすいでしょうが、分岐が多くなれば関数テーブルに軍配が上がります。
組みやすさとメンテナンス性はケースバイケースです。
といった感じでしょうか。

【補足】
switchも大抵は分岐が増えるとテーブルジャンプなどに機械語変換されます。
※ C言語の規格の話ではなく、コンパイラ毎の実装の問題。
関数テーブルとの違いはswitchが関数内ジャンプ(gotoと考えてもらえれば)と関数テーブルが関数コールで、スタックフレームを作る分だけ関数コールが速度的に若干不利です。
ただ、読みやすさを犠牲にするほどじゃないので読みやすさを再優先で考えるべきです。

Re: 関数ポインタテーブルとswitch文

Posted: 2013年9月16日(月) 10:57
by KENNY
ありがとうございます。
やはり読みやすさを考えて作るのが一番ということですね。
パフォーマンスにはほとんど差がないことがわかり安心しました。