ぼやきごと/2012-03-19/C++型消去:派生クラス型を〜 の別種 の変更点


#blog2navi()
*C++型消去:派生クラス型を〜 の別種 [#aab17bd2]
LEFT:Prev: [[[C++型消去:派生クラス型を引数に取る関数オブジェクトを基本クラスに登録して呼び出す>ぼやきごと/2012-03-18/C++型消去:派生クラス型を引数に取る関数オブジェクトを基本クラスに登録して呼び出す]]]

[[前回の記事>ぼやきごと/2012-03-18/C++型消去:派生クラス型を引数に取る関数オブジェクトを基本クラスに登録して呼び出す]]の別種です。~
@code{Character}; クラス側ではなく、 @code{Controller}; クラス側に関数登録&実行の仕組みを実装しています。

#code(c){{
#include <functional>
#include <string>
#include <memory>
#include <iostream>

namespace
{
    ///--------------------
    /// ベースクラス
    class Character
    {
    protected:
        /// コンストラクタ。
        Character() { }

    public:
        /// デストラクタ。
        virtual ~Character() { }

    private:
        // コピー禁止
        Character(const Character&);
        void operator=(const Character&);
    };

    ///--------------------
    /// 派生クラス1
    class Suika : public Character { };

    ///--------------------
    /// 派生クラス2
    class Meiling : public Character { };

    ///--------------------
    /// 派生クラス3
    class Tenshi : public Character { };

    ///--------------------
    /// 操作クラス
    class Controller
    {
    private:
        /// メンバ関数オブジェクトを実行する。
        template<class TMemFunc, class TChara>
        void invokeCall(void* memFunc, void* chara)
        {
            TMemFunc* f = static_cast<TMemFunc*>(memFunc);
            (*f)(this, static_cast<TChara*>(chara));
        }

        /// メンバ関数オブジェクトを破棄する。
        template<class TMemFunc>
        void invokeDispose(void* memFunc)
        {
            delete static_cast<TMemFunc*>(memFunc);
        }

        /// Character クラスの派生クラス型を引数に取るメンバ関数を登録する。
        template<class TChara>
        void setFunc(void (Controller::*func)(TChara*))
        {
            resetFunc();

            typedef std::mem_fun1_t<void, Controller, TChara*> MemFuncType;
            _memFunc = new MemFuncType(func);
            _invokeCall = &Controller::invokeCall<MemFuncType, TChara>;
            _invokeDispose = &Controller::invokeDispose<MemFuncType>;
        }

        /// setFunc で登録したメンバ関数を実行する。
        void doFunc()
        {
            if (_memFunc != 0)
            {
                (this->*_invokeCall)(_memFunc, _chara.get());
            }
        }

        /// 登録されているメンバ関数を破棄する。
        void resetFunc()
        {
            if (_memFunc != 0)
            {
                (this->*_invokeDispose)(_memFunc);
                _memFunc = 0;
            }
        }

        /// メンバ関数オブジェクト
        void* _memFunc;

        /// メンバ関数オブジェクト実行関数
        void (Controller::*_invokeCall)(void*, void*);

        /// メンバ関数オブジェクト破棄関数
        void (Controller::*_invokeDispose)(void*);

    public:
        /// コンストラクタ。
        Controller()
            : _chara(), _memFunc(0), _invokeCall(0), _invokeDispose(0)
        {
        }

        /// デストラクタ。
        virtual ~Controller()
        {
            resetFunc();
        }

        /// 特定の派生クラスで初期化する。
        template<class TChara>
        void initialize()
        {
            // メンバ関数を登録する
            setFunc(&Controller::procCallback<TChara>);

            // 基本クラスにアップキャストして保持
            _chara.reset(new TChara());
        }

        /// 初期化した内容で処理を実行する。
        void execute()
        {
            // initialize で登録したメンバ関数を呼び出す
            doFunc();
        }

    private:
        /// 関数オブジェクト用の基本定義。
        template<class TChara>
        void procCallback(TChara*)
        {
            std::cout << "(Basic function)" << std::endl;
        }

        /// Suika 用の特殊化定義。
        template<>
        void procCallback(Suika*)
        {
            std::cout << "Ibuki Suika" << std::endl;
        }

        /// Meiling 用の特殊化定義。
        template<>
        void procCallback(Meiling*)
        {
            std::cout << "Chugo... Hong-Meiling!!" << std::endl;
        }

    private:
        std::auto_ptr<Character> _chara;
    };
}

///--------------------
/// メイン関数。
int main(int, char**)
{
    Controller ctrl;

    // Suika で初期化して実行
    ctrl.initialize<Suika>();
    ctrl.execute();

    // Meiling で初期化して実行
    ctrl.initialize<Meiling>();
    ctrl.execute();

    // Tenshi で初期化して実行
    ctrl.initialize<Tenshi>();
    ctrl.execute();

    return 0;
}
}}

前回との違いは次の通り。

- 登録する関数を @code{Controller}; クラスのメンバ関数に限定している。
- 型情報を保持する関数(@code{invokeCall};)にもメンバ関数を用いることで、 @code{this}; の受け渡しを不要にしている。
- メンバ関数 @code{setFunc}; を外部から直接呼び出せないようになり、唯一呼び出しているメンバ関数 @code{initialize<TChara>}; では @code{_chara}; へのインスタンス設定も行っているため、追加の型チェックを行うことなく @code{TChara}; 型が @code{Character}; 型の派生クラスであることが保証されている。

操作対象のクラスに依存せずにコールバック的な仕組みを実装したいならこちらの方法が良いでしょう。

RIGHT:Category: &#x5b;[[C++>ぼやきごと/カテゴリ/C++]]&#x5d;&#x5b;[[プログラミング>ぼやきごと/カテゴリ/プログラミング]]&#x5d; - 2012-03-19 00:17:49
----
RIGHT:&blog2trackback();
#comment(above)
#blog2navi()