メンバ関数のテーブル化

プログラムを組んでいると膨大な量の関数へのアクセスを他のパラメータで表現したい場合、特に数値によって実行する関数を切り替えたい事態に遭遇する。こういう場合、関数へのポインタの配列を作成しそれを実行する。 switch 〜 case 文を使っていても、最近のコンパイラは自動的にテーブル化してくれるようだが、プログラムの可読性はかなりひどくなったり、美しくなかったりする場合も少なくない。ある一定量を超えたら(その境界は目的と経験と感による)、関数のテーブル化は必須であると思われる。そんな中、CPPのクラスのメンバ関数をテーブル化するにはどうすればいいのかという質問をよく見かけるようになったので、その方法について解説する。

 

staticメンバ関数

まず、メンバ関数をテーブル化したい時の状況の例を説明する。サンプルとして以下のようなクラスと実行するプログラムを考える。

class CTest1{
public:
    static void Test0(void);
    static void Test1(void);
    static void Test2(void);
    void ExecFunc(int nIndex);
};

void CTest1::Test0(void){}
void CTest1::Test1(void){}
void CTest1::Test2(void){}

void CTest1::ExecFunc(int nIndex)
{
    switch(nIndex){
    case 0:
        Test0();
        break;
    case 1:
        Test1();
        break;
    case 2:
        Test2();
        break;
    default:
        break;
    }
}

int main(void)
{
    CTest1 test;

    test.ExecFunc(0);
    test.ExecFunc(1);
    test.ExecFunc(2);

    return EXIT_SUCCESS;
}

CTest1::ExecFuncを関数へのポインタの配列を使ったテーブルアクセスに書き換えると、以下のようになる。

void (*apfnTable1[])(void) = { 
    &CTest1::Test0, 
    &CTest1::Test1,
    &CTest1::Test2,
}; 

void CTest1::ExecFunc(int nIndex)
{
    apfnTable1[nIndex](); 
}

 

staticメンバ関数

前の例はメンバ関数が static な場合であった。次に一般的な非staticなメンバ関数について考える。重要な個所をで示した。

class CTest2{
public:
    void Test0(void);
    void Test1(void);
    void Test2(void);
    void ExecFunc(int nIndex);
};

void CTest2::Test0(void){}
void CTest2::Test1(void){}
void CTest2::Test2(void){}

void (CTest2::*apfnTable2[])(void) = { 
    &CTest2::Test0, 
    &CTest2::Test1,
    &CTest2::Test2,
}; 

void CTest2::ExecFunc(int nIndex)
{
    (this->*apfnTable2[nIndex])(); 
}

int main(void)
{
    CTest2 test;

    test.ExecFunc(0);
    test.ExecFunc(1);
    test.ExecFunc(2);

    (test.*apfnTable2[0])();
    (test.*apfnTable2[1])();
    (test.*apfnTable2[2])();

    CTest2 *ptest = new CTest2;

    (ptest->*apfnTable2[0])();
    (ptest->*apfnTable2[1])();
    (ptest->*apfnTable2[2])();

    delete ptest;

    return EXIT_SUCCESS;
}

 

privateメンバ関数

テーブル化したいメンバ関数がprivateメンバ関数の場合、テーブルはクラス内のメンバとする必要がある。以下にその方法を示す。privateでない場合でも、テーブルをクラス内におきたい場合も同じような方法を使用する。

メンバへのポインタ(::*)と、スコープ解決演算子(::)の使い方に注意せよ!

class CTest3{
private:
    void Test0(void);
    void Test1(void);
    void Test2(void);
public:
    void ExecFunc(int nIndex);
    static void (CTest3::*apfnTable[])(void); 
};

void CTest3::Test0(void){}
void CTest3::Test1(void){}
void CTest3::Test2(void){}

void (CTest3::*CTest3::apfnTable[])(void) = { 
    &CTest3::Test0, 
    &CTest3::Test1,
    &CTest3::Test2,
}; 

void CTest3::ExecFunc(int nIndex)
{
    (this->*apfnTable[nIndex])(); 
}

int main(void)
{
    CTest3 test;

    test.ExecFunc(0);
    test.ExecFunc(1);
    test.ExecFunc(2);

    (test.*CTest3::apfnTable[0])();
    (test.*CTest3::apfnTable[1])();
    (test.*CTest3::apfnTable[2])();

    CTest3 *ptest = new CTest3;

    (ptest->*CTest3::apfnTable[0])();
    (ptest->*CTest3::apfnTable[1])();
    (ptest->*CTest3::apfnTable[2])();

    delete ptest;

    return EXIT_SUCCESS;
}

 

継承

CPPのクラスを使うのなら、継承についても考えみたい。以下に2通りのサンプルとアクセス例を示す。上記の説明とCPPの継承が理解できていれば、容易にわかるはずだか、このサンプルが理解できない間はこの手の手法に手を出さないほうが無難と思われる。十分理解した上で使用しないと、思わぬ動作を引き起こすことになるだろう。

