読者です 読者をやめる 読者になる 読者になる

これでほぼ完璧! C++シングルトン基底クラスの決定版!

 シングルトンパターンは、デザインパターンがさっぱりわからないプログラマーでも聞いた事があるかもしれないくらい有名であるにもかかわらず、真面目に実装しようと思うと様々な問題が発生してしまいます。
 具体的には、

  1. static変数の初期化順序が未定義であるために、予期せぬ値を参照してしまう。
  2. 複数のスレッドから同時に初期化処理を行なった際、適切な同期を行なえない。
  3. シングルトンクラスのコンストラクタでシングルトンを参照した際、無限ループが発生する。

 このうち最後だけはどうにもなりませんので何とか気をつけることとして、他の2つはできれば「シングルトン基底クラス」的なクラスを継承すれば自動的にどうにかしてくれる……というのが理想的です。

 なので作ってみました。

#include <memory>

template <class T>
class Singleton
{
public :
  static T& singleton()
  {
    static typename T::singleton_pointer_type s_singleton(T::createInstance()) ;

    return getReference(s_singleton) ;
  }

private :
  typedef std::unique_ptr<T> singleton_pointer_type ;

  inline static T *createInstance() { return new T() ; }

  inline static T &getReference(const singleton_pointer_type &ptr) { return *ptr ; }

protected :
  Singleton() {}

private :
  Singleton(const Singleton &) = delete;
  Singleton& operator=(const Singleton &) = delete;
  Singleton(Singleton &&) = delete;
  Singleton& operator=(Singleton &&) = delete;
};

 ミソは、T::singleton_pointer_type こと std::unique_ptr<T> を静的ローカル変数にしている部分。
 静的ローカル変数は、関数が呼び出されるまで初期化が遅延されるため、変数の初期化順序に依存しないプログラムが書ける上、C++11では複数スレッドから同時に呼ばれても一度だけ初期化され、初期化が完了するまで全ての呼び出しがロックされる保証が存在します*1
 また、静的ローカル変数を T ではなく std::unique_ptr<T> とすることで、プログラム終了時にデストラクタが呼ばれる、というメリットをそのままに、Singleton を継承した実際のクラスでインスタンスの構築方法をカスタマイズできるようにしてあります。
 最後にコピー&ムーブコンストラクタと代入演算子を削除してやれば完璧ですね。

 使用法はこんな感じ。

class Hoge : public Singleton<Hoge>
{
private :
  friend class Singleton<Hoge>;
  Hoge() { std::cout << "Hoge()" << std::endl; }

  // デフォルトコンストラクタ以外でインスタンスを構築したい場合、createInstance()を上書きする
  /*
  Hoge(int) { std::cout << "Hoge(int)" << std::endl; }
  static Hoge *createInstance()
  {
    return new Hoge(0);
  }
  */
};

 なお、createInstance() を上書きしたいケースは、次のような場合になると思います。

  • シングルトンにするのはインターフェースであり、実際のインスタンスは非公開の具象クラスである。
  • シングルトンの実際のインスタンスは、設定ファイルの内容等に応じて切り替える。
  • シングルトンのインスタンスを、プレースメントnewを用いて構築したい。
  • そもそも std::unique_ptr<T> を使いたくない場合。

 最後の場合、singleton_pointer_type やら getReference() やらも再定義してやった上で createInstance() の戻り値の型も適切に設定してやることで、いろいろ怪しげなカスタマイズができてしまいます。
 本当だったら、この辺の処理は template <class T> SingletonAdapter みたいなクラスを作ってやるのがよりC++らしいのかもしれませんが、そのためだけに friend class SingletonAdapter<T>; するのも何だかなーと思ったので中止。

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門