Smart Pointer koncepciója ( Egyszerű Smart Pointer implementálása C++ nyelven ) Bevezetés Mik a smart pointer-ek 1? A válasz egyszerű; a smart pointer-ek olyan mutatók amik okosak. Mit is jelent ez pontosan? Igazából a smart pointer-ek olyan objektumok amik pointer-ek extrákkal. Ezek az objektumok rugalmasak mint egy pointer és rendelkeznek az objektumok előnyeivel (pl.: konstruktor, destruktor hívás automatikusan). A smart pointer célja, hogy kezelje azokat a hibákat amiket az alapvető pointer-ek okoznak. (Innen jött az okos jelző) Problémák a pointer-ekkel Mik azok a gyakori problémák amikkel találkozunk miközben C++ pointer-eket használunk? A válasz, memória kezelés. Vessünk egy pillantást erre a kódra: Hányszor fordult elő, az a hiba, hogy elfelejtettük törölni a pname-t? Jó lenne ha valaki gondoskodna a memória felszabadításról, amikor a pointer már nincs használatban (most nem a garbage collector 2 - ra gondolok). Mi lenne ha pointer magától gondoskodna erről a problémáról? Pontosan erre lett megalkotva a smart pointer. Írjunk egy smart pointer osztályt és nézzük meg, hogy tudjuk vele kezelni a pointer-eket. 1.) Smart pointer jelentése okos mutatók, későbbiekben az angol nevét fogom használni 2.) Garbage Collector azaz szemétgyűjtő, például a JAVA nyelv használja
Kezdjük egy valósághű problémával, mondjuk legyen egy Személy osztály ami a következőképp van meghatározva: Nézzük meg ezt a kódot, minden egyes alkalommal amikor létrehozunk egy pointer-t akkor törölnünk is kell azt. Pontosan ezt szeretném elkerülni, kell valami automatikus eljárás ami törli a pointer-t. Ami azonnal eszembe jut az a destruktor, de a pointereknek nincs destruktora, mi legyen? A mi smart pointer-ünknek viszont van, tehát készítünk egy osztályt, legyen a neve SP ami tárolhat egy pointer-t a Személy osztályunkra, így törölni fogja a pointer-t amikor a destruktor meghívódik. Valahogy így: Vegyük észre a következőket: - Létrehoztunk egy objektumot az SP osztályból ami tárolja a mi Személy osztály pointerünket. Most a desktruktor az SP osztálynak meg fog hívódni amikor az objektumunk elhagyja hatáskörét, így törölni fogja a Személy típusú pointer-t; szóval nincs fájdalmas pointer törlés. - Még egy dolog nagyon fontos, ugyanúgy tudjuk meghívni a Mutat() eljárást (miközben az SP osztály objektumát használjuk) mint ahogy eredetileg 3 azaz az osztály pontosan pointer-ként fog viselkedni 3.) -> operátor a. operátor helyett
Interfész smart pointer-hez Mivel a smart pointer-nek pointer-ként kell viselkednie ezért tudnia kell mindent amit egy pointer tud. PL.: a következő operátorokat felül kell definiálni: - Csillag operátor (operator * dereferálás) - Indirection (operator ->) Írjuk meg az SP osztályt: Ez az osztály a mi smart pointer osztályunk. A fő célja ennek az osztálynak, hogy tároljon egy pointer-t a Személy osztályról és törölje azt amikor a desktruktor meghívódik miközben tudja mindazt amit egy pointernek tudnia kell. Generikus smart pointer Egy problémánk akad amit azonnal ki lehet szúrni ezt a smart pointer-t csak a Személy osztályra lehet alkalmazni. Ez azt jelenti, hogy minden egyes osztálynak és/vagy típusnak külön meg kellene írni a smart pointer osztályt és az nem egyszerű. De szerencsénkre segítségünkre vannak a templateek, így generikussá tudjuk tenni az osztályunkat, tegyük is meg: Most már rá tudjuk húzni bármely osztályra a smart pointer-ünk. Nézzük, meg hogy a smart pointerünk tényleg olyan okos-e mint gondoljuk
Nézzük mi történik itt, p és q ugyanarra a Személy osztálybeli pointer-re referál. Most, hogy q ki megy a hatókörből; q destruktor-a meg fog hívódni ami törli a Személy osztály pointer-t. Így nem tudjuk meghívni p->mutat(); mert p egy Dangling pointer 4 lesz, így itt hiba keletkezik. (Megj.: ez a hiba akkor is felmerült volna ha normál pointer-t használunk smart pointer helyett). Addig nem szabad törölni a Személy osztály pointer-t amíg valaki használja. Hogy is kéne ezt megoldani? Implementáljunk egy referencia számláló mechanizmust a smart pointer osztályunkba és ez meg fogja oldani a problémát. 4.) Dangling pointer: olyan pointer ami olyan területre mutat ami törölve lett.
Referencia számlálás Most pedig készítünk egy referencia számláló osztályt nevezzük RC nek. Ez az osztály fenntart egy egész típusú értéket ami a referencia számot fogja reprezentálni. Legyenek metódusaink amik növelik és csökkentik ezt a referencia számot. Most, hogy van egy referencia számláló osztályunk vezessük be a smart pointer osztályunkba. Fenntartunk egy RC pointer-t ami az ami megosztozik az összes példányon ami ugyanarra a pointer-re referál. Ahhoz, hogy ez megtörténjen felül kell definiálni az értékadó operátort és a Copy Konstruktor-t is az SP osztályunkban.
Amikor létrehozunk egy Szemely típusú smart pointer-t p néven, az SP konstruktora fog meghívódni az adatok el lesznek tárolva és egy új RC pointer is létre fog jönni. Az AddRef() metódusa az RC-nek meghívódik és megnöveli egyre a referencia számlálót. Most SP q = p; helyen létre jön egy új smart pointer q néven a Copy konstruktor által. Itt az adatok át fognak másolódni és a referencia megint megnövelődik, így már 2 lesz az értéke. Most r = p; meghívja az értékadó operátort és hozzá rendeli p értéket q-hoz. Itt ismét másolódnak az adatok így a referencia számláló 3-ra növekszik. Amikor r és q elhagyja a hatókört, a megfelelő objektumok destruktor-ai meg fognak hívódni. Itt még a referencia számlálók fognak csak csökkeni és az adatok nem fognak törlődni, hacsak a referencia számláló nem 0. Innentől az adatunk csak akkor fog törlődni ha már senki nem fog referálni rá. Folytatás hamarosan Példa program letölthető itt: http://people.inf.elte.hu/kabuabi/sp_sample.zip