CRTP (奇妙に再帰したテンプレートパターン) を使ったシングルトンポリシークラスを作って使い回したいと色々試行錯誤した結果、最終的に StackOverflow のこの記事 に行き着いた。
ここでは前述の記事から機能を抜いた劣化版(?)のシングルトンクラスを紹介します。
シングルトンクラスの要件
こんな感じのクラスを作りたいというのが発端です。
- インスタンス取得に仮想関数を使わない
- 最初に呼ばれるまでメモリを占有しない
- プログラム終了時にデストラクタが呼ばれる
- 使い回せてコーディングが楽
最終的なコード
template<typename T>
class Singleton
{
public:
//! シングルトンのインスタンスを取得
static T& getInstance()
{
// 最初に呼ばれたときに一度だけ初期化される
static T singleton;
return singleton;
}
protected:
Singleton() = default;
virtual ~Singleton() = default;
// コピー・代入禁止
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
最低限の使い方
最低限のコードはクラス定義に Singleton クラス継承を足しただけのシンプルなものです。
class Foo : public Singleton<Foo>
{
};
継承元である Singleton
クラスのテンプレート引数に自分自身 (Foo) を入れているのが CRTP イディオム特有のところです。
見慣れないと奇妙な感じですが使用例を見てみましょう。
使用例1
#include <iostream>
#include "singleton.hpp"
using namespace std;
class Foo : public Singleton<Foo>
{
public:
void bar() {
cout << "シングルトンだよ\n";
}
};
int main(void)
{
cout << "*** CHECK POINT 1 ***\n";
Foo& foo = Foo::getInstance();
foo.bar();
cout << "*** CHECK POINT 2 ***\n";
}
*** CHECK POINT 1 ***
シングルトンだよ
*** CHECK POINT 2 ***
Foo::getInstance()
を呼ぶと Singleton
クラスの static T& getInstance()
が呼ばれますが、ここでテンプレート引数 T
に Foo
が渡されているため Foo&
が返ってきます。それで Foo
のメンバ関数 bar()
が使えています。
ここが CRTP のミソの部分で、「インスタンス取得に仮想関数を使わない」という要件が満たされています。
基本的な使用法これだけなのですが、 Foo
にプライベートのコンストラクタやデストラクタを持たせたいときにはもうひと作業必要です。
private コンストラクタ・デストラクタを使用する最低限のコード
継承先のクラスで Singleton
をフレンドクラスに登録してあげる必要があります。
class Foo : public Singleton<Foo>
{
friend class Singleton;
Foo() { /* 実装 */ }
~Foo() { /* 実装 */ }
};
Singleton::getInstance
内で Foo
のインスタンスを生成しているため、 Singleton
クラスが Foo
のコンストラクタ・デストラクタにアクセスできるようにしてあげるためです。
使用例2
private コンストラクタ・デストラクタの使用例です。
#include <iostream>
#include "singleton.hpp"
using namespace std;
class Foo : public Singleton<Foo>
{
public:
void bar() {
cout << "シングルトンだよ\n";
}
private:
friend class Singleton;
Foo() {
cout << "生まれたよ\n";
}
~Foo() {
cout << "死んだよ\n";
}
};
int main(void)
{
cout << "*** CHECK POINT 1 ***\n";
Foo& foo = Foo::getInstance();
foo.bar();
cout << "*** CHECK POINT 2 ***\n";
}
*** CHECK POINT 1 ***
生まれたよ
シングルトンだよ
*** CHECK POINT 2 ***
死んだよ
出力を見ると CHECK POINT 1 より後にコンストラクタが呼ばれているのが分かります。【シングルトンクラスの要件】に挙げた、「2. 最初に呼ばれるまでメモリを占有しない」が満たされています。
「3. プログラム終了時にデストラクタが呼ばれる」のも確認できます。
なかなか便利ではないでしょうか。
補足
冒頭に挙げた StackOverflow の記事 では、この記事で紹介した機能に加え、最初の getInstance
使用時にパラメータを渡し、引数付きコンストラクタを呼べるようにしてあります。そちらもご参考ください。