シングルトンなメソッドを静的な関数にしたい

こんにちは、マツノブです。

今回もプログラムの話をしていきましょう。

皆さんはシングルトンクラスのメソッドをコールバックとして呼ぶためにヘルパ関数を作ったこともあると思います。

あれってすごく面倒ですよね。

class SingletonClass {
public:
    static SingletonClass* GetInstance(void);
    
    void func(int);
};

void SingletonHelper(int int_value){
    SingletonClass::GetInstance()->func(int_value);
}

↑のようなコードを書いた後に SingletonClass::func の引数をintからfloatに変更したのに SingletonHelper 関数の引数がintのままになっていて予期せぬ不具合に遭遇するという苦い記憶があるプログラマも多いでしょう。

今回はそんな面倒なヘルパ関数を自動生成する方法を考えていこうと思います。

// シングルトン関数を静的関数にするマクロ
#define SINGLETON_TO_STATIC_METHOD(classname, methodname) \
    decltype(SingletonMethod::GetFunctionType(&classname::methodname))::call<&classname::methodname>

// シングルトン関数を静的関数にするマクロ
#define SINGLETON_TO_STATIC_METHOD_NAMING(funcname, classname, methodname) \
    auto funcname = SINGLETON_TO_STATIC_METHOD(classname, methodname)

class SingletonMethod {
    template<typename T, typename Ret, typename...Args>
    class _SingletonMethod {
    public:
        template<Ret(T::*func)(Args...)>
        inline static Ret call(Args... args) {
            return (T::GetInstance()->*func)(args...);
        }
    };

    template<typename T, typename Ret, typename...Args>
    class _SingletonMethod_const {
    public:
        template<Ret(T::*func)(Args...)const>
        inline static Ret call(Args... args) {
            return (T::GetInstance()->*func)(args...);
        }
    };
    
public:
    template<typename T, typename Ret, typename...Args>
    inline static _SingletonMethod<T, Ret, Args...> GetFunctionType(Ret(T::*)(Args...));

    template<typename T, typename Ret, typename...Args>
    inline static _SingletonMethod_const<T, Ret, Args...> GetFunctionType(Ret(T::*)(Args...)const);
};

↑に記述したマクロを使用して前述のSingletonHelperを実装すると

SINGLETON_TO_STATIC_METHOD_NAMING(SingletonHelper, SingletonClass, func);

となります。

それでは順を追って見ていきましょう。まず最初に

SINGLETON_TO_STATIC_METHOD_NAMING(SingletonHelper, SingletonClass, func);

が展開されて

auto SingletonHelper = SINGLETON_TO_STATIC_METHOD(SingletonClass, func)

となって

auto SingletonHelper = decltype(SingletonMethod::GetFunctionType(&SingletonClass::func))::call<&SingletonClass::func>

になります。

decltype(SingletonMethod::GetFunctionType(&SingletonClass::func))

は decltype によって SingletonMethod::GetFunctionType(&SingletonClass::func) の戻り値である

_SingletonMethod<SingletonClass, void, int> と解釈されます。

最後に auto SingletonHelper が右辺値 _SingletonMethod<SingletonClass, void, int>::call<&SingletonClass::func>  によって型推論により関数ポインタに置き換わります。

今回の説明では関数ポインタに代入していますが

SetCallback(SINGLETON_TO_STATIC_METHOD(SingletonClass, func));

のようにコールバック関数を設定する関数に直接代入すればヘルパ関数の名前を考える必要もなくなります。

_SingletonMethod と _SingletonMethod_const を用意する必要がないようにも見えますがVisualStudio環境下ではconstメソッドと非constメソッドでのテンプレート引数のオーバーロードを行うとdecltypeの展開に失敗する場合があったので泣く泣く2つに分けました。

テンプレートとマクロで長々と回りくどいコードを書きましたがこれ一つでヘルパ関数の実装や保守から開放されると思うとちょっと嬉しい気持ちになりませんか?

それでは皆さん良いプログラミングライフを

\ 最新情報をチェック /