C++のデザインパターン - シングルトン

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
ナビゲーションに移動 検索に移動

概要

シングルトンパターンは、クラスのインスタンスが1つだけ存在することを保証するデザインパターンである。

シングルトンパターンの特徴を以下に示す。

  • プライベートコンストラクタによる外部からのインスタンス化を防止する。
  • 静的メソッドによる唯一のインスタンスへのアクセス提供する。
  • マルチスレッド環境での同期処理の必要性


シングルトンパターンの使用場面を以下に示す。

  • 設定マネージャー
  • データベース接続管理
  • ロギングシステム
  • ファイルシステム


しかし、グローバル状態を作り出すため単体テストの難しさやコードの結合度が高くなる等の課題もある。
そのため、使用は慎重に検討する必要がある。

実装方法には、ポインタベース、参照ベース、C++11以降では静的局所変数を利用した方法等がある。

マルチスレッド対応が必要な場合は、double-checked lockingパターンや静的局所変数の初期化の特性を利用した実装 (C++11以降) も可能である。


ポインタベースのシングルトンクラス

以下の例では、コンストラクタはprivateで隠蔽して、newの代わりにSingleton::getInstanceメソッドでインスタンスを取得する。
privateメンバ変数static Singleton* instanceで自身のインスタンスのポインタを保持している。

クラス外部で、Singleton* Singleton::instance = 0;により、自身のインスタンスのポインタを初期化している。

 // Singleton.h
 
 #ifndef SINGLETON_H
 #define SINGLETON_H
 
 class Singleton
 {
 private:
    static Singleton* m_Instance;  // インスタンスのポインタ
    int m_Data = 1;
 
    // シングルトンのインスタンス生成にはgetInstanceメソッドを使用するため、コンストラクタを隠蔽する
    Singleton()
    {
       std::cout << "Singleton instance created" << std::endl;
    }
 
    Singleton(const Singleton&)            = delete;  // コピーコンストラクタを禁止
    Singleton& operator=(const Singleton&) = delete;  // 代入演算子を禁止
 
 public:
    ~Singleton()
    {
        if (instance != nullptr) {
            delete instance;
            instance = nullptr;
        }
    }
 
    // インスタンスを取得するメソッド
    static Singleton* getInstance()
    {
       // インスタンスが存在しない場合は生成する
       if(m_Instance == NULL)
       {
          m_Instance = new Singleton();
       }
 
       return m_Instance;
    }
 
    int getData()
    {
       return Singleton::getInstance()->m_Data;
    }
 
    void setData(int data)
    {
       Singleton::getInstance()->m_Data = data;
    }
 };
 
 // インスタンスのポインタを初期化
 Singleton *Singleton::m_Instance = nullptr;
 
 #endif


上記のクラスの使用例を、以下に示す。

 #include <iostream>
 #include "Singleton.h"
 
 int main(int argc, char* argv[])
 {
    // インスタンスAを生成
    Singleton* InstanceA = Singleton::getInstance();
    std::cout << "インスタンス1のアドレス: " << InstanceA << std::endl;
 
    // 別のインスタンスBを生成
    Singleton* InstanceB = Singleton::getInstance();
    std::cout << "インスタンス2のアドレス: " << InstanceB << std::endl;
 
    // 各インスタンスのメソッドを呼び出す
    std::cout << "インスタンスAのdata: " << InstanceA->getData() << std::endl;
    std::cout << "インスタンスBのdata: " << InstanceB->getData() << std::endl;
 
    // インスタンスAの保持データを更新する
    std::cout << "インスタンスAのメンバ変数を10に更新" << std::endl;
    inst1->setData(10);
 
    // 各インスタンスのメソッドを呼び出す
    std::cout << "インスタンスAのdata: " << inst1->getData() << std::endl;
    std::cout << "インスタンスBのdata: " << inst2->getData() << std::endl;
 
    return 0;
 }


# 出力例
インスタンスAのアドレス: 0x9045kd9s
インスタンスBのアドレス: 0x9045kd9s
インスタンスAのdata: 1
インスタンスBのdata: 1
インスタンスAのメンバ変数を10に更新
インスタンスAのdata: 10
インスタンスBのdata: 10



参照ベースのシングルトンクラス

参照ベースのシングルトンクラスは、静的局所変数方式とポインタベース方式の中間的なアプローチである。

