C++のデザインパターン - シングルトン
概要
シングルトンパターンは、クラスのインスタンスが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};