Singleton を C++ で
Singleton は、 生成に関するデザインパターンの一つで、 この種類のオブジェクトがただ一つだけ存在することを保証し、 他のコードに対して唯一のアクセス・ポイントを提供します。
Singleton には、 大域変数とほぼ同じ長所と短所があります。 両方とも随分と便利ですが、 コードのモジュール性を犠牲にしています。
シングルトンのクラスに依存しているあるクラスを使う場合、 シングルトンのクラスも一緒に使う必要があります。 ほとんどの場合、 この制限は、 ユニット・テストの作成で問題となります。
複雑度:
人気度:
使用例: 多くの開発者は、 Singleton をアンチ・パターンと見なしています。 C++ コードでの使用が減少したのはこのためです。
見つけ方: Singleton は、 キャッシュされた同一オブジェクトを返す静的生成メソッドで識別できます。
素朴なシングルトン
いい加減なシングルトンの実装は、 超簡単です。 コンストラクターを隠して、 静的生成メソッドを一つ書くだけです。
同じクラスは、 マルチ・スレッド環境下では正しく動作しません。 複数のスレッドが、 生成メソッドを同時に呼ぶことができ、 シングルトン・クラスの複数のインスタンスができてしまいます。
main.cc: 概念的な例
/** * The Singleton class defines the `GetInstance` method that serves as an * alternative to constructor and lets clients access the same instance of this * class over and over. */ class Singleton { /** * The Singleton's constructor should always be private to prevent direct * construction calls with the `new` operator. */ protected: Singleton(const std::string value): value_(value) { } static Singleton* singleton_; std::string value_; public: /** * Singletons should not be cloneable. */ Singleton(Singleton &other) = delete; /** * Singletons should not be assignable. */ void operator=(const Singleton &) = delete; /** * This is the static method that controls the access to the singleton * instance. On the first run, it creates a singleton object and places it * into the static field. On subsequent runs, it returns the client existing * object stored in the static field. */ static Singleton *GetInstance(const std::string& value); /** * Finally, any singleton should define some business logic, which can be * executed on its instance. */ void SomeBusinessLogic() { // ... } std::string value() const{ return value_; } }; Singleton* Singleton::singleton_= nullptr;; /** * Static methods should be defined outside the class. */ Singleton *Singleton::GetInstance(const std::string& value) { /** * This is a safer way to create an instance. instance = new Singleton is * dangeruous in case two instance threads wants to access at the same time */ if(singleton_==nullptr){ singleton_ = new Singleton(value); } return singleton_; } void ThreadFoo(){ // Following code emulates slow initialization. std::this_thread::sleep_for(std::chrono::milliseconds(1000)); Singleton* singleton = Singleton::GetInstance("FOO"); std::cout << singleton->value() << "\n"; } void ThreadBar(){ // Following code emulates slow initialization. std::this_thread::sleep_for(std::chrono::milliseconds(1000)); Singleton* singleton = Singleton::GetInstance("BAR"); std::cout << singleton->value() << "\n"; } int main() { std::cout <<"If you see the same value, then singleton was reused (yay!\n" << "If you see different values, then 2 singletons were created (booo!!)\n\n" << "RESULT:\n"; std::thread t1(ThreadFoo); std::thread t2(ThreadBar); t1.join(); t2.join(); return 0; } Output.txt: 実行結果
If you see the same value, then singleton was reused (yay! If you see different values, then 2 singletons were created (booo!!) RESULT: BAR FOO スレッド・セーフなシングルトン
問題を可決するには、 最初のシングルトン・オブジェクトの生成の間、 スレッドを同期する必要があります。
main.cc: 概念的な例
/** * The Singleton class defines the `GetInstance` method that serves as an * alternative to constructor and lets clients access the same instance of this * class over and over. */ class Singleton { /** * The Singleton's constructor/destructor should always be private to * prevent direct construction/desctruction calls with the `new`/`delete` * operator. */ private: static Singleton * pinstance_; static std::mutex mutex_; protected: Singleton(const std::string value): value_(value) { } ~Singleton() {} std::string value_; public: /** * Singletons should not be cloneable. */ Singleton(Singleton &other) = delete; /** * Singletons should not be assignable. */ void operator=(const Singleton &) = delete; /** * This is the static method that controls the access to the singleton * instance. On the first run, it creates a singleton object and places it * into the static field. On subsequent runs, it returns the client existing * object stored in the static field. */ static Singleton *GetInstance(const std::string& value); /** * Finally, any singleton should define some business logic, which can be * executed on its instance. */ void SomeBusinessLogic() { // ... } std::string value() const{ return value_; } }; /** * Static methods should be defined outside the class. */ Singleton* Singleton::pinstance_{nullptr}; std::mutex Singleton::mutex_; /** * The first time we call GetInstance we will lock the storage location * and then we make sure again that the variable is null and then we * set the value. RU: */ Singleton *Singleton::GetInstance(const std::string& value) { std::lock_guard<std::mutex> lock(mutex_); if (pinstance_ == nullptr) { pinstance_ = new Singleton(value); } return pinstance_; } void ThreadFoo(){ // Following code emulates slow initialization. std::this_thread::sleep_for(std::chrono::milliseconds(1000)); Singleton* singleton = Singleton::GetInstance("FOO"); std::cout << singleton->value() << "\n"; } void ThreadBar(){ // Following code emulates slow initialization. std::this_thread::sleep_for(std::chrono::milliseconds(1000)); Singleton* singleton = Singleton::GetInstance("BAR"); std::cout << singleton->value() << "\n"; } int main() { std::cout <<"If you see the same value, then singleton was reused (yay!\n" << "If you see different values, then 2 singletons were created (booo!!)\n\n" << "RESULT:\n"; std::thread t1(ThreadFoo); std::thread t2(ThreadBar); t1.join(); t2.join(); return 0; } Output.txt: 実行結果
If you see the same value, then singleton was reused (yay! If you see different values, then 2 singletons were created (booo!!) RESULT: FOO FOO