閑古鳥

オールドプログラマの日記。プログラミングとか病気(透析)の話とか。

Template Metaprogramming入門(2) - SingletonHolder

今回は少し実践的なものを。SingletonHolder を使用してみます。テンプレートを使用して、Singletonパターンの実装を簡単にしようというものです。元ネタは Modern C++ Design。

Singletonパターンについても書くと長くなってしまうのでその辺は端折りますが(自分で調べてね)、大雑把に言えば実行時に一つしかインスタンスを生成できないクラスをSingletonと言います。C++では、このパターンを実現するためにコンストラクタやコピーコンストラクタ、また代入演算子(operator=)をプライベートにすることで実現できます。唯一のインスタンスは、クラス内にある静的メソッドにより生成し、取得します。コードにするとこんな感じ(長くなるので以下詳細ページでのみ閲覧できるようにしてあります)。

class Singleton
{
public:
    //! Singletonクラスのインスタンスを取得するためのアクセサ
    static Singleton& Instance()
    {
        static Singleton theInstance; // 最初に一回だけ生成される
        return theInstance;
    }
private:
    Singleton() {} // privateなので外部から生成することはできない
    Singleton(const Singleton&);            // 実装はしない
    Singleton& operator=(const Singleton&); // 実装はしない
};

int main()
{
    Singleton& hoge = Singleton::Instance();
    /* hogeに対する操作 */

    Singleton hoe; // コンパイルエラー: コンストラクタを参照できないので生成不可
    return 0;
}

ということで、Singleton::Instance() を経由しないとインスタンスを取得できず、また Instance メソッドから返されるインスタンスは必ず同じものだという保証がされる、と。状態を一つしか持ちたくない時等に使います。GoFのパターンの中でも多分最も有名で最も簡単なものなだけあり、単純明快です。

ここからが本題。このSingletonパターンを適用したクラスを作りたくなった時、上記のような形でコンストラクタなどをプライベートにしたり Instance メソッドを作ったりなどの作業が毎回入るのはそれなりに手間だと思いますが、この手間を無くそうというのが、最初にも書きましたが今回の SingletonHolder というテンプレートクラスになります。SingletonHolder を使えば、例えば Hage というクラスに Singleton パターンを適用したくなった時、次のように定義するだけで事足ります。

// class Hage の実装は省略。
typedef SingletonHolder<Hage> HageHolder;

SingletonHolder のテンプレート引数に Singleton にしたいクラスを渡してやるだけで、 Singleton パターンに必要な実装(コンストラクタをプライベートにする等)を変わりにやってくれます。で、Singletonとして生成される型を typedef で別名(ここでは HageHolder)をつけ、Hageクラスを使用するほうはこのクラスを使用してもらうという形になります。SingletonHolder は次のような実装になります。

template<class T>
class SingletonHolder
{
public:
    static T& Instance()
    {
        if(m_pInstance)
        {
            MakeInstance();
        }
        return *m_pInstance;
    }
    
private:
    static T* m_pInstance;
    static void MakeInstance()
    {
        m_pInstance = new T();
        ::atexit(DestroyInstance);
    }
    static void DestroyInstance()
    {
        delete m_pInstance();
    }

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

滅茶苦茶簡略化していますが、イメージとしてこんなものでしょうか。注目すべき点はSingletonHolder クラスの Instance メソッドで得られるインスタンスがテンプレート引数のものになっているところ。つまりテンプレート引数に Hage を渡せば間接的に HageHolder という Singleton パターンを適用したクラスが出来上がるわけです。しかし、書いていて気づきましたが、これだと Hage の方に直接アクセスすればいくらでもインスタンスが生成できてしまいますね。Hage のコンストラクタをプライベートにしておく作業は、プログラマの方で毎度やっておかなければならないようです。ついでにコンストラクタをプライベートにすると new できないので、 SingletonHolder を friend にする作業も必要ですね。単純に Singleton を簡単に実装したい時にはなんだかいまいちです。

ちなみに、参考にした Modern C++ Design で紹介されているコードは、もっとちゃんと実装されています。また、 PhoenixSingleton(一度消えても復活するSingleton) だとかユーザ側でインスタンスを破棄する(デストラクタが呼ばれる)順番を変更できる LifeTimeTracker というのを組み込んだ Singleton だとかが用意されていますが、その辺はややこしいので以下略。詳細を知りたい方は書籍を購入するか、Lokiというライブラリ(書籍で公開されているもの)が SourceForge で公開されていますので、そちらを参照してください。