Stáhnout prezentaci
Prezentace se nahrává, počkejte prosím
1
Singleton 1 1
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(). 2 2
3
Singleton – obsah Motivace Základní myšlenka Implementace Problémy
Vícevláknovost dědičnost destrukce Shrnutí 3 3
4
Singleton – motivace Co mají následující příklady společného? Odpověď:
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. 4 4
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 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 problem inicializace – staticke promenne sa inicializuju pri zavadeni probramu. Norma nedefinuje poradi inicializace, nelze se tedy vytvarat zavislosti 5 5
6
Singleton – požadavky Zaručení jediné instance Globální přístupový bod
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 6 6
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. 7 7
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. 8 8
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í. 9 9
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() {...} private static 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ě 10 10
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í public class Singleton { private Singleton () {} class SingletonCreator { private static SingletonCreator () {} internal static readonly Singleton instance = new Singleton(); } public static 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. 11
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 public enum Singleton { INSTANCE; public void doSomethingUseful() { ... } } ... Singleton.INSTANCE.doSomethingUseful() Řeší problém s reflection, serializací 12
13
Singleton – problémy GoF '95 Problém vícevláknových aplikací Dědičnost
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) 13 13
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 14 14
15
uzamknutí zajistí přístup jen
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(); public static 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 15 15
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(); public static Singleton Instance() { if (instance == null) { lock (lockObj) { 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? 16 16
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 17 17
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ů 18 18
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; virtual void doSomethingUseful() = 0; private: /* konstruktory, destruktory, atd. */ }; class Prvni : public Singleton { virtual void doSomethingUseful() {...} 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. 19 19
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 { 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í 20
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>; void echo () { std::cout << “I’m Derived" << std::endl; } 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í. 21 21
22
Singleton – destrukce Zodpovědnost za zrušení Kdy je objekt 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 22 22
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 23 23
24
Singleton – destrukce killer
Idea ukazatel zabalit do třídy starají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 { static Singleton* instance(); 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(); 24 24
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 25 25
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 26 26
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 27 27
28
inicializace přesunuta do privátní metody
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; static bool destroyed; static Singleton* pInstance; }; Singleton* Singleton::pInstance = 0; bool Singleton::destroyed = false; detekce problému inicializace přesunuta do privátní metody nastavení zrušení příznak zrušení 28 28
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! 29 29
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 fungovat na 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 pro nastavování dlouhověkosti prioritní fronta na zabití po ukončení programu se objekty destruují v tomto pořadí 30 30
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žák virtuální destruktor LifeTimeTracker virtual ~LifeTimeTracker() longevity šablona instanciována a objekt vytvořen šablonou SetLongevity ConcreteLTT<T> ~ConcreteLTT() T ~T() delete obj; 31 31
32
vlastní fronta na zabití
Singleton – implementace dlouhověkosti 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; }; 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 32 32
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í 33 33
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 34 34
Podobné prezentace
© 2024 SlidePlayer.cz Inc.
All rights reserved.