36
THE SINGLETON DESIGN PATTERN by A. Buniak

T HE S INGLETON DESIGN PATTERN by A. Buniak. В СТУП + ПОВТОРЕННЯ Шаблон Singleton гарантує, що певний клас матиме лише один екземпляр,

  • View
    215

  • Download
    1

Embed Size (px)

Citation preview

THE SINGLETON DESIGN PATTERNby 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

32

МОЖЛИВІ СТРАТЕГІЇ

НАВІТЬ ТАК

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.

36

Дякую за увагу