シングルトン

ソースはChatGPT4o

オブジェクト指向における再利用のためのデザインパターン 単行本 – 1999/10/1Erich Gamma (著)

継承に関してはGAME PROGRAMMING GEMS(P13)に記述があるが、理解はできてない。




シングルトンパターンは、あるクラスに対してインスタンスが一つしか存在しないことを保証するデザインパターンです。
Manager系クラスなど、呼び出す頻度が高く、かつ情報を唯一にまとめておいて欲しいクラスに適用されます。こうしたクラスはグローバルに用意するほど不用意でなく、あらかじめメンバに用意しておく手間もなく、つど関数の引数としてやりとりする必要もありません。ただ必要な時にgetInstance()すれば取り出せます。

GoF p139

class Singleton
{
  public:
    static Singleton *Instance();
  protected:
    Singleton();
  private:
    static Singleton *_instance;
};

Singleton *Singleton::_instance = 0;

Singleton *Singleton::Instance()
{
  if(_instance==0)
  {
    _instance = new Singleton;
  }
  return _instance;
}
  1. クラス Singleton のインスタンスを取得したい場合、Singleton::Instance() メソッドを呼び出します。

  2. 最初の呼び出し時には _instance が nullptr なので、新しいインスタンスが作成され _instance に格納されます。

  3. 以降の呼び出しでは、既存のインスタンスが返されます。

問題点

上記のシングルトンパターンの実装にはいくつかの問題点があります。以下に主な問題点を挙げます。

問題点0. 静的メンバ

初期化順序の問題
静的メンバ変数は、プログラムの開始時に初期化されますが、その初期化順序は不明確です。複数の静的オブジェクトが依存関係を持つ場合、予期しない順序で初期化が行われることがあり、これがバグの原因となる可能性があります。

スレッドセーフ性
C++03までの仕様では、静的メンバ変数の初期化はスレッドセーフではありません。マルチスレッド環境で複数のスレッドが同時に初期化を試みると、複数のインスタンスが生成される可能性があります。

メモリリークのリスク
静的メンバ変数としてポインタを用意し、そのポインタを用いてダイナミックにメモリを確保する場合、プログラム終了時にそのメモリが解放されないまま残ると、メモリリークの原因となります。これはシングルトンパターンの実装で特に注意が必要です。

グローバル変数のように扱われる
静的メンバ変数はグローバル変数と同じくプログラム全体でアクセス可能であり、そのスコープを制御することが難しくなります。これにより、意図しないアクセスや変更が発生しやすくなります。

再利用の難しさ
クラス外で静的メンバ変数を定義することで、そのクラスの再利用が難しくなる場合があります。特にライブラリとして利用する場合、静的メンバ変数の管理が難しくなる可能性があります。

問題点1. マルチスレッド環境での安全性

この実装はマルチスレッド環境でスレッドセーフではありません。複数のスレッドが同時に `Instance()` メソッドを呼び出すと、複数のインスタンスが作成される可能性があります。

解決策

マルチスレッド環境での問題を解決するためには、ロック機構を使用してインスタンス生成を同期化する必要があります。

#include <mutex>

class Singleton
{
  public:
    static Singleton *Instance();
  protected:
    Singleton();
  private:
    static Singleton *_instance;
    static std::mutex _mutex;
};

Singleton *Singleton::_instance = 0;
std::mutex Singleton::_mutex;

Singleton *Singleton::Instance()
{
  std::lock_guard<std::mutex> lock(_mutex);
  if(_instance == 0)
  {
    _instance = new Singleton();
  }
  return _instance;
}

問題点2. インスタンスの破棄

この実装では、生成されたシングルトンインスタンスを適切に破棄する方法が提供されていません。プログラム終了時にメモリリークが発生する可能性があります。

解決策

インスタンスを適切に破棄するために、デストラクタを追加し、静的メソッドでインスタンスを削除する方法を提供します。

class Singleton
{
  public:
    static Singleton *Instance();
    static void DestroyInstance();
  protected:
    Singleton();
    ~Singleton();
  private:
    static Singleton *_instance;
};

Singleton *Singleton::_instance = 0;

Singleton *Singleton::Instance()
{
  if(_instance == 0)
  {
    _instance = new Singleton();
  }
  return _instance;
}

void Singleton::DestroyInstance()
{
  delete _instance;
  _instance = 0;
}

Singleton::~Singleton()
{
  // デストラクタの実装
}

問題点3. インスタンス生成のタイミング

この実装ではインスタンスが初めて `Instance()` メソッドが呼ばれたときに生成されます。これにより、必要になるまでインスタンスが生成されないという利点がありますが、場合によってはプログラムの初期化時にインスタンスを生成したい場合もあります。

解決策

C++11以降では、静的ローカル変数を使用してスレッドセーフなシングルトンを実装することができます。