メモリ管理の注意が必要となり、明示的なデストラクタの実装やスマートポインタの使用すべき場合がある。

 class Singleton
 {
 private:
    Singleton() = default;
    static Singleton* createInstance()
    {
       return new Singleton();
    }
 
 public:
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
 
    static Singleton& getInstance()
    {
       static Singleton& instance(*createInstance());
       return instance;
    }
 };



静的局所変数を使用したシングルトンクラス (C++11以降)

静的局所変数を使用したシングルトンクラスは、以下に示すメリットがある。

  • スレッドセーフ (C++11規格で保証)
  • メモリリーク防止
  • nullポインタチェックが不要
  • デストラクタの明示的実装が不要
  • 遅延初期化 (lazy initialization) の実現
  • より安全で簡潔な実装


 class Singleton
 {
 private:
    Singleton() = default;  // プライベートコンストラクタ (外部からのインスタンス化を防止)
 
 public:
    Singleton(const Singleton&)            = delete;  // コピーコンストラクタを削除 (シングルトンの複製を防止)
    Singleton& operator=(const Singleton&) = delete;  // 代入演算子を削除 (シングルトンの代入を防止)
 
    // C++11以降では、静的局所変数の初期化はスレッドセーフであることが保証されている
    static Singleton& getInstance()
    {
       static Singleton instance;  // マルチスレッド環境でも安全
       return instance;
    }
 
    // ビジネスロジックメソッド
    void sampleMethod()
    {
       // ...略
    }
 };


 // 使用例
 
 #include <iostream>
 
 int main()
 {
    // インスタンスの取得
    Singleton& instance1 = Singleton::getInstance();
    Singleton& instance2 = Singleton::getInstance();
 
    // 同じインスタンスであることを確認
    if (&instance1 == &instance2) {
       std::cout << "同じインスタンスです" << std::endl;
    }
 
    // ビジネスロジックの実行
    instance1.sampleMethod();
 
    return 0;
 }



double-checked lockingパターンのシングルトンクラス (マルチスレッド対応)

double-checked lockingパターンのシングルトンクラスでは、以下に示す特徴がある。

  • atomicを使用したスレッドセーフな実装
  • メモリバリアによる適切な同期
  • 最初のチェックでロックを避けることによるパフォーマンス向上
  • 2重チェックによる安全性確保


※注意
ただし、この実装は複雑で微妙なバグが発生しやすいため、可能であればC++11以降の静的局所変数による実装を推奨する。

 #include <mutex>   // mutex機能とlock_guardを使用するために必要
 #include <atomic>  // アトミック操作とメモリオーダーを使用するために必要
 #include <memory>  // スマートポインタを使用する場合に必要
 
 class Singleton
 {
 private:
    // シングルトンインスタンスへのアクセスを同期するためのミューテックス
    static std::mutex mutex_;
 
    // シングルトンインスタンスへのアトミックポインタ
    // アトミック操作により、マルチスレッド環境での安全性を確保
    static std::atomic<Singleton*> instance_;
 
    // プライベートコンストラクタ (外部からのインスタンス化を防止)
    Singleton() = default;
 
 public:
    // コピーコンストラクタを削除 (シングルトンの複製を防止)
    Singleton(const Singleton&) = delete;
 
    // 代入演算子を削除 (シングルトンの代入を防止)
    Singleton& operator=(const Singleton&) = delete;
 
    // シングルトンインスタンスを取得するスレッドセーフな関数
    static Singleton* getInstance()
    {
       // 最初の確認 : ロック無しでインスタンスの存在を確認
       Singleton* tmp = instance_.load(std::memory_order_relaxed);
 
       // メモリバリア : 他のスレッドの変更を確実に認識
       std::atomic_thread_fence(std::memory_order_acquire);
 
       if (tmp == nullptr) {
          // ロックを取得して2重チェック
          std::lock_guard<std::mutex> lock(mutex_);
          tmp = instance_.load(std::memory_order_relaxed);
 
          // 2重チェック : 他のスレッドがインスタンスを生成していないことを確認
          if (tmp == nullptr) {
             // 新しいインスタンスを生成
             tmp = new Singleton;
 
             // メモリバリア : インスタンスの作成を他のスレッドに確実に通知
             std::atomic_thread_fence(std::memory_order_release);
 
             // 生成したインスタンスを保存
             instance_.store(tmp, std::memory_order_relaxed);
          }
       }
 
       return tmp;
    }
 };
 
 // 静的メンバ変数の初期化
 std::mutex Singleton::mutex_;
 std::atomic<Singleton*> Singleton::instance_{nullptr};