サンプル1

class CBase;
typedef void (CBase::*FUNC)(void);

class CBase{
public:
    virtual int GetNumFunc(void) = 0;
    virtual FUNC GetFunc(int nIndex) = 0;
    virtual void ExecFunc(int nIndex) = 0;
};

class CDerived0 : public CBase{
private:
    static const int m_nNumFunc;
    static void (CDerived0::*apfnTable[])(void);
public:
    int GetNumFunc(void){return m_nNumFunc;}
    FUNC GetFunc(int nIndex){return (FUNC)apfnTable[nIndex];}
    void ExecFunc(int nIndex){(this->*apfnTable[nIndex])();}
    void Test0(void){printf("D0T0\n");}
    void Test1(void){printf("D0T1\n");}
};

void (CDerived0::*CDerived0::apfnTable[])(void) = {
    &CDerived0::Test0,
    &CDerived0::Test1,
};

const int CDerived0::m_nNumFunc = sizeof(CDerived0::apfnTable) / sizeof(CDerived0::apfnTable[0]);

class CDerived1 : public CBase{
private:
    static const int m_nNumFunc;
    static void (CDerived1::*apfnTable[])(void);
public:
    int GetNumFunc(void){return m_nNumFunc;}
    FUNC GetFunc(int nIndex){return (FUNC)apfnTable[nIndex];}
    void ExecFunc(int nIndex){(this->*apfnTable[nIndex])();}
    void Test0(void){printf("D1T0\n");}
    void Test1(void){printf("D1T1\n");}
    void Test2(void){printf("D1T2\n");}
};

void (CDerived1::*CDerived1::apfnTable[])(void) = {
    &CDerived1::Test0,
    &CDerived1::Test1,
    &CDerived1::Test2,
};

const int CDerived1::m_nNumFunc = sizeof(CDerived1::apfnTable) / sizeof(CDerived1::apfnTable[0]);

int main(void)
{
    int i;

    CBase *pBase0 = new CDerived0;
    CBase *pBase1 = new CDerived1;

    for(i = 0; i < pBase0->GetNumFunc(); i++) pBase0->ExecFunc(i);
    for(i = 0; i < pBase1->GetNumFunc(); i++) pBase1->ExecFunc(i);

    for(i = 0; i < pBase0->GetNumFunc(); i++){
        FUNC pfn = pBase0->GetFunc(i);
        (pBase0->*pfn)();
    }
    for(i = 0; i < pBase1->GetNumFunc(); i++){
        FUNC pfn = pBase1->GetFunc(i);
        (pBase1->*pfn)();
    }

    delete pBase0;
    delete pBase1;

    return 0; 
}

サンプル2

class CBase;
typedef void (CBase::*FUNC)(void);

class CBase{
private:
    static const int m_nNumFunc;
    static FUNC apfnTable[];
public:
    virtual int GetNumFunc(void){return m_nNumFunc;}
    virtual FUNC GetFunc(int nIndex){return apfnTable[nIndex];}
    virtual void ExecFunc(int nIndex){(this->*apfnTable[nIndex])();}
    virtual void Test0(void){printf("BT0\n");}
    virtual void Test1(void){printf("BT1\n");}
    virtual void Test2(void){printf("BT2\n");}
};

FUNC CBase::apfnTable[] = {
    &CBase::Test0,
    &CBase::Test1,
    &CBase::Test2,
};

// くどいようだが、FUNC CBase::apfnTable[] は、以下のようも書ける事を知っておいてほしい
// void (CBase::*CBase::apfnTable[])(void) 


const int CBase::m_nNumFunc = sizeof(CBase::apfnTable) / sizeof(CBase::apfnTable[0]);

class CDerived0 : public CBase{
public:
    // GetNumFunc をオーバーライドしない場合の動作も試していただきたい
    int GetNumFunc(void){return 2;}
    void Test0(void){printf("D0T0\n");}
    void Test1(void){printf("D0T1\n");}
};

class CDerived1 : public CBase{
public:
    int GetNumFunc(void){return 3;}
    void Test0(void){printf("D1T0\n");}
    void Test1(void){printf("D1T1\n");}
    void Test2(void){printf("D1T2\n");}
};

int main(void)
{
    int i;

    CBase *pBase0 = new CDerived0;
    CBase *pBase1 = new CDerived1;

    for(i = 0; i < pBase0->GetNumFunc(); i++) pBase0->ExecFunc(i);
    for(i = 0; i < pBase1->GetNumFunc(); i++) pBase1->ExecFunc(i);

    for(i = 0; i < pBase0->GetNumFunc(); i++){
        FUNC pfn = pBase0->GetFunc(i);
        (pBase0->*pfn)();
    }
    for(i = 0; i < pBase1->GetNumFunc(); i++){
        FUNC pfn = pBase1->GetFunc(i);
        (pBase1->*pfn)();
    }

    delete pBase0;
    delete pBase1;

    return 0; 
}