[ 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つの実装方法を紹介しています。