LoginSignup
8
7

More than 5 years have passed since last update.

CRTP (奇妙に再帰したテンプレートパターン) を使ったシングルトン

Last updated at Posted at 2015-10-01

CRTP (奇妙に再帰したテンプレートパターン) を使ったシングルトンポリシークラスを作って使い回したいと色々試行錯誤した結果、最終的に StackOverflow のこの記事 に行き着いた。

ここでは前述の記事から機能を抜いた劣化版(?)のシングルトンクラスを紹介します。

シングルトンクラスの要件

こんな感じのクラスを作りたいというのが発端です。

  1. インスタンス取得に仮想関数を使わない
  2. 最初に呼ばれるまでメモリを占有しない
  3. プログラム終了時にデストラクタが呼ばれる
  4. 使い回せてコーディングが楽

最終的なコード

singleton.hpp
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

sample1.cpp
#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() が呼ばれますが、ここでテンプレート引数 TFoo が渡されているため Foo& が返ってきます。それで Foo のメンバ関数 bar() が使えています。
ここが CRTP のミソの部分で、「インスタンス取得に仮想関数を使わない」という要件が満たされています。

基本的な使用法これだけなのですが、 Foo にプライベートのコンストラクタやデストラクタを持たせたいときにはもうひと作業必要です。

private コンストラクタ・デストラクタを使用する最低限のコード

継承先のクラスで Singleton をフレンドクラスに登録してあげる必要があります。

class Foo : public Singleton<Foo>
{
  friend class Singleton;

  Foo() { /* 実装 */ }
  ~Foo() { /* 実装 */ }
};

Singleton::getInstance 内で Foo のインスタンスを生成しているため、 Singleton クラスが Foo のコンストラクタ・デストラクタにアクセスできるようにしてあげるためです。

使用例2

private コンストラクタ・デストラクタの使用例です。

sample2.cpp
#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 使用時にパラメータを渡し、引数付きコンストラクタを呼べるようにしてあります。そちらもご参考ください。

8
7
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7