ゲーム業界エンジニアの独り言

クライアント、サーバ、インフラなどなど興味あることなんでも紹介

ゲームプログラマのためのデザインパターン(シングルトン)

今、「ゲームプログラマのためのC++」を読んでます。 ゲームプログラムで使えるデザインパターンについてまとめてみました。

ゲームプログラマのためのC++

ゲームプログラマのためのC++

デザインパターンとは・・・

デザインパターンはプログラム設計でよく見られる問題の特性と、 このような問題に対して最も一般的に採用されている解決策の両方の 両方をカプセル化した抽象構造です。

自分なりに解釈すると、 オブジェクト指向で設計する際に起こりやすい問題の解決策だったり、 再利用性や可読性を上げるための設計の考え方だと思ってます。

ゲーム開発において特に有効なデザインパターン

  • シングルトン
  • ファザード
  • オブザーバ
  • ビジター

今回はシングルトンを紹介します。

シングルトンとは・・・

その名前から分かるとおり、インスタンスを一つしか持っていないクラスです。 一般にシングルトンの目的は、ある一連の機能を集中管理する場所の提供で、 残りのコードはグローバルにその場所にアクセスできます。

例:ファイルマネージャ

ほとんどのゲームは、ファイルの検索、オープン、クローズ、修正するシステムを持っています。 このようなシステムはファイルマネージャと呼ばれ、通常は一つのゲームに一つしかありません。

シングルトンを使わない場合のファイルマネージャ実装例

class FileManager
{
public:
    FileManager();
    ~FileManager();
    bool FileExists(const char* strName) const;
    File* OpenFile(const char* strName, eFileOpenMode mode);
    bool CloseFile(File* pFile);
protected:
};
FileManager fileManager;
File *pFP = fileManager.OpenFIle("test.txt". eReadOnly);

このコードは問題なく動作しますが、大きなプロジェクトでは多少の問題が起こります。

まず複数のファイルマネージャを作成を防止する機能がついていません。 ファイル管理は通常、特定のクラスやシステムの仕様に左右されないので、 ファイルマネージャが複数に存在する理由はないし、リソースの無駄です。

さらに、マルチスレッドを使用している場合は無分別にファイルアクセスを行うのは危険です。

本当に必要なのは、ファイルアクセスに責任を単独で負うただ1つのマネージャーです。 ここで、「じゃあインスタンスを一つ作るようにするよ!」と思っていても、 他のプログラマが2つめのインスタンスを作らない保証はありません。

アクセスの問題もあります。 ファイルマネージャの唯一のインスタンスはどこにおけばいいでしょうか? グローバル空間に置いておくだけというのは考えにくいですね・・・

そこで、以上の解決策としてシングルトンを使用します。

シングルトンの実装

class Singleton
{
public:
    static Singleton *GetInstance()
    {
        return s_pInstance;
    }

   static void Create();
   static void Destroy();

protected:
    static Singleton *s_pInstance;
    Singleton(): // 隠しコンストラクタ
};
// 唯一のインスタンスをNULLで初期化
Singleton *Singleton::s_pInstance = NULL;

// Create()
static  void Singleton::Create()
{
    if (!s_pInstance)
    {
        s_pInstance = new Singleton;
    }
}

// Destroy()
static  void Singleton::Destroy()
{
    delete s_pInstance;
    s_pInstance  = NULL;
}

コンストラクタを外部公開しないことで、外部からのインスタンス作成を禁止して 複数のインスタンス作成を不可能にします。

インスタンスの取得を static関数 にすることでグローバルアクセスを提供します。

インスタンスへのアクセスをクラス管理しているためカプセル化も行えています。

ここでは明示的に Create() と Destroy() を呼んでいます。 GetInstance() でインスタンスの作成を行う実装もありますが、 明示的に作成と破棄を行うほうが管理しやすいためこうしています。

ファイルマネージャーでの使用例

起動時にファイルマネージャーのインスタンスを作成します。

FileManager::Create();

この機能はこれまでと同じ方法でアクセスします。

FileManager::GetInstance()->OpenFile("test.txt", eReadOnly);

プログラムのシャットダウン時にインスタンスを破棄します。

FileManager::Destroy();

テンプレート化

コードの再利用性も考えてテンプレート化してみます。

template<class T>
class Singleton
{
public:
    static inline T& GetInstance()
    {
        static T instance;
        return instance;
    }

protected:
    Singleton() {} // 外部でのインスタンス作成は禁止
    virtual ~Singleton() {}

private:
    void operator=(const Singleton& obj) {} // 代入演算子禁止
    Singleton(const Singleton &obj) {} // コピーコンストラクタ禁止
};

使用例

class FileManager : public Singleton<FileManager>
{
public:
    friend class Singleton<FileManager>; // Singleton でのインスタンス作成は許可

public:
    bool FileExists(const char* strName) const;
    File* OpenFile(const char* strName, eFileOpenMode mode);
    bool CloseFile(File* pFile);

protected:
    FileManager(); // 外部でのインスタンス作成は禁止
    virtual ~FileManager();
};