第58章 メンバ関数ポインタ天国2

 メンバ関数ポインタの使い方については前回ので問題ありません。今回は、メンバ関数ポインタについての細かい話をしようと思います。


 では、今回の要点です。


 では、いってみましょう。


 前回はメンバ関数ポインタの文法と使い方について話しただけだったので、先ずは1つ例でも挙げておきましょう。

プログラム1
// Lane.h
// 実行レーンクラス
template <typename t_Lane>
class CLane
{
private:
    // enum はクラスの中でも定義できます
    // int 型の定数を使いたい場合によくやります
    enum Const{ LaneSize = 16, };      // レーンのサイズ
    typedef void (t_Lane::*FPLANE)();  // 処理関数の型

private:
    t_Lane* m_pHandlee;         // 操作対象
    FPLANE m_fpLane[LaneSize];  // 実行レーン
    int    m_nLane;             // 処理数

public:
    CLane(t_Lane& rHandlee)
        : m_pHandlee(&rHandlee), m_nLane(0) { }
    void Set(t_Lane& rHandlee)  // オブジェクトのすり替え
        { m_pHandlee = &rHandlee; }

public:
    bool Push(FPLANE fpLane);     // レーンに処理を追加
    void Do();                    // 処理を全実行
    void Clear(){ m_nLane = 0; }  // レーンを全削除
};

// レーンに処理を追加
template <typename t_Lane>
bool CLane<t_Lane>::Push(FPLANE fpLane)
{
    if(m_nLane >= LaneSize)
        return false;
    // この書き方は第7章を参照
    m_fpLane[m_nLane++] = fpLane;
    return true;
}

// 処理を全実行
template <typename t_Lane>
void CLane<t_Lane>::Do()
{
    for(int i = 0; i < m_nLane; i++)
        (m_pHandlee->*m_fpLane[i])();
}
プログラム2
// MemFP1.cpp
#include <iostream.h>
#include "Lane.h"

class CCalc
{
private:
    int m_a, m_b;

public:
    CCalc(){ m_a = 0; m_b = 0; }

public:
    // 値のセット
    void Set(int a, int b){ m_a = a; m_b = b; }

public:
    // 各種計算を行い、結果を表示
    void Add();
    void Sub();
    void Mul();
    void Div();
    void Mod();
};

// 足し算
void CCalc::Add()
{
    cout << m_a << " + " <<
            m_b << " = " << m_a + m_b << endl;
}

// 引き算
void CCalc::Sub()
{
    cout << m_a << " - " <<
            m_b << " = " << m_a - m_b << endl;
}

// 掛け算
void CCalc::Mul()
{
    cout << m_a << " * " <<
            m_b << " = " << m_a * m_b << endl;
}

// 割り算
void CCalc::Div()
{
    cout << m_a << " / " <<
            m_b << " = " << m_a / m_b << endl;
}

// 余り
void CCalc::Mod()
{
    cout << m_a << " % " <<
            m_b << " = " << m_a % m_b << endl;
}

int main()
{
    CCalc calc;
    calc.Set(51, 7);

    CLane <CCalc> lane(calc);
    lane.Push(CCalc::Mul);  // 51 * 7
    lane.Push(CCalc::Mod);  // 51 % 7
    lane.Push(CCalc::Add);  // 51 + 7
    lane.Push(CCalc::Div);  // 51 / 7
    lane.Push(CCalc::Sub);  // 51 - 7
    lane.Do();

    return 0;
}
実行結果
51 * 7 = 357
51 % 7 = 2
51 + 7 = 58
51 / 7 = 7
51 - 7 = 44

 CLane は、メンバ関数ポインタを登録しておいて、あとで一気にその全てを実行するためのクラステンプレートです。仕組みは実に簡単で、登録して、実行するだけです(笑)。先に登録した方を先に実行します(このような処理形態を「先入れ先出し(FIFO:First In First Out)」と呼びます)。

 関数ポインタは typedef して使うと便利第9章で言ったように、登録する関数の型は FPLANE という名前に typedef して使っています(typedef をクラス内で使っても問題ありません)。

 そして MemFP1.cpp では、CCalc という計算結果を表示するクラスを作り、CLane を利用しています。

 このように、メンバ関数ポインタを使えば逐次処理を登録してまとめて実行するということができます。他にも、あるフラグ変数によって処理を分岐させたいときにメンバ関数ポインタを使って分岐する、といったこともできます。使い方はまだまだあると思います。


 では次に、上の CLane に仮想関数を登録するとどうなるのでしょうか? それは、やはり実際にやって確かめてみましょう。

プログラム3
// MemFP2.cpp
#include <iostream.h>
#include "Lane.h"

class CCommand
{
public:
    virtual void Command() = 0;
};

class C1 : public virtual CCommand
{
public:
    virtual void Command();
};

void C1::Command()
{
    cout << "C1::Command" << endl;
}

class C2 : public virtual CCommand
{
public:
    virtual void Command();
};

void C2::Command()
{
    cout << "C2::Command" << endl;
}

class C3 : public C1, public C2
{
public:
    virtual void Command();
};

void C3::Command()
{
    cout << "C3::Command" << endl;
}

int main()
{
    C1 c1;
    C2 c2;
    C3 c3;

    CLane <CCommand> lane(c1);
    lane.Push(CCommand::Command);
    lane.Do();

    lane.Set(c2);
    lane.Do();

    lane.Set(c3);
    lane.Do();

    return 0;
}
実行結果
C1::Command
C2::Command
C3::Command

 複雑なのもなんなので簡単な例にしましたが、検証のために仮想継承まで使っています。

 CCommand::Command を登録しましたが、実際にはオブジェクトごとに適切な関数が呼ばれています。このように、メンバ関数ポインタに仮想関数を代入しても適切に動作するのです。仮想継承なんかを使っても適切に動作します。


 では、最後にメンバ関数ポインタのサイズについて話したいと思います。

 先ず、MemFP1.cpp でサイズを見てみましょう。

プログラム4
// MemFP1b.cpp
... 略 ...

int main()
{
    ... 略 ...

    cout << sizeof (void (CCalc::*)()) << endl;
    return 0;
}
実行結果例
... 略 ...
4

 次は MemFP2.cpp です。

プログラム4
// MemFP2b.cpp
... 略 ...

int main()
{
    ... 略 ...

    cout << sizeof (void (CCommand::*)()) << endl;
    cout << sizeof (void (C1::*)()) << endl;
    cout << sizeof (void (C2::*)()) << endl;
    cout << sizeof (void (C3::*)()) << endl;
    return 0;
}
実行結果例
... 略 ...
4
12
12
12

 何と、サイズが4だったり12だったりしています!

 このように、メンバ関数ポインタのサイズは int と同じになるとは限らないのです。

 VC++で試したところ、普通は4、多重継承していれば8、仮想継承していれば12となりましたが、これは一般にコンパイラに依存します。VC++の場合、多重/仮想継承しているときには正しい関数を探し出すために必要な情報でも付加されているのでしょう。

 汎用的な処理を行う関数では、引数を int にしておき、int 値とポインタのどちらも渡せるようにすることがあります。そのような場合、普通の関数ポインタならそのまま渡して構わないのですが、メンバ関数ポインタの場合はそのまま渡せないわけです。現にメンバ関数ポインタから int へはキャスト不能になっています。メンバ関数ポインタのアドレスを渡すなど、ちょっと面倒なことをする必要があるわけです。


 では、今回の要点です。


 それでは。


第57章 メンバ関数ポインタ天国

Last update was done on 2001.6.16

この講座の著作権はロベールが保有しています