Prezentace se nahrává, počkejte prosím

Prezentace se nahrává, počkejte prosím

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.

Podobné prezentace


Prezentace na téma: "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."— Transkript prezentace:

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 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 Singleton – naivní implementace → základní myšlenka

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 Statická reference na jedinou instanci. Veřejná statická přístupová metoda. 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ě Ř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; } Na první pohled správné, ale kvůli nedefinovanému pořadí inicializace špatné! class Singleton { private: static Singleton instance; public: static Singleton * instance() { return &instance; } Instance se vytvoří při prvním volání instance() – až když je to potřeba.

9 Singleton – implementace C++ 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. Statická metoda pro přístup k jediné instanci. 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) class Singleton { public static Singleton Instance() { return instance; } void doSomethingUseful() {...} private Singleton() {...} private static Singleton instance = new Singleton(); } Statická metoda pro globální přístup k jediné instanci. 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 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í Singleton – implementace C# (a obdobně Java) 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. Privátní objekt inicializovaný privátním konstruktorem. 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í.

12 Singleton – Java problémy a enum implementace public enum Singleton { INSTANCE; public void doSomethingUseful() {... } }... Singleton.INSTANCE.doSomethingUseful() 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 Ř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í Řešení - zámky public static Singleton Instance() { if (instance == null) { instance = new Singleton(); } return instance; } sem se může dostat více vláken

15 Singleton – více vláken a procesorů, zamykání (C#) Jde to lépe?  referenci (či pointer) si můžeme ukládat – nevolat opakovaně Instance()  double-checked locking pattern 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 Ř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á

16 private static volatile Singleton instance; private static object lockObj = new Object(); public static Singleton Instance() { if (instance == null) { lock (lockObj) { if (instance == null) { instance = new Singleton(); } return instance; } Není to příliš jednoduché řešení pro tak složitý vzor? Singleton – double-checked locking 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í. 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

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# – 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 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ě

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; } virtual void doSomethingUseful() = 0; private: /* konstruktory, destruktory, atd. */ }; class Prvni : public Singleton { public: virtual void 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. Aby mohla bázová třída vytvářet instance. Implementace rozhraní.

20 Singleton – dědičnost (1. případ) 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 * 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. Registruje instanci singletonu pod zadaným jménem. 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 template 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 { friend class Base ; 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 vytváření abstrakních metod. Možnost šablonování metody instance(), místo třídy. Třída Base 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 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; Singleton – destrukce killer 2. destruktor killera 3. destruktor singletonu killer obsahuje ukazatel na náš singleton 1. destrukce statické proměnné Singleton* Singleton::instance() { if (!singletonKiller.getSingleton()) { singletonKiller.setSingleton( new Singleton); } return singletonKiller.getSingleton(); } 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í

25 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();... } 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 1. zavolá se metoda 2. inicializace statického objektu pouze při prvním průchodu registrace atexit 3. konstruktor objektu 4. návrat zkonstruovaného objektu 5. konec programu, destrukce statických proměnných 6. destruktor objektu

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 (__buffer); } funkce generované kompilátorem proměnné generované kompilátorem volání funkce __constructSingleton() zavolá konstruktor na paměti __buffer __buffer obsahuje Singleton zaregistruje destrukci

27 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 Singleton – pořadí destrukce 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 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; Singleton – detekce mrtvé reference Detekce destruovaného objektu  přidání statické proměnné, její nastavení v destruktoru detekce problému nastavení zrušení inicializace přesunuta do privátní metody příznak zrušení

29 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 Singleton – Fénix 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 class SomeSingleton {... }; class SomeClass {... }; SomeClass* pGlobalObject(new SomeClass); template void SetLongevity(T* pDynObj, int longevity); int main() { SetLongevity(&SomeSingle().Inst(), 5); SetLongevity(pGlobalObject, 6);... } 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 šablona pro nastavování dlouhověkosti po ukončení programu se objekty destruují v tomto pořadí prioritní fronta na zabití

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 ConcreteLTT ~ConcreteLTT() delete obj; T ~T() šablona instanciována a objekt vytvořen šablonou SetLongevity LifeTimeTracker virtual ~LifeTimeTracker() longevity virtuální držák virtuální destruktor

32 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 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


Stáhnout ppt "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."

Podobné prezentace


Reklamy Google