Template で Template Method というのは singleton に応用できるのではないかと思って作ってみました。
template<class T>
class Singleton
{
protected:
Singleton() { }
public:
static T* GetInstance()
{
static T t;
return &t;
}
};
class Hoge : public Singleton<Hoge>
{
public:
...
};
ものすごい簡単ですね。
まあ実際は引数に自身のクラス以外を渡すことも出来るので、singleton とは言えない気もしますが、許容範囲内ということにしておきます。
で、これをマルチスレッド対応にする場合、Windows ならクリティカルセクションを使って double checked locking を行う訳ですが、シングルスレッドの場合はクリティカルセクションに入る必要は無いので、何となくもったいないと感じます。
そこで登場するのが ATL のスレッドモデル。
これらはインクリメントとデクリメント、それからクリティカルセクションが、スレッドモデルに適した形で定義されています。
これらのスレッドモデルを使って書くと、
template<class T, class ThreadModel = CComSingleThreadModel>
class Singleton
{
private:
static T* p;
static typename ThreadModel::AutoCriticalSection cs;
protected:
Singleton() { }
public:
static T* GetInstance()
{
if (p == NULL)
{
cs.Lock();
if (p == NULL)
{
try
{
p = new T();
}
catch (...)
{
cs.Unlock();
throw;
}
}
cs.Unlock();
}
return p;
}
};
template<class T, class ThreadModel>
T* Singleton<T, ThreadModel>::p = NULL;
template<class T, class ThreadModel>
typename ThreadModel::AutoCriticalSection Singleton<T, ThreadModel>::cs;
class Hoge : public Singleton<Hoge, CComMultiThreadModel>
{
public:
...
};
こうなります。
シングルスレッドの場合は cs.Lock() の中身はインラインで空き関数になっているので消えて、2回の NULL チェックは……どうなるんだろ?
その部分が最適化されるかどうかは分かりませんが、とりあえずクリティカルセクションに入るという処理は無くなります。
まあ実際のところ、singleton は最初の一回しか初期化されないのでクリティカルセクションに入っても全然構わないわけですが、複数のスレッドモデルで使う可能性があって、なおかつ速度を求めたい場合は使えるかもしれません。
余談:
生成に関する部分を new 以外で生成したい場合は、
template<class T, class ThreadModel = CComSingleThreadModel>
class Singleton
{
private:
static T* p;
static typename ThreadModel::AutoCriticalSection cs;
protected:
Singleton() { }
static T* Create() { return new T(); }
public:
static T* GetInstance()
{
if (p == NULL)
{
cs.Lock();
if (p == NULL)
{
try
{
p = T::Create();
}
catch (...)
{
cs.Unlock();
throw;
}
}
cs.Unlock();
}
return p;
}
};
template<class T, class ThreadModel>
T* Singleton<T, ThreadModel>::p = NULL;
template<class T, class ThreadModel>
typename ThreadModel::AutoCriticalSection Singleton<T, ThreadModel>::cs;
class Hoge : public Singleton<Hoge, CComMultiThreadModel>
{
public:
static Hoge* CreateInstance() { return HogeFactory::Create(); }
...
};
こんな風にすればいいのかなぁと思ってみたり。
テンプレートってすげー。