THE SINGLETON DESIGN PATTERN by A. Buniak
ВСТУП + ПОВТОРЕННЯ
Шаблон Singleton гарантує, що певний клас матиме лише один екземпляр, і забезпечує глобальний доступ до нього
Не дивлячись на легку постановку задачі, з’являються значні труднощі при реалізації
2/36
ЩО МИ ВИВЧИМО
Риси , що відрізняють Singleton від звичайної глобальної змінної
Основні ідіоми мови С++ для підтримки сінглтонів
Забезпечення унікальності сінглтону Видалення сінглтона, керування
тривалістю життя сінглтона, звернення до нього після видалення.
Багатопотоковість
3/36
ПОСТАНОВКА ЗАДАЧІ
Три класи-одинака : Keyboard, Display, Log Log генерує повідомлення про помилки
(якщо їх немає, Log взагалі не створюється).
Класу Log надсилаються всі помилки, які виникають при створенні та видаленні класів
Keyboard та Display.
4/36
ПЕРШІ ІДЕЇ
Лише статичні функції і статичні данісlass Singleton{
public: static void DoJob1();
…private: static int pole1; …
} 5/36
STATIC DATA + STATIC FUNCTIONS != SINGLETON
Лише статичні функції і статичні данісlass Singleton{
public: static void DoJob1();
…private: static int pole1; …
}1. Статичні функції не можуть бути віртуальними
2. Труднощі з ініціалізацією і видаленням об’єктів.
6/36
БІЛЬШ ЗВИЧНЕ РІШЕННЯ (ПОВТОРЕННЯ)
// Singleton.h
class Singleton
{
public:
static Singleton& Instance() //Єдина точка доступу, відсилка більш безпечніша
{
if (!pInstance_)
pInstance_ = new Singleton;
return pInstance_;
}
... operations ...
private:
Singleton(); // забороняємо явне створення нового об’єкту
Singleton(const Singleton&); // забороняємо створення копії Singleton-у
static Singleton* pInstance_; // єдиний екземпляр
Singleton& operator=(const Singleton&);// оператор = не має логіки для одинака
~Singleton();//закриваємо деструктор
};
// Singleton.cpp
Singleton* Singleton::pInstance_ = 0;
7/36
ВАЖЛИВА ДЕТАЛЬ
// Singleton.h
class Singleton
{
public:
…
private:
…
static Singleton* pInstance_;
//vs static Singleton instance_;
…
};
// Singleton.cpp
Singleton* Singleton::pInstance_ = 0;
//vs Singleton Singleton::instance ;
//динамічна vs статична ініціалізація 8/36
Проблемний приклад:
// SomeFile.cpp#include "Singleton.h"int global = Singleton::Instance()->DoSomething();
ВИДАЛЕННЯ SINGLETON
Memory leak має місце якщо конструктор одинака запросить необмежену к-сть ресурсів
Єдиний вихід – видаляти об’єкт класу Singleton при виході з програми
The Meyers Singleton Singleton& Singleton::Instance(){
static Singleton obj;return obj;
}9/36
ПСЕВДОКОД
Singleton& Singleton::Instance()
{
extern void __ConstructSingleton(void* memory);
extern void __DestroySingleton();
static bool __initialized = false;
static char __buffer[sizeof(Singleton)];
if (!__initialized)
{
__ConstructSingleton(__buffer);
atexit(__DestroySingleton);
__initialized = true;
}
return *reinterpret_cast<Singleton *>(__buffer);
}10/36
THE DEAD REFERENCE PROBLEMКонтр приклад:1) Keyboard::Instance() // створено без помилок//atexit(deleteKeyboard);2) Display::Instance() //трапилася помилка при створенні…Display(){ … if (error) Log::Instance().add (“Display creation error”); //atexit(deleteLog);}3) deleteLog(); //ок4) deleteKeyboard()// трапилася помилка при видаленні{if (error) Log::Instance().add (“Keyboard deleting error”);// Log::Instance() - dead reference, завдяки принципу LIFO}
11/36
ХОЧА Б ОБРОБИТИclass Singleton{…private:bool destroyed_;virtual ~Singleton(){pInstance_ = 0;destroyed_ = true;}} // Singleton.cpp…bool Singleton::destroyed_ = false; 12/
36
class Singleton{ … private: // Create a new Singleton and store a // pointer to it in pInstance_ static void Create(); { static Singleton theInstance; pInstance_ = &theInstance; } // Gets called if dead reference detected static void OnDeadReference() { throw std::runtime_error("Dead Reference Detected"); }
}
13/36
class Singleton{
public:Singleton& Instance() { if (!pInstance_) {
// Check for dead referenceif (destroyed_) {
OnDeadReference(); }
else { // First call—initialize Create(); }
} return pInstance_;
}…}
14/36
Проблема оброблена але не вирішена
ВИРІШЕННЯ – THE PHOENIX SINGLETON
void Singleton::OnDeadReference(){ // Отримати обгортку видаленого одинака
Create();//Тепер pInstance_ вказує на “попіл” singleton-у// - місце в пам’яті (raw memory) де знаходився singleton.//створюємо новий об’єкт на цьому місціnew(pInstance_) Singleton;// самі заносимо в стек ф-цію видалення нашого об’єктуatexit(KillPhoenixSingleton);// відновлюємо значення destroyed_ до falsedestroyed_ = false;
}void Singleton::KillPhoenixSingleton(){
//викликаємо деструктор вручну // він присвоїть pInstance_ нуль , а destroyed_ встановить truepInstance_->~Singleton();
}
15/36
ПРОБЛЕМИ Ф-ЦІЇ ATEXIT#include <cstdlib>void Bar(){...}void Foo(){std::atexit(Bar);}int main(){std::atexit(Foo);}
Рекомендація – добавити перевірку:#ifdef ATEXIT_FIXED// Queue this new object's destructoratexit(KillPhoenixSingleton);#endif
16/36
SINGLETONS WITH LONGEVITY: ПЕРШІ КРОКИ
Забезпечити щоб Log мав довшу тривалість життя ніж Keyboard і Display
Хотілося б :// Singleton classclass SomeSingleton { ... };// another Singleton classclass SomeSingleton2 { ... };int main(){
SetLongevity(&SomeSingleton().Instance(), 5);// Гарантувати видалення SomeSingleton2 саме після першогоSetLongevity(&SomeSingleton2().Instance(), 6);
}
17/36
SINGLETONS WITH LONGEVITY: DESIGN DECISIONS Кожен виклик SetLongevity ініціює виклик
atexit. Видалення об’єктів з меншою тривалістю
життя відбувається раніше ніж в об’єктів з більшою тривалістю.
Видалення об’єктів з однаковою тривалістю життя підпадає під стандартне правило мови С++ - останній створений перший зруйнований (LIFO).
18/36
SINGLETONS WITH LONGEVITY: РЕАЛІЗАЦІЯ
namespace Private{
typedef LifetimeTracker** TrackerArray;extern TrackerArray pTrackerArray;extern unsigned int elements;
}//Helper destroyer functiontemplate <typename T>struct Deleter{
static void Delete(T* pObj) { delete pObj; }
}; 19/36
SINGLETONS WITH LONGEVITY: РЕАЛІЗАЦІЯ
namespace Private{
class LifetimeTracker{public:LifetimeTracker(unsigned int x) : longevity_(x) {}virtual ~LifetimeTracker() = 0;friend inline bool Compare(unsigned int longevity,const LifetimeTracker* p) { return p->longevity_ > longevity; }private:unsigned int longevity_;
};// Definition requiredinline LifetimeTracker::~LifetimeTracker() {}}
20/36
SINGLETONS WITH LONGEVITY: РЕАЛІЗАЦІЯ
class ConcreteLifetimeTracker : public LifetimeTracker{public:
ConcreteLifetimeTracker(T* p, unsigned int longevity, Destroyer d) : LifetimeTracker(longevity), pTracked_(p), destroyer_(d){}~ConcreteLifetimeTracker(){ destroyer_(pTracked_);}
private:T* pTracked_;Destroyer destroyer_;
};void AtExitFn();
21/36
SINGLETONS WITH LONGEVITY: РЕАЛІЗАЦІЯ
template <typename T, typename Destroyer>void SetLongevity(T* pDynObject, unsigned int longevity,Destroyer d = Private::Deleter<T>::Delete){
TrackerArray pNewArray = static_cast<TrackerArray>(std::realloc(pTrackerArray, sizeof(T) * (elements + 1)));if (!pNewArray) throw std::bad_alloc();pTrackerArray = pNewArray;LifetimeTracker* p = new ConcreteLifetimeTracker<T, Destroyer>(pDynObject, longevity, d);TrackerArray pos = std::upper_bound(pTrackerArray, pTrackerArray + elements, longevity, Compare);std::copy_backward(pos, pTrackerArray + elements,pTrackerArray + elements + 1);*pos = p;++elements;std::atexit(AtExitFn);
}
22/36
SINGLETONS WITH LONGEVITY: РЕАЛІЗАЦІЯ
static void AtExitFn(){
assert(elements > 0 && pTrackerArray != 0);// Pick the element at the top of the stackLifetimeTracker* pTop = pTrackerArray[elements - 1];// Remove that object off the stack// Don't check errors-realloc with less memory// can't failpTrackerArray = static_cast<TrackerArray>(std::realloc(pTrackerArray, sizeof(T) * --elements));// Destroy the elementdelete pTop;
}
23/36
KDL PROBLEM: МАЙЖЕ ОСТАТОЧНЕ РІШЕННЯ
class Log{ public: static void Create() {
// Create the instancepInstance_ = new Log;// This line addedSetLongevity(*this, longevity_);
}// Rest of implementation omitted// Log::Instance remains as defined earlier private: // Define a fixed value for the longevity static const unsigned int longevity_ = 2; static Log* pInstance_;}; Keyboard та Display аналогічно, але longevity_ = 1
24/36
БАГАТОПОТОКОВІСТЬ
Singleton& Singleton::Instance(){
if (!pInstance_) // 1 { pInstance_ = new Singleton; // 2 }return *pInstance_; // 3
}
25/36
БАГАТОПОТОКОВІСТЬ: ПЕРШІ ІДЕЇ
Singleton& Singleton::Instance(){// mutex_ is a mutex object// Lock manages the mutexLock guard(mutex_);if (!pInstance_){
pInstance_ = new Singleton;}return *pInstance_;}
26/36
БАГАТОПОТОКОВІСТЬ: ПОКРАЩЕННЯ
Singleton& Singleton::Instance(){ if (!pInstance_)
{Lock guard(mutex_);pInstance_ = new Singleton;}
return *pInstance_;}
27/36
БАГАТОПОТОКОВІСТЬ: МАЙЖЕ НАЙКРАЩЕ.DOUBLE-CHECKED PATTERN
Singleton& Singleton::Instance(){ if (!pInstance_) // 1 { // 2 Guard myGuard(lock_); // 3 if (!pInstance_) // 4 {
pInstance_ = new Singleton; } }return *pInstance_;}
28/36
Хоча б: volatile pInstance_;
ОБ’ЄДНАННЯ ВСІХ ІДЕЙ Використання стратегій(див лекція 1). Розбиття:-стратегія Creation-стратегія Lifetime-стратегія Threading model
29/36
РЕАЛІЗАЦІЯtemplate<class T,template <class> class CreationPolicy = CreateUsingNew,template <class> class LifetimePolicy = DefaultLifetime,template <class> class ThreadingModel = SingleThreaded>class SingletonHolder{public:static T& Instance();private:// Helpersstatic void DestroySingleton();// ProtectionSingletonHolder();...// Datatypedef ThreadingModel<T>::VolatileType InstanceType;static InstanceType* pInstance_;static bool destroyed_;};
30/36
ВИМОГИ ДО СТРАТЕГІЙ1)Сreation – створення і видалення об’єктуT* pObj = Creator<T>::Create();
Creator<T>::Destroy(pObj);
2)LifeTimeLifetime<T>::ScheduleDestruction(pDestructionFunctio
n);Lifetime<T>::OnDeadReference();3) Multithreadtemplate <class T> class SingleTh template <class T> class
MultiTh
{ {
... ...
public: public:
typedef T VolatileType; typedef volatile T VolatileType;
}; };
typedef ThreadingModel<T>::VolatileType InstanceType;
31/36
НАВІТЬ ТАК
33/36
class A { ... };class Derived : public A { ... };template <class T> struct MyCreator : public
CreateUsingNew<T>{static T* Create(){
return new Derived;}};typedef SingletonHolder<A, StaticAllocator,
MyCreator> SingleA;
ЗАДАЧА KDL – FINAL DECISION
34/36
class KeyboardImpl { ... };
class DisplayImpl { ... };
class LogImpl { ... };
inline unsigned int GetLongevity(KeyboardImpl*) { return 1; }
inline unsigned int GetLongevity(DisplayImpl*) { return 1; }
// The log has greater longevity
inline unsigned int GetLongevity(LogImpl*) { return 2; }
typedef SingletonHolder<KeyboardImpl, SingletonWithLongevity> Keyboard;
typedef SingletonHolder<DisplayImpl, SingletonWithLongevity> Display;
typedef SingletonHolder<LogImpl, SingletonWithLongevity> Log;
NOTE
35/36
The implementation of Singleton put together here is not the do-it-all class. Only the features used are ultimately included in the generated code. Plus, the implementation leaves room for tweaks and extensions.