( Andrei | 2014. 07. 18., p – 12:26 )

Ha eleg messzirol inditom a dolgot, nezzunk egy fuggvenyt:

void foo() {
some_class obj;
}

some_class konstruktora meghivodik, amikor obj definiciojahoz erunk es a destruktor meghivodik, amikor scope-ot elhagyod. Fontos, hogy ez nem fugg attol, hogy hogyan hagyod el az adott scope-ot, tehat az lehet ido elotti return, kivetel dobasa es normal visszateres a fuggveny vegen. Ez azt jelenti, hogyha obj valamilyen eroforrast birtokol es a destruktorban gondoskodsz a felszabaditasrol, akkor egeszen biztosan nem lesz eroforras szivargasod.

Ez eddig egyszeru es jogosan adodik a kerdes, hogy miert mas ez, mint a GC (ott is felszabadulnak automatikusan a nem hivatkozott objektumok). A mogottes mukodes elegge eltero:

- obj felszabaditasa c++-ban azonnal megtortenik, amint elhagyod a fuggvenyt, GC eseteben a felszabaditas megtortenik valamikor kesobb (az hogy mikor, az nem igazan determinisztikus)
- ha tobb lokalis objektumod is van, akkor a felszabaditas sorrendje deteminisztikus c++-ban (jelesul a letrehozas sorrendjenek ellentettje). GC eseten a felszabaditas sorrendje nem determinisztikus
- a GC algoritmusa lenyegesen bonyolultabb, mint amit a c++-runtime-nak kell vegrehajtania. A c++-runtime-nak pontos kepe van arrol, hogy melyik objektumokat kell felszabaditani egy scope elhagyasakor (azokat, amik az adott scope-ban jottek letre), a garbage collectornak fel kell epitenie az elerheto objektumok grafjat, es a kimarado objektumok mennek a levesbe, ez aszimptotikusan egy nehezebb muvelet.

Ennek a GC viselkedesnek eleg sok (szerintem) negativ kovetkezmenye van:
- Ha olyan eroforrast birtokol az objektum, amivel takarekoskodni kell, vagy az eroforras akadalyozza a tovabbi programfutast (pl. egy lock-ot tart), akkor nem eleg azt tudni, hogy "majd valamikor azert felszabadul".
- Ha tobb objektumot gyujt be egyszerre a gc, amelyek hivatkoznak egymasra, akkor nem elhetsz azzal a feltetelezessel, hogy az objektum referenciai korrektek. Eleg problemassa valik a destruktorok jo implementacioja.

Termeszetesen ez nem azt jelenti, hogy a c++ okos a tobbiek meg tok hulyek.

Pl. legjobb tudomasom szerint c#-ban az elso problemat ugy oldjak meg, hogy van az ugynevezett IDisposable interfesz. Ha egy osztalyod ezt megvalositja es hasznalod a using konstrukciot, akkor a Dispose() meghivasaval determinisztikussa valik a felszabaditas. Viszont a programkodod is bonyolultabb lesz, ugyanis a fenti cucc igy nez ki c#-ban:

void foo() {
using (obj = new some_class()) {
...
}
}

remelem nem rontottam el, regen nem programoztam c#-ban, de gondolom a lenyeg atment. Szoval uj scope-ot kell nyitni, ami szerintem rontja az olvashatosagot es bonyolitja a programod szerkezetet. Masfelol ha elfelejted ezt megtenni, akkor maris ott van a baki, hogy az objektum mar nincs scope-ban, de meg rajta csucsul az eroforrason (tehat az automatikusnak szant eroforraskezeles megiscsak explicit marad, es el lehet rontani). Hiaba irod meg jol az osztalyodat, nem tul nehez (figyelmetlensegbol) pazarlo modon hasznalni. Ehhez kepest a c++ megkozelitest ugymond akarni kell rosszul hasznalni, a veletlen hiba eselye gyakorlatilag nulla (ezt hivjuk ugy, hogy "protection against Murphy, but not against Machiavelli").

Nyilvan nagy kenyelem a GC, de alapjaban veve nemdeterminisztikus folyamat, amit nem igazan lehet determinisztikussa tenni. Ezert is van, hogy a C++ jobb teljesitmenykritikus programok irasara, mint a GC kornyezetek. A GC altal okozott teljesitmenyminuszt szoktak altalaban szidni, de szerintem amirol most irtam sokkal kritikusabb tud lenni, mert a programod helyesseget is befolyasolja.

Errol szol az alaptechnika. Mondjuk a smart pointerek koncepciojaval parositva natur c-s handle-ek eroforrasat is lehet vedeni, tovabba teljesen ki lehet valtani az explicit new/delete hasznalatat dinamikus memoria hasznalatakor. De ez mar a kovetkezo lepcso...