[ C++で開発 ]
C++でのSingletonパターンの実装は、一見簡単に見えますが、メモリの解放タイミング、マルチスレッド安全性、インスタンス数のコントロールと奥深い課題を抱えています。
書籍「オブジェクト指向における再利用のためのデザインパターン」(通称GoF本)より
あるクラスに対してインスタンスが1つしか存在しないことを保証し、それにアクセスするためのグローバルな方法を提供する。
- インスタンスへのアクセスを制御する。
- 名前空間を減らす。
- オペレーションや内部表現を詳細化できる。(Sngletonクラスのサブクラス化)
- インスタンスの数を変えることができる。
- クラスオペレーションよりも柔軟である。
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;
}
|
GoF本の実装例のコードが抱える問題点を列挙します。
Singleton* s1 = Singleton::getInstace();
Singleton* s2 = new Singleton(*s1);書籍「パターンハッチング」(John VLISSIDES著)より
GoFの著者陣の1人、John VLISSIDESが書いた本に、Singletonの実装が掲載されています。プログラム終了時に削除するSingletonDestroyerを導入しています。
class Singleton {
public:
static Singleton* instance();
protected:
Singleton();
Singleton(const Singleton&);
friend class SingletonDestroyer;
virtual ~Singleton() {}
private:
static Singleton* _instance;
static SingletonDestroyer _destroyer;
};
class SingletonDestroyer {
public:
SingletonDestroyer(Singleton* = 0);
~SingletonDestroyer();
void setSingleton(Singleton* s);
Singleton* getSingleton();
private:
Singleton* _singleton;
};
|
// Singletonの実装
Singleton* Singleton::_instance = 0;
SingletonDestroyer Singleton::_destroyer;
Singleton* Singleton::instance() {
if (!_instance) {
_instance = new Singleton;
_destroyer.setSingleton(_instance);
}
return _instance;
}
// SingletonDestroyerの実装
SingletonDestroyer::SingletonDestroyer(Singleton* s) {
_singleton = s;
}
SingletonDestroyer::~SingletonDestroyer() {
delete _singleton;
}
void SingletonDestroyer::setSingleton(Singleton* s) {
_singleton = s;
}
Singleton* SingletonDestroyer::getSingleton() {
return _singleton;
}
|
C++って、ヘッダーに宣言したメンバ関数を実装していなくてもエラーにならないんですね。ただし、実装がない関数を呼んでしまうと、リンクエラーとなります。よくよくC言語で考えれば納得です。
GoF本の実装での問題点については以下のとおりです。
書籍では、さらにSingletonDestroyerをテンプレート化していろいろなSingletonパターン適用クラスに汎用的に対応するサンプルを紹介しています。
また、SingletonDestroyerではなく、C言語標準関数atexit()を使ったクリーンアップについて言及しています。
staticなメンバー変数ではなく、関数のstaticローカル変数で唯一のインスタンスを定義する方法を使用します。
本記事はSingletonパターンの実装を比較して検討することが目的のため、書籍中のコードとクラス名等は変えています。
class Singleton {
public:
static Singleton& instance();
private:
Singleton();
Singleton(const Singleton&);
};
|
Singleton& Singleton::instance() {
static Singleton instance;
return instance;
}
|
GoF本の実装での問題点については以下のとおりです。
また、GoF本の実装にはない問題点として、サブクラス化が困難ということがあります。
マルチスレッド安全性を持たせるために、ダブルチェックロッキングを導入しています。また、削除については、ACE_Cleanupを継承して実現しています(コードは省略)。
template <class TYPE, class ACE_LOCK>
class ACE_Singleton : public ACE_Cleanup {
public:
static TYPE* instance();
virtual void cleanup(void* param = 0);
protected:
ACE_Singleton();
TYPE instance_;
static ACE_Singleton<TYPE, ACE_LOCK> *singleton_;
static ACE_Singleton<TYPE, ACE_LOCK> *&instance_i();
};
|
template <class TYPE, class ACE_LOCK>
TYPE* Singleton<TYPE, ACE_LOCK>::instance() {
ACE_Singleton<TYPE, ACE_LOCK> *&singleton =
ACE_Singleton<TYPE, ACE_LOCK>::instance_i();
if (singleton == 0) {
if (ACE_Object_Manager::starting_up() ||
ACE_Object_Manager::shutting_down())
{
ACE_NEW_RETURN(singleton, (ACE_Singleton<TYPE, ACE_LOCK>), 0);
} else {
static ACE_LOCK *lock = 0;
if (ACE_Object_Manager::get_singleton_lock(lock) != 0)
return 0;
ACE_GUARD_RETURN(ACE_LOCK, ace_mon, *lock, 0);
if (singleton == 0) {
ACE_NEW_RETURN(singleton, (ACE_Singleton<TYPE, ACE_LOCK>), 0);
ACE_Object_Manager::at_exit(singleton);
}
}
}
return &singleton->instance_;
}
|
書籍「BINARY HACKS ハッカー秘伝のテクニック100選」(高林哲、鵜飼文敏、佐藤祐介、浜地慎一郎、首藤一幸 著)より
HACK#37で「C++でシングルトンを実装する」というお題でマルチスレッド対応の4つの実装方法を紹介しています。