class Singleton
{
  public:
    static Singleton& Instance()
    {
        static Singleton instance;
        return instance;
    }
  protected:
    Singleton() {}
    ~Singleton() {}
  private:
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

この方法では、初回アクセス時にインスタンスが作成され、C++11の標準によりスレッドセーフが保証されます。また、プログラム終了時に静的ローカル変数が自動的に破棄されるため、明示的なデストラクタ呼び出しも不要です。

以上のように、マルチスレッド環境への対応、インスタンスの破棄、およびインスタンス生成のタイミングに関する改善を行うことで、より堅牢なシングルトン実装を実現できます。

継承

シングルトンパターンを実装したクラスを継承する場合、いくつかの追加の考慮が必要です。特に、シングルトンインスタンスを管理する方法と、派生クラスごとに異なるシングルトンインスタンスを持たせる方法を考慮する必要があります。以下はその方法の一例です。

テンプレートを用いたシングルトンベースクラス

テンプレートを使用して、派生クラスごとに独自のシングルトンインスタンスを持つことができるようにします。

template <typename T>
class Singleton
{
public:
    static T& Instance()
    {
        static T instance;
        return instance;
    }
protected:
    Singleton() {}
    ~Singleton() {}
private:
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

class DerivedSingleton : public Singleton<DerivedSingleton>
{
    friend class Singleton<DerivedSingleton>; // Singletonクラスにアクセスを許可
public:
    void doSomething() 
    {
        // 派生クラスのメソッド
    }
protected:
    DerivedSingleton() {}
    ~DerivedSingleton() {}
};

ポイント

  1. テンプレートクラス `Singleton`:

    • テンプレート引数 `T` を受け取る `Singleton` クラスを定義します。

    • `Instance()` メソッドは静的ローカル変数を用いてスレッドセーフなシングルトンインスタンスを提供します。

    • コピーコンストラクタと代入演算子を削除しています。

  2. 派生クラス `DerivedSingleton`:

    • `Singleton<DerivedSingleton>` を継承します。

    • `Singleton` クラスが `DerivedSingleton` のプライベートメンバにアクセスできるように、フレンドクラスとして宣言します。

    • コンストラクタとデストラクタはプロテクテッドにして、外部からの直接のインスタンス生成を防ぎます。

使用例

int main()
{
    DerivedSingleton& instance = DerivedSingleton::Instance();
    instance.doSomething();
    
    return 0;
}

この方法により、`Singleton` クラスを基底クラスとして利用しながら、派生クラスごとに独自のシングルトンインスタンスを持つことができます。これにより、コードの再利用性と保守性が向上します。

各種言語

各プログラミング言語でのシングルトンパターンの実装例を以下に示します。

C

C言語では、シングルトンを実装するために関数と静的変数を使用します。

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    // シングルトンのメンバ変数
    int value;
} Singleton;

Singleton* getInstance() {
    static Singleton* instance = NULL;
    if (instance == NULL) {
        instance = (Singleton*)malloc(sizeof(Singleton));
        instance->value = 0; // 初期化
    }
    return instance;
}

int main() {
    Singleton* singleton = getInstance();
    singleton->value = 42;
    printf("Singleton value: %d\n", singleton->value);
    return 0;
}

C#

C#では、シングルトンを静的プロパティとロックを使って実装します。

using System;

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();
    
    public int Value { get; set; }

    private Singleton()
    {
        Value = 0; // 初期化
    }

    public static Singleton Instance
    {
        get
        {
            return instance;
        }
    }
}

class Program
{
    static void Main()
    {
        Singleton singleton = Singleton.Instance;
        singleton.Value = 42;
        Console.WriteLine("Singleton value: " + singleton.Value);
    }
}

Java

Javaでは、シングルトンを静的メソッドと静的変数を使って実装します。

public class Singleton {
    private static Singleton instance;

    public int value;

    private Singleton() {
        value = 0; // 初期化
    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

public class Main {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        singleton.value = 42;
        System.out.println("Singleton value: " + singleton.value);
    }
}

Python

Pythonでは、シングルトンをクラスメソッドとクラス変数を使って実装します。

class Singleton:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(Singleton, cls).__new__(cls)
            cls._instance.value = 0  # 初期化
        return cls._instance

singleton = Singleton()
singleton.value = 42
print(f"Singleton value: {singleton.value}")

JavaScript

JavaScriptでは、シングルトンをモジュールパターンを使って実装します。

const Singleton = (function() {
    let instance;

    function createInstance() {
        const object = new Object();
        object.value = 0; // 初期化
        return object;
    }

    return {
        getInstance: function() {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();

const singleton = Singleton.getInstance();
singleton.value = 42;
console.log("Singleton value: " + singleton.value);

これらの例は、各言語の標準的な方法でシングルトンパターンを実装しています。各言語の特性に応じた方法を使用していますので、言語ごとに最適な方法を学ぶのに役立ちます。





この記事が気に入ったらサポートをしてみませんか?