
Singleton w języku C++
Singleton to kreacyjny wzorzec projektowy gwarantujący istnienie tylko jednego obiektu danego rodzaju. Udostępnia też pojedynczy punkt dostępowy do takiego obiektu z dowolnego miejsca w programie.
Singleton charakteryzuje się prawie takimi samymi zaletami i wadami jak zmienne globalne i chociaż jest bardzo poręczny, to jednak psuje modularność kodu.
Nie można przenieść klasy zależnej od Singletona i użyć jej w innym kontekście bez równoczesnego przeniesienia tego drugiego. To ograniczenie zazwyczaj ujawnia się na etapie tworzenia testów jednostkowych.
Złożoność:
Popularność:
Przykłady użycia: Wielu twórców oprogramowania uważa Singleton za antywzorzec, przez to jego wykorzystanie w kodzie C++ maleje.
Identyfikacja: Singleton można rozpoznać po statycznej metodzie kreacyjnej zwracającej jakiś obiekt którego instancja jest przechowywana w pamięci podręcznej.
Implementacja naiwna
Łatwo jest zaimplementować wzorzec Singleton niechlujnie — wystarczy ukryć konstruktor i zaimplementować statyczną metodę kreacyjną.
Ta sama klasa będzie działać nieprawidłowo w środowisku wielowątkowym — różne wątki mogą wywołać metodę kreacyjną w tym samym momencie, otrzymując wiele instancji klasy Singleton.
main.cc: Przykład koncepcyjny
/** * 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: Wynik działania
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
Singleton z bezpieczeństwem wątków
Aby pozbyć się wyżej wymienionego problemu, trzeba zsynchronizować wątki w momencie pierwszego tworzenia obiektu Singleton.
main.cc: Przykład koncepcyjny
/** * 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: Wynik działania
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