1 / 34

Singleton

Singleton. Jasná i temnější zákoutí jazyka C++. Všichni patrně znají…. ….klíčové slovo static a jeho význam pro členy třídy. ….rozdíl mezi statickou a dynamickou alokací (operátor new ). Někdo už možná pozapomněl, ale každý by asi domyslel, že….

faye
Download Presentation

Singleton

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Singleton

  2. Jasná i temnější zákoutí jazyka C++ • Všichni patrně znají…. • ….klíčové slovo static a jeho význam pro členy třídy. • ….rozdíl mezi statickou a dynamickou alokací (operátor new). • Někdo už možná pozapomněl, ale každý by asi domyslel, že…. • ....není definováno pořadí inicializace statických objektů v různých .cpp souborech. • ….pořadí odstraňování těchto objektů je obrácené než pořadí inicializace. • Avšak toto pořadí není možné za běhu programu nijak rozumně a hezky zjistit. • Málokdo ví…. • ….jak přesně funguje funkce atexit().

  3. Singleton – obsah • Motivace • Základní myšlenka • Implementace • Problémy • Vícevláknovost • dědičnost • destrukce • Shrnutí

  4. Singleton – motivace • Co mají následující příklady společného? • DatabaseConnectionPool – přiděluje spojení do DB jednotlivým částem aplikace • WindowManager – v okenním systému existuje jeden objekt spravující okna • Keyboard, display, log – (KDL) • PrinterSpooler – více tiskáren, jeden manažer • FileSystem • System clock • Herní prostředí – např. mapa ve hře • Odpověď: • Třídy řešící tyto problémy vyžadují jedinou instanci. Mít více instancí může být zbytečné, nežádoucí, nebezpečné nebo nemožné. • Měly by být lehce přístupné z libovolného místa.

  5. Singleton – naivní implementace → základní myšlenka • Globální proměnná • + zaručuje globální přístupový bod • - znepřehledňuje namespace a kód • - nedokáže zaručit jedinou instanci objektu Statická data + statické metody • + zaručuje globální přístupový bod • + zaručuje jedinou instanci • - problém rozšiřitelnosti – statické metody nemohou být virtuální • - problém inicializace (závislosti) • - problém úklidu (kdo to má dělat) • Lepší řešení • třída se o jedinečnost své instance stará sama • inicializace: konstruktor • úklid: destruktor

  6. Singleton – požadavky • Zaručení jediné instance • zakázat vytvoření více instancí • zakázat kopírování existující instance • zakázat zrušení instance • zakázat deserializaci • Globální přístupový bod • použití na libovolném místě kódu • přístup poskytován samotnou třídou • Navíc bychom rádi zachovali • možnost rozšiřitelnosti bez nutnosti zásahu do kódu předka • všechny nástroje objektového programování • virtuální metody, abstraktní metody, přetěžování metod • možnost rozšiřitelnosti pomocí šablon

  7. Singleton – návrh řešení • třída si řídí vytváření instance a přístup k ní sama • je schopná zajistit jedinečnost instance Veřejná statická přístupová metoda. Statická reference na jedinou instanci. Metody nad instancí singletonu.

  8. Singleton – Lazy instantiation • V C++ nelze použít statickou inicializaci • nelze definovat pořadí konstruktorů statických tříd • navíc objekt může být vytvořen zbytečně class Singleton { private: static Singleton instance; public: static Singleton * instance() { return &instance; } } Na první pohled správné, ale kvůli nedefinovanému pořadí inicializace špatné! • Řešení - objekt bude vytvořen, až když o něj bude skutečně požádáno • lze snadno ovlivnit pořadí konstrukce • nikdy nebude vytvořen zbytečně • lze dosáhnout malou úpravou statické funkce vracející instanci objektu • analogická implementace funkční ve všech rozumných jazycích static Singleton instance() { if (instance == 0){ instance = new Singleton; } return instance; } Instance se vytvoří při prvním volání instance() – až když je to potřeba.

  9. Singleton – implementace C++ Statická metoda pro přístup k jediné instanci. class Singleton { public: static Singleton &instance() { if (pInstance == 0) { pInstance = new Singleton; } return pInstance; } void doSomethingUseful() {...} private: Singleton() {...} Singleton(const Singleton&); Singleton& operator=(const Singleton&); ~Singleton() {...} static Singleton* pInstance; }; Singleton* Singleton::pInstance = 0; int main(int argc, char** argv) { Singleton::instance().doSomethingUseful(); ... } Instance se vytvoří při prvním volání instance() – až když je to potřeba. Konstruktor nesmí být public. Inicializuje třídu. Volán jenom zevnitř třídy. Kopírovací konstruktor a operátor přiřazení se snaží kompilátor generovat sám. Je nutné mu v tom zabránit. Destruktor nesmí být public, někdo by mohl smazat instanci. Ukazatel na instanci si spravuje třída sama. Statická inicializace. Jednoduchý příklad použití.

  10. Singleton – implementace C# (a Java) Statická metoda pro globální přístup k jediné instanci. class Singleton { public static Singleton Instance() { return instance; } void doSomethingUseful() {...} private Singleton() {...} privatestatic Singleton instance = new Singleton(); } Konstruktor nesmí být public. Inicializuje třídu. Volán jenom zevnitř třídy. Referenci na instanci si spravuje třída sama. Statická inicializace. • C# a Java v tomto případě zajistí vždy korektní konstrukci a téměř nikdy nevytvoří Singleton zbytečně

  11. Singleton – implementace C# (a obdobně Java) Detailnější pohled – ještě línější implementace • C# a Java inicializují statické položky třídy před prvním přístupem • Navíc tím získáme thread-safe řešení publicclass Singleton { private Singleton () {} class SingletonCreator { private static SingletonCreator () {} internalstaticreadonly Singleton instance = new Singleton(); } publicstatic Singleton Instance() { return SingletonCreator.instancee; } } Vnitřní třída pro línou inicializaci. Viditelnost internal, aby vnější třída mohla k této položce přistoupit. Pro vnější svět je stále privátní. Privátní objekt inicializovaný privátním konstruktorem.

  12. Singleton – Java problémy a enum implementace • Problém serializace, pokud ji požadujeme – vrací novou instanci => je nutné mít všechny položky transient a přetížit readResolve • Problém s reflection – lze přistoupit k privátnímu konstruktoru => měl by házet výjimku, pokud už instance existuje • Jiné řešení – enum Singleton publicenum Singleton { INSTANCE; public void doSomethingUseful() { ... } } ... Singleton.INSTANCE.doSomethingUseful() • Řeší problém s reflection, serializací

  13. Singleton – problémy • GoF '95 • relativně jednoduchý vzor • od té doby rozsáhlé diskuse • Vlissides '98: • “I used to think it was one of the more trivial of our patterns ... Boy, I was wrong!” • Problém vícevláknových aplikací • Dědičnost • Problém destrukce (lifetime)

  14. Singleton – více vláken a procesorů • V prostředí více vláken nesplňuje definici • může vytvořit více instancí public static Singleton Instance() { if (instance == null){ instance = new Singleton(); } return instance; } sem se může dostat více vláken • Řešení - zámky

  15. Singleton – více vláken a procesorů, zamykání (C#) • Řešení - zámky • zaručují jednu instanci • na multiprocesorech nezbytná kombinace s volatile – cache coherency problem • funguje ve všech jazycích • režie může být nepřijatelně vysoká private static volatile Singleton instance; private static object lockObj = new Object(); publicstatic Singleton Instance() { lock (lockObj) { if (instance == null) { instance = new Singleton(); } } return instance; } uzamknutí zajistí přístup jen jednomu vláknu • Jde to lépe? • referenci (či pointer) si můžeme ukládat – nevolat opakovaně Instance() • double-checked locking pattern

  16. Singleton – double-checked locking • Pozorování • referenci získáváme často • konstrukci provádíme (doufejme) jen jednou • stačí zamykat jen při konstrukci • Double-Checked Locking Pattern private static volatile Singleton instance; private static object lockObj = new Object(); publicstatic Singleton Instance() { if (instance == null) { lock (lockObj) { if (instance == null) { instance = new Singleton(); } } } return instance; } Za první podmínku se může dostat více vláken. Za zámek už jen jedno vlákno, znovu ověření podmínky, jinak by později prolezlo případné další čekající vlákno. Zde mi to už nikdo nezmění. • Není to příliš jednoduché řešení pro tak složitý vzor?

  17. Singleton – double-checked locking • C++ problémy • norma pro vícevláknové aplikace mnoho nezaručuje • dovoluje prohazovat instrukce. Instance nemusí být null, ale přesto nemusí být inicializována • Java, C# problémy • dovolují při optimalizaci prohazovat instrukce. Instance nemusí být null, ale přesto nemusí být inicializována • až do Javy 1.2 chyba v garbage collectoru • až do Javy 1.4 nepříliš jasné a správné chování volatile • nyní už double-checked locking s volatile funguje správně • Java, C# – volatile • vlákna přistupují k proměnné „jako by byla celá v zamčené sekci“ • vlákno čte vždy aktuální necachovanou hodnotu

  18. Singleton – dědičnost • Singleton vydává za svou instanci instanci některého z potomků • existuje maximálně jedna instance přes všechny potomky • v C++ musí potomek být spřátelený s bázovou třídou nebo být vnořenou třídou • v Javě nebo C# musí být potomek vnořenou třídou • Několik singletonů implementuje společné rozhraní • existuje maximálně jedna instance pro každého z potomků

  19. Singleton – dědičnost (1. případ) class Singleton { public: static Singleton &instance() { if (pInstance == 0) { if (getEnv(“NAZEV”) == “PRVNI”) pInstance = new Prvni; else pInstance = new Druhy; } return pInstance; } virtualvoid doSomethingUseful() = 0; private: /* konstruktory, destruktory, atd. */ }; class Prvni : public Singleton { public: virtualvoid doSomethingUseful() {...} private: /* konstruktory, destruktory, atd. */ friend class Singleton; }; class Druhy : public Singleton { /* podobně jako třída Prvni */ }; Vytvoří se taková instance, která je požadována. Společné rozhraní, které musí všechny odděděné třídy implementovat. Implementace rozhraní. Aby mohla bázová třída vytvářet instance.

  20. Singleton – dědičnost (1. případ) Registruje instanci singletonu pod zadaným jménem. class Singleton { public: static void Register(const char * name, Singleton *); static Singleton * Instance() { if (_instance == 0) { const char * singletonName = getenv("SINGLETON"); _instance = Lookup(singletonName); } return _instance; } protected: static Singleton * Lookup(const char * name); private: static Singleton * _instance; static list<NameSingletonPair> * registry; }; class MySingleton : public Singleton { private: static MySingleton theSingleton; MySingleton() { ... Singleton::Register("MySingleton", this); } }; Nalezne se a vrátí taková instance, která je požadovaná. Najde instanci podle jména. Udržuje dvojici název odvozeného singletonu a jeho instanci. Singleton se při vytvoření registruje rodiči. Instance musí před jejím hledáním existovat! Problém: Je jedna „pravá“ instance přes všechny potomky, ale ostatní stejně existují

  21. Singleton – dědičnost (2. případ) #include <iostream> template<typename T> class Base { public: static T &instance() { static T inst; return inst; } virtual void echo () = 0; protected: Base() {} virtual ~Base() {} private: Base(const Base &); Base& operator=(const Base &); }; class Derived: public Base<Derived> { friend class Base<Derived>; public: void echo () { std::cout << “I’m Derived" << std::endl; } private: Derived() {} virtual ~Derived() {} }; int main(int argc, char **argv) { Derived &derived = Derived::instance(); derived.echo(); } Dědičnost implementována pomocí šablon. Možnost šablonování metody instance(), místo třídy. Možnost vytváření abstrakních metod. Třída Base<Derived> musí být náš friend (volání konstruktoru ze statické metody instance()). Jednoduché použití.

  22. Singleton – destrukce • Zodpovědnost za zrušení • je nutné uvolnit paměť? • je nutné uvolnit používané prostředky • Kdy je objekt zrušen • nutné dodržet určité pořadí rušení • Některé objekty je potřeba mít přístupné vždy • log

  23. Singleton – destrukce Ostrich • 'Ostrichovo' řešení (leaking singleton) • problém destrukce ignorovat • statická paměť se uvolní automaticky při ukončení procesu • jako každá kulturní třída by měl mít destruktor • zápis do logu, uzavření spojení, odhlášení, ... • lze vést diskuze o tom co je a co není leak (memory leak, resource leak) • V čem je problém? • destruktor se nezavolá • ukládáme statický ukazatel, ne statický objekt

  24. Singleton – destrukce killer • Idea • ukazatel zabalit do třídystarajíci se o destrukci • kompilátor se stará o zrušení statického objektu singletonKiller, který ve svém destruktoru instanci Singleton zabíjí class SingletonKiller { public: void setSingleton(Singleton* _instance) { instance = _instance; } Singleton* getSingleton() { return instance; } ~SingletonKiller() { delete instance; } private: static Singleton* instance; }; class Singleton { public: static Singleton* instance(); private: Singleton(); ~Singleton(); Singleton(const Singleton&); Singleton& operator=(const Singleton&); static SingletonKiller singletonKiller; }; Singleton* SingletonKiller::instance = 0; SingletonKiller Singleton::singletonKiller; 2. destruktor killera killer obsahuje ukazatel na náš singleton 3. destruktor singletonu 1. destrukce statické proměnné Singleton* Singleton::instance() { if (!singletonKiller.getSingleton()) { singletonKiller.setSingleton( new Singleton); } return singletonKiller.getSingleton(); }

  25. Singleton – Meyers • Scott Meyers • místo operátoru new, statická lokální proměnná • instanci nedržíme ve statickém ukazateli • funkce vracející referenci na statický objekt ve funkci 2. inicializace statického objektu pouze při prvním průchodu registrace atexit class Singleton { public: static Singleton& instance() { static Singleton inst; return inst; } private: Singleton() {...} Singleton(const Singleton&); Singleton& operator=(const Singleton&); ~Singleton() {...} }; int main(int argc, char** argv) { Singleton& s = Singleton::instance(); ... } 4. návrat zkonstruovaného objektu 3. konstruktor objektu 6. destruktor objektu 1. zavolá se metoda 5. konec programu, destrukce statických proměnných

  26. Singleton – funkce atexit • Odstraňování statických proměnných • LIFO – nejdříve se odstraní naposledy inicializované • int atexit(void*(pFunction)()); • při vytváření objektu se zaregistruje funkce pro zrušení • při ukončení programu se postupně zavolají registrované funkce 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); } funkce generované kompilátorem proměnné generované kompilátorem __buffer obsahuje Singleton volání funkce __constructSingleton() zavolá konstruktor na paměti __buffer zaregistruje destrukci

  27. Singleton – pořadí destrukce • Dead reference problem • při nevhodném pořadí mohou vzniknout reference na neexistující objekty • příklad: singletony Keyboard, Display, Log • vytvoření instance Log pouze při chybě • destrukce Logu by měla následovat až po destrukcích ostatních singletonů • nebo aspoň poznat problém a slušně umřít inicializace Keyboard inicializace Display s chybou inicializace Log a zapsání chyby konec programu destrukce Log destrukce Display destrukce Keyboard s chybou reference neexistujícího objektu Log

  28. Singleton – detekce mrtvé reference • Detekce destruovaného objektu • přidání statické proměnné, její nastavení v destruktoru class Singleton { public: static Singleton& instance() { if (!pInstance) { if (destroyed) throw ...; else create(); } return *pInstance; } private: static void create() { static Singleton inst; pInstance = &inst; } Singleton() {...} Singleton(const Singleton&); Singleton& operator=(const Singleton&); ~Singleton() { destroyed = true; pInstance = NULL; } staticbool destroyed; static Singleton* pInstance; }; Singleton* Singleton::pInstance = 0; bool Singleton::destroyed = false; detekce problému inicializace přesunutado privátní metody nastavení zrušení příznak zrušení

  29. Singleton – Fénix • Detekce někdy nestačí - nutnost přístupu k singletonu kdykoliv • idea: bájný pták Fénix vstane ze svého popela • znovuvytvoření při detekci zrušeného objektu • příklad KDL - Keyboard a Display obyčejné singletony, Log Fénix • C++: paměť statických objektů zůstane alokována až do konce běhu programu • problém: stav starého mrtvého Singletonu je navždy ztracen class Singleton { ... void KillPhoenix(); }; void Singleton::OnDeadRef() { Create(); new(pInstance) Singleton; atexit(KillPhoenix); destroyed = false; } void Singleton::KillPhoenix() { pInstance->~Singleton(); } zbytek třídy nezměněn při detekci zrušení se uloží reference na paměť zrušeného objektu placement new zavolání konstruktoru na daném místě registrace destruktoru fénixu explicitní zavolání destruktoru nelze delete!

  30. Singleton – dlouhověkost • Problémy Fénixe • ztráta stavu, uložení, uzavření, ... • Nejasnost C++ standardů ohledně funkce atexit() • Singleton s dlouhověkostí • při vytváření singletonu priorita destrukce • KDL – Log bude mít větší dlouhověkost • explicitní mechanismus destrukce objektů • nelze použít destrukci řízenou kompilátorem - pouze dynamické objekty mechanismus by měl fungovatna jakékoliv (dynamické) objekty class SomeSingleton { ... }; class SomeClass { ... }; SomeClass* pGlobalObject(new SomeClass); template <typename T> void SetLongevity(T* pDynObj, int longevity); int main() { SetLongevity(&SomeSingle().Inst(), 5); SetLongevity(pGlobalObject, 6); ... } šablona pronastavování dlouhověkosti prioritní fronta na zabití po ukončení programu se objekty destruují v tomto pořadí

  31. Singleton – implementace dlouhověkosti • Prioritní fronta • při stejných prioritách se chová jako zásobník • C++ pravidlo: dříve inicializované objekty se destruují později • čeho to bude fronta? • neexistuje společný předek, registrační funkce je šablona • ukazatel na abstraktního předka šablon obsahujících ukazatel na objekt virtuální držákvirtuální destruktor šablona instanciovánaa objekt vytvořenšablonou SetLongevity delete obj;

  32. Singleton – implementace dlouhověkosti class LifetimeTracker { public: LifetimeTracker(unsignedint x): longevity(x) {} virtual ~LifetimeTracker() = 0; friend inlinebool Compare( unsignedint longevity, const LifetimeTracker* p) { return p->longevity_ > longevity; } private: unsigned int longevity; }; inline LifetimeTracker::~LifetimeTracker() {} typedef LifetimeTracker** TrackerArray; extern TrackerArray pTA; extern unsigned int elements; template <typename T> struct Deleter { static void Delete(T* pObj) { delete pObj; } }; virtuální držák umí zabíjet... ... a porovnávat stáří vlastní fronta na zabití způsob zabití defaultně delete lze i free, ... Bylo dříve vejce nebo slepice? • TrackerArray se musí chovat jako Singleton • čímžpádem ale trpí všemi jeho problémy

  33. Singleton – kategorizace • Dělení z hlediska vytvoření • dynamické (operátor new, malloc) • destruktor se nezavolá • statické (Meyers singleton) • funkce vracející referenci na statický objekt • Threading model • jednovláknové • bez synchronizace • double-checked locking • problémy se synchronizací – platformě závislé • Životnost (lifetime) • Ostrichovo řešení (Leaking singleton) • resource, memory leaks • killer • obalení ukazatele třídou starající se o destrukci • životnost určená kompilátorem (Meyers singleton) • destrukce v pořadí podle LIFO • detekce mrtvé reference • při detekci zrušeného objektu výjimka • Fénix singleton • po zrušení objektu jeho znovuvytvoření • dlouhověkost (singleton with longevity) • specifikace pořadí destrukcí

  34. Singleton – shrnutí, související vzory • Možné použití • zjednodušení stavu aplikace - zajištění pouze jedné instance nějakého objektu • řízení přístupu do DB, tiskových zdrojů… • omezení plýtvaní zdrojů (lze přidělit existující spojení) • možnost regulovat počty, frekvence… přístupů • přidělení spojení s vhodnými právy • Související NV • Abstract Factory, Builder, Prototype • častá implementace pomocí singletonu • Facade • v případě potřeby pouze jednoho vstup do systému • State • stavové objekty jsou často Singletony

More Related