シングルトンクラステンプレート

ゲームエンジンを作る作業は黙々とそしてとてもゆっくりと進んでいるわけですが,とりあえず何を作るにも根本的なベースシステムが必要となるので,C++の簡単なSingletonパターンを実装しました.

template <class T>
class Singleton {
public:
  virtual void init() = 0;
  virtual void exec() = 0;
  virtual void term() = 0;

  static inline T& get() {
    static T ins;
    return ins;
  }

protected:
  Singleton() {}
  virtual ~Singleton() {}

private:
  Singleton(const Singleton& rhs) {}
  void operator=(const Singleton& rhs) {}
};

class GameSgt : public Singleton<GameSgt> {
public:
  void init();
  void exec();
  void term();

private:
  friend class Singleton<GameSgt>;
  GameSgt() {}
};


対した事はしていません.誰でも思いつく程度の事をしています.キモとなるシングルトンの部分はベースとなるSingletonクラスに機能があり,それを派生側も扱えるようにしています.しかしインスタンスを作るのはあくまでも基底側なので,CRTPというテクニックを使ってテンプレート引数に自分自身を代入しています.本来,CRTPは仮想関数を静的に呼出したりするために用いるんですが,思いっきり仮想関数を普通に定義していますね.今回そこはこだわっていないので特に問題ないという事で.


基本的な使い方は派生クラスでget()を呼び出すだけです.あとは必要に応じて派生クラスにメンバを追加していけばOK.get()が呼び出された時点で自動でlocal static変数がインスタンス化を行い,その後はget()とするだけでインスタンスを取得出来ます.


派生クラス側で大事なのがprivateにコンストラクタを定義しておく事と,friend classとしてSingleton;の宣言が必要となります.ここがちょっとよいとは言えないのですが,どうやら自分自身にアクセスするためにfriend classである必要があるようです.そこさえ守っていれば実体を2つ作らない安全なSingletonとして使用出来るはず.

int main() {
  GameSgt::get().init();
  GameSgt::get().exec();
  GameSgt::get().term();
}


使う時はこんな感じに.init()やterm()といった関数はあくまでも呼び出し順を制御するためのものなので必須なわけではありません.初期化や終了のタイミングが制御出来ればあのシステムがまだ初期化されていないからとかこっちがまだ終わっていないからとかそういう問題が起きないので,このやり方が一番シンプルでトラブルも少ないかと.