C++ の Singleton パターンを Template 化

phiary に引っ越しました. 毎日プログラミングやWebに関する情報を発信しています! RSS 登録してたまに覗いたり, tweet やハテブして拡散してもらえると幸いです.

Pocket

今回は, デザインパターンの中で最もポピュラーな Singleton パターンについて書こうと思います. ゲーム全体を統括するクラスや, リソースを管理するクラスなんかによく使われたりする Singleton パターンですが, staticな変数が必要になるため, どうしても実装に手間がかかります. (static変数を用意したり, getInstanceといったstaticなメソッドが必要になってくるため)

そこで, 目をつけたのがテンプレートクラス. テンプレートクラス内にあるstaticなメンバ変数は, 指定されたテンプレート引数に応じて個別に作成されます. この特性を使えば, static なメンバを型だけ変えて作ることができるのだ!!

実際に作ったクラスがこちら. 学生時代に作っていたライブラリの一部を引っ張ってきただけなので, 色々とツッコミどころがありますがそのへんはご愛嬌ということで.

DATA

実際に使用しているサンプルはこちらからダウンロードできます. SingletonHolderってクラスです.

※Visual C++ 2010のソリューションを使用しています.

IMPLEMENTATION

SingletonHolderの実装部です.

#pragma once

namespace tmlib {

    /**
     *  @brief  シングルトンホルダー
     */
    template <class _Ty>
    class SingletonHolder
    {
    public :
        typedef _Ty InstanceType;

    public :
        /// 生成
        static void create()
        {
            if (_instance == NULL) {
                _instance = new InstanceType();
            }
        }
        /// 破棄
        static void destroy()
        {
            if (_instance != NULL) {
                delete _instance;
                _instance = NULL;
            }
        }

#if 1
        /// インスタンスゲット
        static InstanceType& getInstance()
        {
            return *_instance;
        }
#else
        /// インスタンスゲット
        static InstanceType* getInstance()
        {
            return _instance;
        }
#endif
        /// インスタンス参照ゲット
        static InstanceType& getInstanceRef()
        {
            return *_instance;
        }

        /// インスタンスポインタゲット
        static InstanceType* getInstancePtr()
        {
            return _instance;
        }

        /// 生成済み??
        static bool isCreate()  { return _instance != NULL; };
        /// 破棄済み??
        static bool isDestroy() { return _instance == NULL; };

    private :
        static InstanceType* _instance; //!< インスタンス

    private :
        // 禁止
        SingletonHolder() {}
        virtual ~SingletonHolder() {}

        SingletonHolder(const SingletonHolder&);
        SingletonHolder& operator=(const SingletonHolder&);
    };

    template <class _Ty>
    typename SingletonHolder<_Ty>::InstanceType* SingletonHolder<_Ty>::_instance = NULL;

}
    

EXAMPLE

SingletonHolderの使用例です.


#include <iostream>
#include "singleton-holder.hpp"

// サンプルクラス
class Hoge
{
    // シングルトンのテンプレート引数に自身を指定したものだけに生成をゆるす
    friend tmlib::SingletonHolder<Hoge>;
public :

    // 更新
    void update() { ++_num; }
    // 描画
    void draw() { std::cout << _num << std::endl; }

private :
    int _num;

private :
    Hoge() : _num(0) {}
};

// SingletonHolderを使う際のお決まりtypedef
typedef tmlib::SingletonHolder<Hoge>    HogeHolder;
// 海外のコードでよくみかけるdefineやexternで使うthe...形式(ちょっとオシャレ?)
#define theHoge HogeHolder::getInstance()

void run()
{
    for (int i=0; i<16; ++i) {
        // シングルトンなHogeにアクセス
        theHoge.update();
        theHoge.draw();
    }
}

int main()
{
    // シングルトンなHogeクラスを生成
    HogeHolder::create();

    theHoge.update();   // 更新
    theHoge.draw();     // 描画

    // 実行
    run();

    theHoge.update();   // 更新
    theHoge.draw();     // 描画

    // シングルトンなHogeクラスを破棄
    HogeHolder::destroy();

    return 0;
}

    

COMMENT

ちょっとだけ解説します. Singletonには色々と種類があります. 生成方法や破棄の方法, 取得方法それぞれ実装する人によって千差万別です. そこで私が上記のような実装にした理由をちょこっと書こうと思います.

まずは生成方法についてですが, 主流なのが生成, 生成チェック, インスタンス取得がセットになってる

    ClassInstance* getInstance() {
        if (_instance == NULL) { _instance = new ClassInstance(); }
        return _instance;
    }
    

みたいなコードですが, 私の場合 C は C らしくと生成と破棄はきちんとやろう!!って考えかたなので, あえてcreateとdestroy関数を用意しています.

取得については, ポインタ取得が主流ですが, そうしちゃうと間違ってdeleteしちゃう可能性があるので 参照渡し(getInstance)を採用しています.とはいってもプリプロセッサで切り替えれるようにしてありますし, それぞれ専用の関数(getInstanceRef, getInstancePtr)を用意してるのでご自由にって感じですけどね.

他にもフェニックスという削除タイミングを管理するテクニックやマルチスレッドに対応させた Singleton なんてのもありますが, この辺に触れちゃうと間違ったことを書いちゃいそうなんで端折ります.

詳しく知りたい方はModern C++ Designを参考にしてみてください.まるまる1章使って詳しく説明してくれています.超名著です!!

普段はJavaScriptについてばかり書いていますが本職のゲームプログラミングではC/C++がメインなので、 ちょこちょここういったエントリーも書いていこうと思います.

TRACK BACK URL

POST COMMENT

メールアドレスが公開されることはありません。