You are on page 1of 18

Dr.

eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

Dodatak: Pametni pokazivai rjeenje za gotovo sve probleme


vezane za curenje memorije
Mogunost pojave curenja memorije predstavlja ozbiljan problem u programskim jezicima u kojima
se upravljanje dinamikom alokacijom memorije i oslobaanjem zauzetog prostora vri runo, kao to su
C i C++. Programeri koji programiraju u ovim programskim jezicima esto zaale to ne programiraju u
nekim jezicima kao to su recimo Java ili C#, koji podravaju automatsko upravljanje memorijom.
Naime, osnovna ideja u tim jezicima je da programer eksplicitno samo zauzima memoriju onda kad mu
je potrebna (upotrebom operatora new ili nekog slinog), dok oslobaanje memorije programer nikada
ne vri eksplicitno (odnosno, nikakav pandan operatoru delete u takvim jezicima ne postoji).
Umjesto toga, periodino se s vremena na vrijeme (bez kontrole programera) aktivira potprogram nazvan
sakuplja smea (engl. garbage collector) koji analizira stanje memorije i oslobaa prostor koji
zauzimaju objekti za koje sakuplja utvrdi da vie nisu potrebni (recimo alocirani objekti na koje ne
pokazuje niti jedan pokaziva). U ovom dodatku emo pokazati da je na relativno jednostavan nain u
jeziku C++ mogue ostvariti vrlo slian efekat, tj. ostvariti skoro automatsko upravljanje memorijom, i
to bez upotrebe sakupljaa smea (koji svakako ne postoji u jezicima C i C++).
Prije nego to objasnimo monu tehniku za skoro automatsko upravljanje memorijom zasnovanu na
tzv. pametnim pokazivaima (engl. smart pointers), treba naglasiti da ni sakupljai smea nisu rjeenja
za sve probleme, a pored toga, oni uvode i neke nove probleme. Na prvom mjestu, aktiviranje sakupljaa
smea uvijek nakratko pauzira izvoenje programa (u nepredvidljivim trenucima), to moe biti vrlo
problematino u vremenski kritinim aplikacijama i aplikacijama koje rade u realnom vremenu. Dalje,
kada se koristi sakuplja smea, posve je nepredvidljivo u kojem e tano trenutku neki objekat biti
uklonjen iz memorije, to onemoguava vezivanje ikakve skupine akcija koja bi se automatski izvrila u
nekom predvidljivom trenutku pri uklanjanju objekta iz memorije (drugim rijeima, koncept destruktora
je nespojiv sa konceptom sakupljanja smea), to ponekad moe biti veliko ogranienje. Pored toga,
treba naglasiti da memorija nije jedini resurs koji moe curiti, ve to moe svaki raunarski resurs koji
se zauzima na zahtjev tokom izvoenja programa. Takvi resursi su npr. datoteke, zatim tzv. muteksi u
tzv. vienitnim programima, tzv. brave slogova u bazama podataka, rukovatelji (engl. handles) grafikim
objektima i mnogi drugi. Pri tome, sakuplja smea rjeava samo probleme curenja memorije, dok je
potpuno slijep po pitanju curenja drugih resursa. Programeri u Javi ili C#-u koji su potpuno nenavikli
na curenje memorije, teko se bore sa problemima curenja drugih resursa (koji se moraju rjeavati na
manje-vie identian nain kako se u C++-u rjeava problem curenja memorije), dok se C++ programeri
vrlo lako snalaze u takvim situacijama, jer su navikli da resursi mogu curiti. Pri tome, C++ programeri
tipino rjeavaju curenje raznoraznih resursa na praktino identian nain kako emo ovdje rijeiti
problem curenja memorije (dok je taj nain recimo u Javi neprimjenljiv, zbog nepostojanja destruktora).
Treba naglasiti da postoje brojni pokuaji da se napravi sakuplja smea za jezike C ili C++ koji bi
radio na identian nain kao recimo u Javi ili C++-u. Mada postoje takvi sakupljai koji rade manje ili
vie uspjeno, treba znati da se nikada nee moi napraviti potpuno automatski sakuplja smea za
jezike C ili C++ koji bi radio uspjeno u svim situacijama. Razlog je jednostavan: ovi jezici prosto od
poetka nisu bili dizajnirani tako da mogu pouzdano saraivati sa sakupljaem smea. Naime, C ili C++
podravaju neke konstrukcije koje jednostavno nisu saglasne sa konceptom sakupljanja smea. Recimo,
pretpostavimo da postoji sakuplja smea i razmotrimo sljedei isjeak koda u C++-u:
Student *s(new Student(...));
// Parametri konstruktora nisu bitni
s += 10;
...
// ta ako se ovdje aktivira sakuplja smea???
s -= 10;
cout << s->DajIndeks();

U ovom isjeku, dinamiki alociramo objekat tipa Student i dodjeljujemo njegovu adresu
pokazivau s. Nakon toga, iz nekih razloga, poveavamo pokaziva s za 10 mjesta unaprijed
(smisao ovakve igrarije je dosta upitan, ali je ovo u naelu mogue). Pokaziva s vie ne pokazuje na
dinamiki alocirani objekat. Zapravo, na njega vie ne pokazuje niko, i sakuplja smea ima pravo da ga

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

ukloni kada se aktivira. Pretpostavimo da se dogodi njegovo aktiviranje prije nego to se izvri naredba
s -= 10. Sakuplja smea e ukloniti objekat iz memorije. Meutim, nakon naredbe s 10,
pokaziva s pokazuje ponovo na isto mjesto u memoriji kao i neposredno nakon alokacije objekta.
Programer vjerovatno oekuje da je dinamiki alocirani objekat i dalje tu (s obzirom da, posmatrajui
program, ne vidi nikakav razlog da on vie nije tu). Meutim, ukoliko se u meuvremenu aktivirao
sakuplja smea, on e sigurno ukloniti objekat, jer ne moe predviditi da e se kasnije pokaziva s
ponovo pokazivati na staro mjesto. Dakle, u ovom primjeru sakuplja smea bi mogao dovesti do
brisanja objekta za koji programer oekuje da je i dalje u memoriji. Ni najinteligentniji sakuplja smea
ne moe se ispravno snai u ovakvim situacijama. Problem je u Javi i C#-u rijeen tako da su u tim
jezicima ovakve vratolomije prosto zabranjene. Meutim, jezici C i C++ daju veliku slobodu u radu sa
pokazivaima, koja jednostavno kreiranje dobrog sakupljaa smea u ovim jezicima ini prosto
nemoguom misijom.
Nakon to smo vidjeli ta je nemogue, razmotrimo ta se moe uiniti. Vidjeemo da se, uz samo
malo discipline, moe uiniti zaista mnogo. Na prvom mjestu, trebamo razmotriti kako uope moe doi
do curenja memorije. Najoigledniji nain da napravimo curenje memorije je da dinamiki alociramo
memoriju i da je zaboravimo osloboditi prije nego to nestane pokaziva koji uva adresu alociranog
objekta, kao recimo u sljedeem primjeru:
int NekaFunkcija(...) {
Student *s(new Student(...));
...
return neka_vrijednost;
}

Ovdje do curenja memorije dolazi zbog injenice da po izlasku iz funkcije gubimo pokaziva s na
dinamiki alocirani objekat, koji i dalje ostaje alociran. Naravno, rjeenje bi bilo dodati naredbu
delete s prije naredbe return. Meutim, time nisu rijeeni svi potencijalni problemi. Na prvom
mjestu, postoje mnoge funkcije iz kojih je mogue izai na vie razliitih mjesta. Razmotrimo, na
primjer, sljedeu funkciju:
int NekaFunkcija(...) {
Student *s(new Student(...));
...
if(neki_uvjet) return neka_vrijednost;
...
if(neki_drugi_uvjet) return neka_druga_vrijednost;
...
delete s;
return neka_treca_vrijednost;
}

Bez obzira to ispred posljednje return naredbe imamo naredbu delete, do curenja memorije
e doi ukoliko doe do prijevremenog izlaska iz funkcije putem neke od ranijih return naredbi.
Oigledno bi naredbu delete trebalo staviti ispred svake return naredbe, to ve postaje veoma
zamorno i otvara veliku mogunost da negdje zaboravimo staviti delete:
int NekaFunkcija(...) {
Student *s(new Student(...));
...
if(neki_uvjet) {
delete s; return neka_vrijednost;
}
...
if(neki_drugi_uvjet) {
delete s; return neka_druga_vrijednost;
}
...
delete s;
return neka_treca_vrijednost;
}

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

Najgore od svega je to do izlaska iz funkcije moe doi i na neoekivani nain, putem bacanja
izuzetka. Posmatrajmo recimo sljedei primjer:
int NekaFunkcija(...) {
Student *s(new Student(...));
...
NekaDrugaFunkcija(...);
...
delete s;
return neka_treca_vrijednost;
}

Ovdje do curenja memorije moe doi ukoliko poziv funkcije NekaDrugaFunkcija baci izuzetak.
Naime, s obzirom da se u funkciji NekaFunkcija ne vri hvatanje izuzetaka, bacanje izuzetka
dovee do njenog trenutnog naputanja, tako da se naredba delete nee ni izvriti. Mogue ali ne
osobito elegantno rjeenje je hvatanje izuzetka unutar funkcije, pri emu e se tada vriti brisanje objekta
i prosljeivanje izuzetka na vii nivo obrade, kao u sljedeem primjeru:
int NekaFunkcija(...) {
Student *s(new Student(...));
...
try {
NekaDrugaFunkcija(...);
}
catch(...) {
delete s; throw;
}
...
delete s;
return neka_treca_vrijednost;
}

Neelegantnost ovakvog rjeenja pogotovo dolazi do izraaja ukoliko postoji mnogo mjesta na kojima
moe doi do bacanja izuzetaka. Posebno je problematino ukoliko nismo posve sigurni gdje sve moe
doi do bacanja izuzetaka.
Kada bi gore opisani sluajevi bili jedini naini kako moe doi do curenja memorije, problem
curenja bi bilo vrlo lako rjeiv, jer emo vrlo uskoro vidjeti da za ovakva curenja postoji vrlo jednostavno
univerzalno rjeenje. Naalost, ovo ni izdaleka nisu jedini naini kako moe doi do curenja memorije.
Meutim, kako sve probleme treba rjeavati postepeno, pokaimo prvo kako se na univerzalan nain
rjeavaju problemi sa curenjem memorije uzrokovani gore opisanim primjerima. Ideja je vrlo jednostavna,
a zasniva se na poznatom C++ idiomu, koji se obino naziva skraenicom RAII (Resource Aquisition Is
Initialization). Prema tom idiomu, svakom resursu (pa tako i memoriji) treba pridruiti neku klasu, pri
emu zauzimanje tog resursa treba izvriti pri inicijalizaciji odgovarajueg primjerka te klase, dok
oslobaanje tog resursa treba povjeriti destruktoru. Konkretno, definiraemo neku klasu koju emo
nazvati recimo Drzac (engl. holder), s obzirom da joj je cilj da dri neki dinamiki alocirani
objekat. Ova klasa e u sebi imati sakriven pokaziva na dinamiki alocirani objekat, dok e se sama
alokacija odnosno dealokacija vriti prilikom inicijalizacije objekta odnosno prilikom njegovog
unitavanja (tj. unutar destruktora). Klasu emo izvesti kao generiku klasu, da bi mogla sadravati
pokazivae na objekte razliitih tipova. To bi izgledalo otprilike ovako:
template <typename Tip>
class Drzac {
Tip *pokazivac;
Drzac(const Drzac &);
// Neimplementirano
Drzac &operator =(const Drzac &);
// Neimplementirano
public:
explicit Drzac(Tip *pokazivac) : pokazivac(pokazivac) {}
~Drzac() { delete pokazivac; }
Tip *DajPokazivac() const { return pokazivac; }
};

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

Primijetimo da smo zabranili kopiranje i meusobno dodjeljivanje objekata tipa Drzac. Saznaemo
uskoro zato je to bitno, a prije toga pogledajmo kako bi se u osnovi ovakvi objekti mogli koristiti:
int NekaFunkcija(...) {
Drzac<Student> s(new Student(...));
...
return neka_vrijednost;
}

Ovdje je potpuno otpala potreba za naredbom delete, a prestali su postojati i razlozi za brigu oko
toga na koje se sve naine moe eventualno izai iz funkcije NekaFunkcija. Zaista, kako god izali
iz nje, tim izlaskom objekat s prestaje postojati, ime se izvrava njegov destruktor koji e osloboditi
dinamiki alocirani objekat tipa Student. Vrlo jednostavno, zar ne?
Funkcija lanica DajPokazivac slui za pristup pokazivau sakrivenom u objektu tipa Drzac.
Recimo, da pozovemo metodu Ispisi nad dinamiki kreiranim studentom iji se pokaziva uva u
drau s, mogli bismo pisati:
s.DajPokazivac()->Ispisi();

Ovo je posve jasno, ali je pomalo nezgrapno. Bilo bi mnogo ljepe kada bismo dra s mogli
sintaksno koristiti kao da se radi o pokazivau, tj. kad bi mogli pisati prosto
s->Ispisi();

Meutim, to uope nije teko postii! Uradimo li to, korak smo blie onome to nazivamo pametni
pokazivai. Tim terminom nazivamo objekte koji se i sa aspekta sintakse i sa aspekta koritenja ponaaju
manje-vie poput obinih pokazivaa, ali rade jo neke dodatne stvari koje obini pokazivai ne rade
(npr. vode brigu o automatskom oslobaanju dinamiki alocirane memorije onda kada ona vie nije
potrebna). Da vidimo prvo ta bi trebao neki objekat podravati da bismo mogli smatrati da se on
ponaa poput pokazivaa. Ako nita drugo, oekujemo da se na njega moe primijeniti dereferenciranje
(npr. da pomou *s moemo pristupiti odgovarajuem objektu tipa Student na koji objekat s
ukazuje) i da na njega moemo primijeniti operator ->. Slijedi da je sve to treba uraditi preklopiti
operator * (unarnu verziju) i operator ->. To moemo uraditi recimo ovako (nakon ovog
preklapanja, funkcija lanica DajPokazivac i nije vie osobito potrebna):
template <typename Tip>
class Drzac {
Tip *pokazivac;
Drzac(const Drzac &);
// Neimplementirano
Drzac &operator =(const Drzac &);
// Neimplementirano
public:
explicit Drzac(Tip *pokazivac) : pokazivac(pokazivac) {}
~Drzac() { delete pokazivac; }
Tip &operator *() const { return *pokazivac; }
Tip *operator ->() const { return pokazivac; }
};

Operatorska funkcija za operator * je vrlo jasna: ona prosto kao rezultat daje dereferencirani
pokaziva koji je sakriven unutar objekta tipa Drzac, to i elimo da postignemo. Iz funkcije se vraa
referenca iz dva razloga: da izbjegnemo nepotrebno kopiranje objekta i da omoguimo da se rezultat
dereferenciranja moe iskoristiti sa lijeve strane jednakosti (tj. kao l-vrijednost), to je naravno mogue
kod normalnih pokazivaa. Interesantnija je operatorska funkcija za operator ->. Jednom prilikom je
reeno da se izrazi poput x->y u sluaju kada objekat x ima preklopljen operator -> interpretiraju
kao x.operator->()->y. Dakle, izraz poput s->Ispisi() interpretirae se zapravo kao izraz
s.operator->()->Ispisi(). Meutim, kako operatorska funkcija za operator -> vraa kao
rezultat pokaziva interno pohranjen u objektu tipa Drzac, to e izraz s.operator->() biti
upravo taj pohranjeni pokaziva, tako da e se poziv metode Ispisi izvriti nad objektom na koji taj
pokaziva pokazuje, a upravo to smo i htjeli postii!
4

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

Ostaje da vidimo zbog ega smo zabranili kopiranje i meusobno dodjeljivanje objekata tipa
Drzac. Razlog je to bi, ovako kako su zasada napisani, njihovo kopiranje i meusobno dodjeljivanje
moglo imati kobne posljedice. Pretpostavimo recimo da je njihovo kopiranje mogue i razmotrimo
sljedei isjeak:
Drzac<Student> s1(new Student(...));
Drzac<Student> s2(s1);

Oba objekta s1 i s2 sada uvaju pokaziva na isti objekat tipa Student. Meutim, kada se izvri
destruktor jednog od njih, on e ukloniti taj objekat (odnosno osloboditi memoriju koju on zauzima).
Drugi od njih e tada sadravati visei pokaziva. to je jo gore, kada se izvri i njegov destruktor,
imaemo oslobaanje ve osloboene memorije, to e vrlo vjerovatno dovesti do kraha! Slina stvar bi
se dogodila i kada bi bilo dozvoljeno meusobno dodjeljivanje objekata tipa Drzac.
U sluaju kada nam nije neophodno kopiranje i meusobno dodjeljivanje objekata tipa Drzac
(koji u sebi uvaju pokazivae na dinamiki alocirane objekte i autotmatski se brinu o njihovom
unitavanju), ovakvo rjeenje problema sa curenjem memorije sasvim zadovoljava. Meutim, esto je
zabrana kopiranja i meusobnog dodjeljivanja previe restriktivna. Na primjer, to u potpunosti
onemoguava da se objekti tipa Drzac prenose po vrijednosti u funkcije ili da se vraaju kao rezultati
iz funkcija. Takoer, zabrana meusobnog dodjeljivanja zapravo onemoguava da se objektu tipa
Drzac ita dodijeli, tako da se objekat tipa Drzac od trenutka stvaranja pa do kraja svog ivota
uva stalno jedan te isti objekat, odnosno nemogue ga je preusmjeriti na neki drugi objekat (po ovom
pitanju, objekti tipa Drzac su srodni referencama).
Prije nego to razmotrimo kako moemo ukloniti ova ogranienja, trebamo uoiti da je pri radu sa
obinim pokazivaima upravo mogunost da pokaziva preusmjerimo na neki drugi objekat jedan od
estih razloga koji mogu dovesti do curenja memorije. Pogledajmo, na primjer, sljedei isjeak:
Student *s(new Student(...));
...
s = new Student(...);

// Curenje memorije je neizbjeno

Ovdje smo inicijalizirali pokaziva s da pokazuje na dinamiki alocirani objekat tipa Student.
Kasnije smo preusmjerili isti pokaziva da pokazuje na drugi dinamiki alocirani objekat tipa
Student, a prvi objekat nismo oslobodili. Kako vie niko ne pokazuje na prvi dinamiki alocirani
objekat, niko ga vie ne moe ni osloboditi, tako da je curenje memorije neizbjeno. To ne znai da e
preusmjeravanje nekog pokazivaa na drugi objekat a da pri tome dinamiki alocirani objekat na koji je
taj pokaziva prije pokazivao uvijek dovesti do curenja memorije. Nee, ukoliko se u meuvremenu
pojavi jo neki pokaziva koji pokazuje na isti objekat, kao recimo u sljedeem primjeru:
Student *s(new Student(...));
...
Student *s2(s);
...
s = new Student(...);

// Ne mora biti curenja memorije

U ovom primjeru ne mora doi do curenja memorije, jer u trenutku kada se pokaziva s preusmjeri na
drugi objekat, pokaziva s2 i dalje pokazuje na objekat na koji je prije toga pokazivao pokaziva s.
Naravno, do curenja memorije e doi ukoliko se taj objekat ne oslobodi prije nego to pokaziva s1
ne prestane ivjeti (osim ukoliko se u meuvremenu ne pojavi jo pokazivaa koji pokazuju na isti
objekat).
Pojava da vie pokazivaa pokazuje na isti objekat jedna je od manifestacija pojave u programiranju
poznate kao aliasing. Mada je oigledno da aliasing nekada moe pomoi da ne doe do curenja
memorije, aliasing esto moe uzrokovati greku oslobaanja ve osloboene memorije. Zaista, mnogi
naivno misle da je dovoljno nakon naredbe delete postaviti odgovarajui pokaziva na nulu da se
izbjegne ova greka. Meutim, ova taktika nita ne pomae ukoliko postoji aliasing. Razmotrimo, na
primjer, sljedei isjeak:
5

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

Student *s(new Student(...));


...
Student *s2(s);
...
delete s;
s = 0;
...
delete s2;
// Oslobaanje ve osloboene memorije!

Nakon to izvrimo naredbu delete s, oslobodili smo memoriju na koju pokazuje pokaziva s.
Meutim, kako na isto mjesto u memoriji pokazuje i pokaziva s2, on je nakon toga postao visei
pokaziva. Kasnije, kada se izvri naredba delete s2, imaemo oslobaanje ve osloboenog
prostora. Nita nam nije od koristi to smo upisali nulu u pokaziva s, kada se ta izmjena nee odraziti
na pokaziva s2 (koji je, naravno, neovisna varijabla od s).
Sada emo vidjeti kako jednim udarcem moemo rijeiti sve dosada opisane probleme. Kreiraemo
klasu slinu klasi Drzac koja e u sebi interno uvati pokaziva na neki objekat, ali e se primjerci te
klase smjeti slobodno kopirati, i moi e im se vriti dodjela (samim tim e se tokom ivota moi
preusmjeravati na razliite objekte, slino klasinim pokazivaima). Ovu klasu emo nazvati recimo
PametniPokazivac, a primjerke te klase zvaemo pametni pokazivai. Prilikom kreiranja nekog
pametnog pokazivaa automatski e se dinamiki kreirati i jedan broja, koji e se inicijalizirati na
jedinicu (ovom brojau e se pristupati preko drugog internog pokazivaa, koji je takoer pohranjen
unutar pametnog pokazivaa, tako da se pametni pokaziva interno sastoji od dva obina pokazivaa).
Uloga tog brojaa je da broji koliko je u posmatranom trenutku pametnih pokazivaa vezano na jedan te
isti objekat. Svaki put kada se jedan pametni pokaziva kopira u drugi, broja se poveava za jedinicu,
to se izvodi unutar konstruktora kopije (ovo je u sutini tehnika brojanja pristupa, o kojoj smo ve
govorili). to se tie destruktora, on prvo umanjuje broja za jedinicu. Ukoliko broja nije dostigao nulu,
nita se ne deava, s obzirom da je to znak da ima i drugih pametnih pokazivaa koji su vezani za isti
objekat. Tek ukoliko je broja dostigao nulu, vri se oslobaanje objekta na koji pokaziva pokazuje, jer
je to trenutak kada nestaje i posljednji pametni pokaziva koji je bio vezan na razmatrani objekat (istom
prilikom unitava se i broja, koji vie nije potreban). Malo je sloeniji operator dodjele, koji mora
uveati broja pristupa vezan za objekat koji se dodjeljuje, a umanjiti broja pristupa objekta kojem se
dodjeljuje (razlog nije teko uvidjeti) i izvriti eventualno oslobaanje resursa ukoliko je broja dostigao
nulu. Sve to zajedno izgleda recimo ovako:
template <typename Tip>
class PametniPokazivac {
Tip *pokazivac;
int *p_brojac;
void Test() const { if(!pokazivac) throw "NUL pokazivac!\n"; }
public:
PametniPokazivac(Tip *pokazivac = 0) : pokazivac(pokazivac),
p_brojac(new int(1)) {}
~PametniPokazivac() {
if(--(*p_brojac) == 0) { delete pokazivac; delete p_brojac; }
}
PametniPokazivac(const PametniPokazivac &izvorni) :
pokazivac(izvorni.pokazivac), p_brojac(izvorni.p_brojac) {
(*p_brojac)++;
}
PametniPokazivac &operator =(const PametniPokazivac &izvorni) {
(*izvorni.p_brojac)++;
if(--(*p_brojac) == 0) { delete pokazivac; delete p_brojac; }
pokazivac = izvorni.pokazivac; p_brojac = izvorni.p_brojac;
return *this;
}
Tip *operator ->() const { Test(); return pokazivac; }
Tip &operator *() const { Test(); return *pokazivac; }
};

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

Ovdje treba ukazati na jo neke sitne detalje u izvedbi. Prvo, primijetimo da konstruktor dozvoljava i
da se parametar izostavi, ime se pokaziva inicijalizira na nulu. Time se, u sutini, kreira pametni
nul-pokaziva, to moe biti korisno ukoliko na poetku ne znamo na ta pametni pokaziva treba da
pokazuje, nego emo to odluiti tek naknadno. Meutim, znamo da se nul-pokazivai ne smiju
dereferencirati. Kod obinih pokazivaa, rezultat dereferenciranja je nepredvidljiv ukoliko se radi o
nul-pokazivau. Ovdje smo izveli da se prilikom pokuaja derefenciranja (zapravo primjene operatora
*) ili primjene operatora -> baca izuzetak ukoliko se ustanovi da se radi o pametnom nul-pokazivau.
Takvo ponaanje je definitivno sigurnije od nepredvidljivog ponaanja (ovim se zapravo simulira
ponaanje kakvo imamo u Javi ili C#-u). Ovo je postignuto tako to se u operatorskim funkcijama za
preklapanje operatora * odnosno -> poziva jednostavna privatna funkcija Test koja testira
ispravnost pokazivaa i baca izuzetak ukoliko se radi o nul-pokazivau. Dalje, primijetimo da konstruktor
vie nije eksplicitan, to omoguava automatsku konverziju obinih (glupih) pokazivaa u odgovarajue
pametne pokazivae. Tako recimo postaje mogue direktno dodijeliti pametnom pokazivau neki obini
pokaziva, npr. adresu nekog objekta kreiranog pomou operatora new (tom prilikom e se posmatrani
obini pokaziva pretvoriti u odgovarajui pametni pokaziva prije nego to se izvri dodjela).
Pogledajmo sada ta smo do sada postigli. Sljedei isjeak koda stvarao je curenje memorije kada su se
koristili obini pokazivai, dok sada curenja memorije nema:
PametniPokazivac<Student> s(new Student(...));
...
s = new Student(...);
// Nema curenja memorija

Zaista, kada se s preusmjeri na drugi dinamiki alocirani objekat tipa Student, broja koji je bio
vezan za prvi dinamiki kreirani objekat pada na nulu, tako da se unutar operatora dodjele vri
oslobaanje memorije koju je zauzimao taj objekat. S druge strane, u sljedeem isjeku ne dolazi ni do
kakvog oslobaanja memorije (bar ne u okviru tog isjeka), ba kao to i treba:
PametniPokazivac<Student> *s(new Student(...));
...
PametniPokazivac<Student> *s2(s);
...
s = new Student(...);

Zaista, onog trenutka kada se pametni pokaziva s iskopira u pametni pokaziva s1, broja pristupa
prvom dinamiki alociranom objektu tipa Student poveava se na 2. Kada se kasnije s preusmjeri
na drugi dinamiki alocirani objekat tipa Student, broja pristupa prvom objektu smanjuje se na 1.
Kako on pri tome nije dostigao nulu, to je znak da jo nije dolo vrijeme da se taj objekat treba obrisati.
Primijetimo da smo rijeili i problem oslobaanja ve osloboenog prostora. Naime, na pametne
pokazivae nikada ne treba primijenjivati naredbu delete, s obzirom da se oni automatski brinu o
unitavanju objekta koji uvaju u sluaju kada on nije vie potreban. Zapravo, na pametne pokazivae
naredbu delete nije ni mogue primijeniti (pokuaj da to uradimo dovodi do sintaksne greke), s
obzirom da naredba delete oekuje kao parametar iskljuivo obini pokaziva (pametni pokazivai
zapravo strogo posmatrano uope nisu pokazivai nego primjerci jedne klase, koju dodue razvijamo
tako da se ti primjerci ponaaju vrlo slino poput obinih pokazivaa, samo malo pametnije).
Nema nikakvog razloga da pametne pokazivae ne smijemo stavljati u nizove ili vektore. Ba
naprotiv, to je veoma dobra ideja. Recimo, pogledajmo sljedei kod u kojem se koristi vektor obinih
pokazivaa na objekte tipa Student:
vector<Student*> studenti;
for(int i = 0; i < broj_studenata; i++) {
...
studenti.push_back(new Student(...));
}

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

Ovaj isjeak sam po sebi nije sporan, ali prije ili kasnije e neko sve ove dinamiki alocirane objekte tipa
Student morati i izbrisati. Drugim rijeima, prije ili kasnije u kodu e se morati nai i isjeak poput
sljedeeg:
for(int i = 0; i < broj_studenata; i++) delete studenti[i];

Ovo samo po sebi i ne bi bio toliki problem kada ne bi postojala opasnost da negdje doe do bacanja
izuzetka i da uslijed toga tok izvravanja programa uope ne doe do isjeka u kojem se vri brisanje.
Sve se to da srediti pomou try catch blokova, ali situacija moe postati vrlo zamrena. Znatno
je pametnije u vektoru umjesto obinih pokazivaa uvati pametne pokazivae, kao u sljedeem isjeku:
vector<PametniPokazivac<Student> > studenti;
for(int i = 0; i < broj_studenata; i++) {
...
studenti.push_back(new Student(...));
}

Sada, kada vektor studenti iz bilo kojeg razloga prestane postojati, prestaju postojati i objekti
pohranjeni u njemu. Tom prilikom se izvravaju destruktori tih objekata ako postoje. Kako pametni
pokazivai imaju destruktore, izvravanje njihovih destruktora dovee do automatskog oslobaanja
memorije koju su zauzimali dinamiki alocirani objekti tipa Student, bez potrebe da se mi o tome
runo brinemo. Najljepe od svega je to e se to dogoditi bez obzira na tok izvravanja programa,
odnosno bez obzira kako smo i kojim putem izali izvan opsega u kojem postoji vektor studenti.
Glavnina posla sada je iza nas. Ostaju jo neki detalji, oko kojih se dodue treba malo pomuiti.
Recimo, vidjeli smo da se, po potrebi, obini pokazivai automatski pretvaraju u pametne pokazivae.
ta je meutim sa obrnutom pretvorbom, tj. iz pametnih u obine (glupe) pokazivae? Zamislimo, na
primjer, da imamo neku funkciju koja prima kao parametar obini pokaziva na objekat tipa Student:
void NekaFunkcija(Student *pok_s) {
...
}

Pretpostavimo dalje da imamo pametni pokaziva na objekat tipa Student i da ga zbog nekog razloga
elimo proslijediti ovoj funkciji, kao u sljedeem isjeku:
PametniPokazivac<Student> s(new Student(...));
...
NekaFunkcija(s);

Ovaj isjeak naravno nee raditi, jer funkcija NekaFunkcija oekuje obini pokaziva. ta uiniti?
Jedna od mogunosti je u klasu PametniPokazivac dodati funkciju lanicu DajGlupiPokazivac
koja prosto vraa obini pokaziva na objekat pohranjen unutar pametnog pokazivaa, kao to je radila
funkcija lanica DajPokazivac u prvoj verziji klase Drzac. Tada bismo mogli pisati neto poput
PametniPokazivac<Student> s(new Student(...));
...
NekaFunkcija(s.DajGlupiPokazivac());

Meutim, interesantno je da moemo proi i bez ove funkcije. Naime, pretvorbu pametnog pokazivaa
s u odgovarajui glupi pokaziva moemo ostvariti konstrukcijom &*s. Zaista, dereferenciranjem
pametnog pokazivaa dobijamo objekat na koji on pokazuje, nakon ega uzimanje adrese tog objekta
daje obini pokaziva. Dakle, moemo pisati neto poput sljedeeg:
PametniPokazivac<Student> s(new Student(...));
...
NekaFunkcija(&*s);

Ovo dodue ne radi u sluaju ukoliko je s pametni nul-pokaziva, jer smo operator * izveli tako da
se pametni nul-pokaziva ne moe dereferencirati.
8

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

U naelu, nimalo ne bi bilo teko ostvariti i mogunost automatske konverzije pametnih pokazivaa
u obine pokazivae. Dovoljno bi bilo u klasi PametniPokaziva dodati odgovarajuu operatorsku
funkciju za pretvorbu, recimo ovako:
template <typename Tip>
class PametniPokazivac {
...
operator Tip*() { return pokazivac; }
};

Na ovaj nain bi se pametni pokazivai mogli koristiti u posve istim kontekstima kao i obini pokazivai.
Meutim, iskustvo pokazuje da bi od takve konverzije bilo vie tete nego koristi. Ba i jeste problem to
to bi se pametni pokazivai mogli koristiti u posve istim kontekstima kao i obini. Recimo, neko bi
nehotice mogao primijeniti naredbu delete na pametni pokaziva, to bi nakon omoguavanja ove
automatske konverzije postalo mogue. Tako neto bi definitivno dovelo do greke oslobaanja ve
osloboene memorije (prvi put bi se memorija oslobodila kada se izvri naredba delete, a drugi put
kada se izvri destrkutor odgovarajueg pametnog pokazivaa). Drugim rijeima, automatska konverzija
pametnih pokazivaa u glupe je isuvie opasna, pa je bolje ne omoguiti je. U sutini, trebamo se
truditi da pametni pokazivai to je god mogue vie lie (po nainu upotrebe) na obine, ali da ipak ne
lie previe. Zapravo, ako dizajniramo pametni pokaziva tako da u potpunosti oponaa sve osobine
obinih pokazivaa, ono to emo dobiti kao produkt je efektivno upravo obini glupi pokaziva, samo
upakovan u drugo pakovanje!
Interesantno je pitanje ako ve ne trebamo i ne moemo koristiti naredbu delete sa pametnim
pokazivaima, kako moemo forsirati uklanjanje nekog dinamikog objekta u sluaju kada znamo da
nam on vie nije potreban? Vrlo jednostavno, uinimo da pametni pokaziva na njega postane pametni
nul-pokaziva. Ukoliko je on prije toga bio jedini pametni pokaziva koji pokazuje na taj objekat,
oslobaanje objekta e biti odmah izvreno unutar operatora dodjele. Dakle, moemo uiniti ovo:
PametniPokazivac<Student> s(new Student(...));
...
s = 0;
// Objekat na koji ukazuje "s" se automatski dealocira

Sljedea stvar o kojoj se moramo pobrinuti tie se nasljeivanja. Znamo da obini pokaziva koji je
deklariran da pokazuje na neki objekat neke bazne klase moe pokazivati i na bilo koji objekat neke
klase koja je izvedena iz bazne klase. Na primjer, ukoliko je klasa DiplomiraniStudent izvedena iz
klase Student, sljedei isjeak koda je legalan (i ne samo da je legalan, nego je od kljunog znaaja
za realizaciju polimorfizma):
Student *s;
...
s = new DiplomiraniStudent(...);

Meutim, pokuaj da istu stvar uradimo sa pametnim pokazivaima jo uvijek nee raditi, dok ne
izvrimo neke intervencije u klasi PametniPokazivac. Drugim rijeima, u ovom trenutku sljedei
isjeak nee raditi (tanije, nee se moi kompajlirati):
PametniPokazivac<Student> s;
...
s = new DiplomiraniStudent(...);

Problem je to konstrukcija new DiplomiraniStudent(...) daje kao rezultat obini pokaziva


tipa DiplomiraniStudent * koji se dodue moe automatski pretvoriti u pametni pokaziva, ali
tipa PametniPokazivac<DiplomiraniStudent> to je naalost sasvim drugaiji tip od tipa
PametniPokazivac<Student>, tako da se dodjela ne moe izvriti (dvije razliite konkretne klase
izvedene iz iste generike klase su sasvim razliiti tipovi). ta da se radi? Najjednostavnije je dodati jo
jednu operatorsku funkciju lanicu za operator dodjele koja e omoguiti da se pametnom pokazivau na
neki tip dodijeli pametni pokaziva na neki drugi tip. Poto nemamo ideje koji bi sve to drugi tipovi
mogli biti, ova funkcija lanica e biti generika (da, i to je mogue), a njena implementacija bie
9

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

praktiki identina kao i obina operatorska funkcija za operator dodjele (da izbjegnemo dupliranje
koda, mogue je iz obine operatorske funkcije za operator dodjele pozvati generiku verziju, samo uz
eksplicitnu specifikaciju da se radi o istom tipu). Kako je potrebno podrati i mogunost da se pametni
pokaziva na neki bazni tip inicijalizira pametnim pokazivaem na neki tip izveden iz baznog tipa,
potrebno je dodati i jedan generiki konstruktor koji e podrati takvu inicijalizaciju, pri emu e
njegova implementacija biti praktino istovjetna kao kod konstruktora kopije (da, dobro ste uli, ak i
konstruktori mogu biti generiki). Time dobijamo sljedeu implementaciju:
template <typename Tip>
class PametniPokazivac {
template <typename DrugiTip>
friend class PametniPokazivac;
Tip *pokazivac;
int *p_brojac;
void Test() const { if(!pokazivac) throw "NUL pokazivac!\n"; }
public:
PametniPokazivac(Tip *pokazivac = 0) : pokazivac(pokazivac),
p_brojac(new int(1)) {}
~PametniPokazivac() {
if(--(*p_brojac) == 0) { delete pokazivac; delete p_brojac; }
}
template <typename DrugiTip>
PametniPokazivac(const PametniPokazivac<DrugiTip> &izvorni) :
pokazivac(izvorni.pokazivac), p_brojac(izvorni.p_brojac) {
(*p_brojac)++;
}
PametniPokazivac(const PametniPokazivac &izvorni) :
pokazivac(izvorni.pokazivac), p_brojac(izvorni.p_brojac) {
(*p_brojac)++;
}
template <typename DrugiTip>
PametniPokazivac &operator =(
const PametniPokazivac<DrugiTip> &izvorni) {
(*izvorni.p_brojac)++;
if(--(*p_brojac) == 0) { delete pokazivac; delete p_brojac; }
pokazivac = izvorni.pokazivac; p_brojac = izvorni.p_brojac;
return *this;
}
PametniPokazivac &operator =(const PametniPokazivac &izvorni) {
return operator =<Tip>(izvorni);
}
Tip *operator ->() const { Test(); return pokazivac; }
Tip &operator *() const { Test(); return *pokazivac; }
};

Primijetimo da smo unutar ove izvedbe morali proglasiti da je pametni pokaziva na neki drugi tip
prijateljska klasa pametnog pokazivaa koji upravo implementiramo. Na prvi pogled ovo djeluje udno,
ali bez toga implementacije generikog konstruktora i generikog operatora dodjele ne bi mogle
pristupiti privatnim atributima pametnog pokazivaa na neki drugi tip, jer je to u sutini posve druga
klasa. Isto tako, neko bi se mogao zapitati zato postoji i generika i negenerika verzija operatora
dodjele. Zar ona generika verzija nee uhvatiti i sluaj kada se pametnom pokazivau na neki tip
dodjeljuje drugi pametni pokaziva na isti tip. Trebalo bi da hoe, da nema jedne male zvrke. Naime,
kompajler kad vidi da nije implementiran klasini (negeneriki) operator dodjele, on automatski
generirira podrazumijevani operator dodjele koji prosto kopira atribute (i nita drugo), to definitivno
nije ono to elimo. Jedini nain da ovo sprijeimo je da zaista implementiramo i klasini operator
dodjele, bez obzira to bi generiki operator dodjele uradio pravu stvar, samo da mu kompajler prui
priliku da to uradi.
Neko bi se mogao uplaiti da e generiki konstruktor i generiki operator dodjele otvoriti brojne
rupe u sistemu, poput mogunosti da se pametnom pokazivau na neki tip dodijeli pametni pokaziva
na neki posve nekompatibilan tip (npr. da se pametnom pokazivau na tip Student dodijeli pametni
10

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

pokaziva na tip int). Sreom, ovo nee biti mogue. Greka e biti prijavljena onog trenutka kada se
unutar implementacije generikog konstruktora ili generikog operatora dodjele pokua izvriti kopiranje
obinog pokazivaa na neki tip u obini pokaziva na drugi nekompatibilni tip, ili se pokua obinom
pokazivau na neki tip dodijeliti obini pokaziva na drugi nekompatibilni tip.
Da li je ovim implementacija pametnog pokazivaa gotova? Jeste, ukoliko smo zadovoljni dosada
postignutim funkcionalnostima. Meutim, moe se primijetiti da jo uvijek nedostaju neke stvari koje
smo navikli raditi sa obinim pokazivaima. Recimo, u ovom trenutku uope ne moemo porediti da li
su dva pametna pokazivaa jednaka ili razliita, tj. da li pokazuju na isti objekat ili ne (bez da ih
pretvaramo u obine pokazivae). Ovo naravno nije dobro (to se tie poreenja po veliini, vidjeemo
uskoro da za pametne pokazivae ono i nema smisla). Na prvi pogled, izgleda da nije velika muka
implementirati preklopljene operatore == i != da podre ova poreenja. Meutim, to je znatno
munije nego to izgleda, zbog velikog broja kombinacija koje treba podrati. Na primjer, treba podrati
i poreenje pametnog pokazivaa na neki bazni tip sa pametnim pokazivaem na neki tip izveden iz
baznog tipa i obrnuto. Takoer, ima smisla podrati poreenje izmeu pametnih i obinih pokazivaa na
kompatibilne tipove, kao i poreenje pametnih pokazivaa sa nulom (kako bismo inae testirali da li je
pametni pokaziva u nekom trenutku nul-pokaziva). Sve ovo dovodi da treba napraviti vie verzija
operatorskih funkcija za operatore == i != (od kojih e neke biti generike), pri emu treba paziti da
ne doemo u situaciju da neke kombinacije budu dvosmislene. Konano, mnogi programeri su navikli da
koriste testove poput if(!p) ... odnosno if(p) ... da testiraju da li je (obini) pokaziva p
nul-pokaziva ili ne, pa bi bilo dobro podrati ovakve testove i za pametne pokazivae. Prva varijanta je
trivijalna za implementaciju (preklapanjem unarnog operatora !), dok je druga varijanta mnogo gora
nego to izgleda (oigledna izvedba preko automatske konverzije u tip bool ima toliko tetnih
nuspojava da je praktino neupotrebljiva). Ovdje je podrka za ovu varijantu izvedena koritenjem vrlo
prljavog trika, koji je u svojoj knjizi predloio jedan od najveih strunjaka za C++ Andrei
Alexandrescu. Detalje trika neemo objanjavati, a ideja se zasniva na automatskoj konverziji u
pokaziva na primjerak jedne pomone privatne klase (u nie prikazanoj implementaciji nazvane
Neobrisiva) koja je takva da se na odgovarajui pokaziva ne moe primijeniti naredba delete
(ime se zatvaraju neke rupe koje bi inae mogunost takve konverzije otvorila). Sve u svemu, naa
finalna implementacija klase PametniPokazivac izgledae ovako:
template <typename Tip>
class PametniPokazivac {
template <typename DrugiTip>
friend class PametniPokazivac;
Tip *pokazivac;
int *p_brojac;
void Test() const { if(!pokazivac) throw "NUL pokazivac!\n"; }
class Neobrisiva {
void operator delete(void *);
// Dio prljavog trika...
};
public:
PametniPokazivac(Tip *pokazivac = 0) : pokazivac(pokazivac),
p_brojac(new int(1)) {}
~PametniPokazivac() {
if(--(*p_brojac) == 0) { delete pokazivac; delete p_brojac; }
}
template <typename DrugiTip>
PametniPokazivac(const PametniPokazivac<DrugiTip> &izvorni) :
pokazivac(izvorni.pokazivac), p_brojac(izvorni.p_brojac) {
(*p_brojac)++;
}
PametniPokazivac(const PametniPokazivac &izvorni) :
pokazivac(izvorni.pokazivac), p_brojac(izvorni.p_brojac) {
(*p_brojac)++;
}
template <typename DrugiTip>
PametniPokazivac &operator =(
const PametniPokazivac<DrugiTip> &izvorni) {
(*izvorni.p_brojac)++;

11

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

if(--(*p_brojac) == 0) { delete pokazivac; delete p_brojac; }


pokazivac = izvorni.pokazivac; p_brojac = izvorni.p_brojac;
return *this;
}
PametniPokazivac &operator =(const PametniPokazivac &izvorni) {
return operator =<Tip>(izvorni);
}
Tip *operator ->() const { Test(); return pokazivac; }
Tip &operator *() const { Test(); return *pokazivac; }
bool operator !() const { return pokazivac == 0; }
friend bool operator ==(const PametniPokazivac &p1, const Tip *p2) {
return p1.pokazivac == p2;
}
friend bool operator ==(const Tip *p1, const PametniPokazivac &p2) {
return p1 == p2.pokazivac;
}
template <typename Tip2>
friend bool operator ==(const PametniPokazivac &p1,
const Tip2 *p2) {
return p1.pokazivac == p2;
}
template <typename Tip2>
friend bool operator ==(const Tip2 *p1,
const PametniPokazivac &p2) {
return p1 == p2.pokazivac;
}
template <typename Tip2>
bool operator ==(const PametniPokazivac<Tip2> &p2) const {
return pokazivac == p2.pokazivac;
}
friend bool operator !=(const PametniPokazivac &p1, const Tip *p2) {
return p1.pokazivac != p2;
}
friend bool operator !=(const Tip *p1, const PametniPokazivac &p2) {
return p1 != p2.pokazivac;
}
template <typename Tip2>
friend bool operator !=(const PametniPokazivac &p1,
const Tip2 *p2) {
return p1.pokazivac != p2;
}
template <typename Tip2>
friend bool operator !=(const Tip2 *p1,
const PametniPokazivac &p2) {
return p1 != p2.pokazivac;
}
template <typename Tip2>
bool operator !=(const PametniPokazivac<Tip2> &p2) const {
return pokazivac != p2.pokazivac;
}
operator Neobrisiva *() const {
if(!pokazivac) return 0;
// Prljavi trik koji omoguava da
static Neobrisiva pomocna;
// radi konstrukcija if(p) ... pri
return &pomocna;
// emu je p pametni pokaziva
}
};

Uz ovaj tekst, priloena je i gore prikazana implementacija izdvojena u posebnu programsku


datoteku pod imenom PametniPokazivac.h. Da biste koristili ovaj pametni pokaziva (tj. generiku
klasu PametniPokazivac), dovoljno je na poetak Vaeg programa staviti direktivu
#include "PametniPokazivac.h"

12

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

Nakon to smo se upustili u implementacione detalje, razmotrimo sada kako ove pametne
pokazivae treba, a kako ne treba koristiti. Moda je bolje rei kako ne treba. Naime, pametni pokazivai
se ne mogu koristiti prosto kao zamjena za obine pokazivae, svugdje i u svakom kontekstu. Tano
treba znati za ta pametni pokazivai jesu namijenjeni a za ta nisu. Greke nastale uslijed pogrene
upotrebe pametnih pokazivaa mogu biti veoma ozbiljne i potencijalno fatalne. esto se kae da ni
jedna implementacija pametnog pokazivaa ne moe biti onoliko pametna da sprijei sve mogue
gluposti koje programer moe nainiti (pametni pokazivai mogu biti prilino pametni, ali njihovi
korisnici mogu biti jo gluplji). Sretna je okolnost da su pravila za ispravnu upotrebu pametnih
pokazivaa relativno jednostavna i vrlo logina, tako da je veoma mala ansa da programer koji iole pazi
ta radi i koji iole razumije o emu se zapravo radi napravi neku od moguih greaka (osim jednog
izuzetka, koji emo ostaviti za kraj).
Na prvom mjestu, treba shvatiti ta je cilj pametnih pokazivaa. Njihova je svrha da uvaju adrese
dimaniki alociranih objekata i da se brinu o njihovom automatskom oslobaanju u onom trenutku kada
nema vie niko ko e se o njima brinuti. Posljedica toga je da pametnim pokazivaima smijemo
dodjeljivati iskljuivo adrese dinamiki alociranih objekata. Ukoliko pametnom pokazivau dodijelimo
adresu nekog objekta koji nije dinamiki alociran, sigurno emo imati problema. Naime, problem e
nastati onog trenutka kada se unutar destruktora ili operatora dodjele pokua izvriti oslobaanje objekta
na koji pametni pokaziva pokazuje, a on uope nije ni bio dinamiki alociran. Pokuaj oslobaanja
prostora koji nije uope ni bio zauzet uvijek ima nepredvidljive (i nikada dobre) posljedice. Recimo,
sljedei programski isjeak sigurno e stvoriti nevolje:
Student s(...);
...
PametniPokazivac<Student> *ps(&s);

Adrese objekata koji nisu dinamiki alocirani smiju se dodjeljivati iskljuivo obinim (glupim)
pokazivaima, to nije neko ogranienje, s obzirom da trajanje ivota takvih objekata svakako nije pod
kontrolom programera, nego je odreen pravilima jezika (npr. lokalni objekti prestaju ivjeti kada tok
programa napusti blok unutar kojeg su definirani). Ukoliko zbog nekog razoga izriito elimo nekom
objektu koji nije dinamiki alociran pristupati pomou pametnog pokazivaa, jedino to moemo uraditi
je kreirati novi dinamiki objekat koji je kopija postojeeg objekta, i onda tu kopiju predati pametnom
pokazivau na uvanje. Ovo je vrlo lako ostvariti recimo ovako:
Student s(...);
...
PametniPokazivac<Student> *ps(new Student(s));

Jedan od razloga zbog kojeg bi nam neto ovako trebalo je recimo u sluaju da imamo neku
kontejnersku strukturu podataka (recimo vektor), u kojem uvamo pametne pokazivae. Ti pametni
pokazivai po prirodi stvari trebaju uvati samo dinamiki alocirane objekte. Meutim, ukoliko u tu
kontejnersku strukturu treba upisati i neki objekat koji nije dinamiki kreiran, dinamiki emo nainiti
njegovu kopiju i nju upisati u kontejnersku strukturu, na nain analogan gore prikazanom. To bi moglo
izgledati ovako:
Student s(...);
// Statiki alocirani objekat tipa Student
...
vector<PametniPokazivac<Student> > studenti;
...
studenti.push_back(new Student(...)); // Novi dinamiki objekat
...
studenti.push_back(new student(s));
// Din. kopija postojeeg objekta

Druga stvar koju treba zapamtiti da pametni pokazivai koje smo ovdje razvili nisu namijenjeni za
uvanje dinamiki alociranih nizova, ve samo individualnih objekata. Drugim rijeima, sljedea
konstrukcija e uzrokovati probleme:
PametniPokazivac<int> *p(new int[100]);

13

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

Razlozi za ovo ogranienje su toliko banalni da je to pomalo i tuno. Naime, kada pametni
pokaziva zakljui da je vrijeme za oslobaanje objekta koji mu je povjeren na uvanje, on to izvodi
pomou naredbe delete (to se moe desiti na dva mjesta: unutar destruktora i unutar operatora
dodjele). Meutim, nizovi se ne smiju oslobaati sa delete nego sa delete []. To je manje-vie
jedini sutinski razlog. Trivijalno je napraviti modifikaciju generike klase PametniPokazivac koja
bi umjesto delete koristila delete [] (mogli bismo je zvati PametniPokazivacZaNizove) i
koja bi uvala dinamiki alocirane nizove (i iskljuivo njih). Meutim, nema velike potrebe na neim
takvim. Naime, u praktinim primjenama dinamika alokacija nizova se gotovo nikada ne koristi, nego
se umjesto toga koriste vektori, tj. primjerci generikog tipa vector (izuzetak su jedino kolski
primjeri za razumijevanje mehanizma dinamike alokacije memorije, te interne implementacije. Moe se
rei da za sluaj nizova vektori preuzimaju ulogu pametnih pokazivaa na nizove.
Sljedea stvar koju treba primijetiti je da za pametne pokazivae nismo definirali operatore poput
+, +=, ++ itd. Posljedica je da za ovako implementirane pametne pokazivae nije definirana
pokazivaka aritmetika. Meutim, ukoliko bolje razmislimo, vidjeemo da za njih pokazivaka
aritmetika zapravo i nema smisla. Pokazivaka aritmetika se sa (obinim) pokazivaima koristi samo u
sluaju pokazivaa koji pokazuju na neki element niza, a nai pametni pokazivai uvijek pokazuju
iskljuivo na dinamiki alocirane individualne objekte (koji pri tome nisu nizovi). U vezi sa tim, nije na
odmet napomenuti jednu injenicu koja je est izvor konfuzije u vezi sa klasinim pokazivaima u
jezicima C i C++. Naime, u ovim jezicima pokazivai se, zavisno od konkretne primjene, koriste na dva
fundamentalno razliita naina, koji uz to nisu ni meusobno kompatibilni. Jedan nain primjene
pokazivaa je da pomou njih klizimo kroz neki niz podataka (koristei pri tome pokazivaku
aritmetiku), i na taj nain efikasno vrimo pristup podacima na koje oni u tom trenutku pokazuju. Takav
nain upotrebe pokazivaa nazivamo iteratorska upotreba, s obzirom da se tada pokazivai ponaaju kao
neka (primitivna) vrsta iteratora (objekata za kretanje kroz neku kontejnersku strukturu podataka).
Recimo, u sljedeem primjeru, pokaziva p se koristi na iteratorski nain:
int a[10];
...
for(int *p = a; p < a + 10; p++) cout << *p << endl;

S druge strane, pokazivai se koriste i kao objekti pomou kojih se moe izvriti indirektni pristup
nekom drugom objektu. Ukoliko taj drugi objekat ima svoje ime, pokaziva na neki nain tad ima ulogu
nadimka (engl. moniker) za taj objekat, stoga se taj nain upotrebe pokazivaa naziva monikerska
upotreba. Susree se i naziv referencijska upotreba, s obzirom da pokaziva tada upuuje (referira) na
neki drugi objekat. Ovaj nain upotrebe pokazivaa je posebno rasprostranjen u jeziku C, dok se u jeziku
C++ u mnogim sluajevima za slinu namjenu mogu koristiti jednostavniji, ali ogranieniji objekti
(naravno, to su reference). U sluaju kada je taj objekat kojem se indirektno pristupa dinamiki alociran,
pokazivai (ili reference, ali znatno rjee) su tada i jedini nain da se takvom objektu pristupi.
Nevolja je to su iteratorska i monikerska (referncijska) upotreba pokazivaa u svojoj osnovi
meusobno nesaglasni. Razmotrimo recimo sljedei programski isjeak:
int *a(new int[10]);
...
for(int i = 0; i < 10; i++) cout << *a++ << endl;
delete [] a;

// a kao moniker
// a kao iterator
// Evo nevolje...

Ovdje je dinamiki alociran jedan niz od 10 cijelih brojeva i pokaziva a je inicijaliziran da pokazuje
na njegov poetak. Dalji pristup ovom nizu moe se vriti jedino preko tog pokazivaa. Dakle, ovdje se
a koristi kao pristupnik (moniker). Meutim, neto kasnije taj isti pokaziva a se koristi za kretanje
kroz isti niz (primjenom pokazivake aritmetike) sa ciljem pristupa uzastopnim elementima niza. Ovo je
tipino koritenje pokazivaa kao iteratora. Problem sa ovakvim mijeanjem naina upotrebe je u tome
to smo time izgubili adresu poetka niza (koju vie niko ne uva). Ukoliko kasnije izvrimo naredbu
delete (tanije delete []) nad takvim pokazivaem, nevolje su neizbjene, s obzirom da
pokaziva vie ne pokazuje na mjesto gdje se zaista nalazi alocirani objekat (niz). Neko bi se mogao
snai i prije brisanja runo korigirati sadraj pokazivaa a tako da ponovo pokazuje na pravo
mjesto, kao recimo u sljedeem isjeku:
14

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

int *a(new int[10]);


...
for(int i = 0; i < 10; i++) cout << *a++ << endl;
a -= 10;
delete [] a;

Poneko sa prilinom dozom mate i hrabrosti mogao bi doi na ideju i da napie neto poput sljedeeg (s
obzirom da parametar naredbi delete i delete [] prihvataju i izraze):
int *a(new int[10]);
...
for(int i = 0; i < 10; i++) cout << *a++ << endl;
delete [] a - 10;

Neosporna je injenica da ovakva rjeenja rade i da su ak legalna i ispravna. Meutim, isto tako je
neosporna injenica da su ona runa i da prosto prizivaju nevolje (mala nepanja je dovoljna da se
izazove totalni haos). Pedantno rjeenje sastoji se u tome da potpuno razdvojimo monikerski i iteratorski
nain upotrebe i da koristimo dva odvojena pokazivaa (jedan za jednu, a drugi za drugu namjenu), kao
recimo u sljedeem rjeenju (u kojem se a koristi kao moniker, a p kao iterator):
int *a(new int[10]);
...
int *p(a);
for(int i = 0; i < 10; i++) cout << *p++ << endl;
delete [] a;

U sutini, kada smo ovako razdvojili upotrebe ova dva pokazivaa, nestaje i potreba za posebnom
indeksnom promjenljivom i, kao recimo u sljedeem rjeenju:
int *a(new int[10]);
...
int *p(a);
for(int *p = a; p < a + 10; p++) cout << *p << endl;
delete [] a;

Pouka koju treba izvui je da isti pokaziva nikada ne treba koristiti i kao iterator i kao moniker. Sa
pametnim pokazivaima ne moemo izazvati ovaj problem, s obzirom da se oni svakako koriste samo
kao monikeri. Mada to na prvi pogled izgleda kao ogranienje, sada vidimo da je to zapravo dobro.
injenica da se pametni pokazivai ne mogu koristiti kao iteratori, rjeava se vrlo jednostavno. Za tu
svrhu treba koristiti obine pokazivae. Ili jo bolje, prave iteratore (u sluaju da umjesto niza koristimo
generiki tip vector, to je svakako preporueno rjeenje).
Sada emo ukazati na jo jedan potencijalni problem koji moe nastati pri upotrebi pametnih
pokazivaa. Dodue, malo je vjerovatno da e se ovaj problem pojaviti kod prosjenih programera, ve
uglavnom kod onih hrabrijih i sklonijih eksperimentiranju, ali ipak treba ukazati na njega. Naime, ve je
reeno da se po potrebi iz pametnog pokazivaa moe izvui obini (glupi) pokaziva, dok se obini
pokaziva svakako automatski pretvara u pametni u sluaju potrebe. Neko bi mogao doi na (glupu)
ideju da privremeno sauva sadraj pametnog pokazivaa u obinom pokazivau prije neke njegove
izmjene, a da kasnije, kada mu ponovo zatreba stari sadraj pametnog pokazivaa, obnovi sauvanu
vrijednost iz obinog pokazivaa, kao recimo u sljedeem isjeku:
PametniPokazivac<Student> s(new Student(...));
...
Student *glupi_pok(&*s);
// (glupi) pokuaj da se stanje s-a sauva
s = new Student(...);
// preusmjeravanje s na drugi objekat
...
s = glupi_pok;
// (glupi) pokuaj obnove s-a

Problem je u injenici da glupi pokaziva ne moe u sebi da sauva svu pamet koju pametni
pokaziva posjeduje (preciznije, informacija o broju pokazivaa koji pokazuju na referisani objekat se
gubi). Stoga, kada kasnije pokuavamo obnoviti sadraj pametnog pokazivaa iz glupog, pametni
15

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

pokaziva nema drugog izbora nego da kreira novi broja pristupa (inicijaliziran na 1), to se deava
unutar konstruktora pametnog pokazivaa (pri automatskoj konverziji glupog u pametni pokaziva).
Ovo vrlo vjerovatno nije stanje kakvo je bilo prije ovih vratolomija, tako da su kasniji problemi takoer
vrlo vjerovatni. Dodue, nije mnogo vjerovatno da e neko napraviti greku ovakve vrste, ali nije ni
posve nemogue. Vjerovatnoa nehotinog pravljenja greaka ovakve vrste bila bi mnogo vea da je jo
podrana i automatska konverzija pametnih pokazivaa u obine pokazivae, to je razlog vie zbog
kojeg nismo eljeli podrati ovakvu konverziju.
Nije na odmet naglasiti jo jednu stvar. Pedantni programeri su navikli koristiti pokazivae na
konstantne objekte sa ciljem da sprijee nehotinu izmjenu sadraja nekog objekta u sluajevima kada to
nije bila namjera. Na primjer, deklaracija poput
const Student *s(new Student(...));

definira (obini) pokaziva s koji pokazuje na konstantni objekat tipa Student, u smislu da e
svaki pokuaj izmjene objekta na koji s pokazuje (npr. pozivom neke metode mutatora) biti sprijeen
od strane kompajlera. Kako postii isti efekat koristei pametne pokazivae? Naivni pokuaj da to
uradimo izgleda ovako:
const PametniPokaziva<Student> s(new Student(...));

Ovo sintaksno prolazi, ali ne daje eljeni efekat. Ovo definira konstantni pametni pokaziva koji pokazuje
na neki objekat tipa Student (o ijoj se konstantnosti nita ne govori). Dakle, sam pokaziva je
konstantan (u smislu da mu se sadraj ne moe promijeniti, npr. ne moe se preusmjeriti na neki drugi
objekat), a ne objekat na koji on pokazuje. Mi elimo da konstantan bude upravo objekat na koji on
pokazuje, a ne sam pokaziva. Korektna deklaracija kojom postiemo eljeni efekat izgleda ovako:
PametniPokaziva<const Student> s(new Student(...));

Klasa PametniPokazivac koju smo razvili razvijena je za kolske i poluprofesionalne primjene,


tako da su namjerno izostavljene neke stvari koje bi znatno uslonile implementaciju, a koje mogu biti
potrebne u profesionalnim primjenama (tako uslonjena implementacija se vie ne bi mogla objasniti
studentima koji sluaju kurs Tehnike programiranja, jer bi se oslanjala na tehnike koje znatno izlaze
izvan okvira ovog kursa). Recimo, ovdje prikazana implementacija pametnog pokazivaa nije sigurna za
rad u tzv. vienitnom (engl. multithread) okruenju, u kojem je mogue da se vie funkcija izvrava
prividno istovremeno (u paraleli). Za one kojima treba vie, treba znati da Boost kolekcija dodatnih
biblioteka za programski jezik C++ (moe se nai na web stranici www.boost.org) sadri generiki
tip podataka nazvan shared_ptr koji je po osnovnim idejama praktino identian ovdje razvijenom
tipu PametniPokazivac, samo to ima jo nekih dodatnih mogunosti (recimo, u stanju je upravljati
i drugim raunarskim resursima a ne samo memorijom) i to nema problema sa vienitnim radom. U
planu je da se tip shared_ptr ukljui kao standardni tip podataka u sljedeu reviziju jezika C++.
Iz dosada izloenog moglo bi se zakljuiti da uz upotrebu pametnih pokazivaa, pod uvjetom da se
koriste na ispravan nain (to, kako to smo vidjeli, i nije osobito teko), problemi sa curenjem memorije
postaju prolost. Dovoljno je na za uvanje adresa dinamikih kreiranih objekata (i samo njih) na svim
mjestima umjesto obinih pokazivaa koristiti pametne pokazivae (bilo ove koje smo upravo razvili,
bilo neke profesionalnije poput shared_ptr), zar ne? Naalost, ne. Vrijeme je za malo otrenjenje.
Sa pametnim pokazivaima, ak i uz konzistentnu upotrebu ipak je mogue napraviti curenje memorije
(zbog toga smo i rekli da oni rjeavaju skoro sve probleme vezane za curenje memorije, ali ne sve).
Problem koji emo opisati objanjava zbog ega pametni pokazivai ipak ne mogu u potpunosti
zamijeniti sakupljae smea (iako su po mnogo emu bolji, fleksibilniji i efikasniji od njih). Problem
koji emo opisati poznat je kao kruno referenciranje (engl. circular reference) i on je Ahilova peta svih
pametnih pokazivaa. Objasniemo ovaj problem prvo na jednom posve jednostavnom primjeru, a zatim
emo dati generalizaciju. Pretpostavimo da imamo neku strukturu ili klasu koja neki od svojih atributa
sadri pokaziva na neki drugi primjerak iste strukture ili klase (kao to znamo, takve strukture odnosno
klase nazivaju se vorovi). Djeluje mudra ideja da se obini pokaziva unutar vora zamijeni pametnim
pokazivaem (u osnovi, to i jeste mudra ideja). Na primjer, pretpostavimo da nam je data sljedea
struktura:

16

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

struct Cvor {
int vrijednost;
PametniPokazivac<Cvor> veza;
};

Razmotrimo sada sljedei isjeak programa, koji djeluje posve bezazleno:


PametniPokazivac<Cvor> c1(new Cvor);
PametniPokazivac<Cvor> c2(new Cvor);
c1->veza = c2;
c2->veza = c1;

Ovdje smo dinamiki kreirali dva vora (tj. objekta tipa Cvor) i inicijalizirali pametne pokazivae
c1 i c2 da pokazuju na njih. Zatim smo atribut veza u prvom od njih postavili da pokazuje na
onaj drugi, a atribut veza u drugom od njih da pokazuje na onaj prvi. Dakle, uspostavili smo neku
vrstu ciklike (krune) veze izmeu dva dinamiki alocirana objekta. U ovom trenutku, brojai pristupa
za oba dinamiki alocirana objekta imaju vrijednost 2, jer na svaki njih pokazuju po dva pametna
pokazivaa (jedan od njih je c1 odnosno c2, a drugi je onaj pametni pokaziva u atributu veza
koji se nalazi u onom drugom od dva vora u odnosu na vor koji razmatramo). Pretpostavimo sada da
pametni pokazivai c1 i c2 prestanu postojati (npr. kada doemo do kraja bloka u kojem su
definirani). Njihovi destruktori se izvravaju i umanjuju brojae pristupa za 1. Meutim, ti brojai e
sada imati vrijednost 1, tako da dinamiki kreirani objekti nee biti osloboeni, iako im se nakon toga
vie ne moe pristupiti. Iako su nestali jedini pametni pokazivai pomou kojih se ovim vorovima moe
pristupiti, ovi vorovi se i dalje meusobno uvaju (putem pametnih pokazivaa koji su pohranjeni u
njima) i na taj nain spreavaju da budu uklonjeni. Ukoliko se ova kruna veza ne raskine prije nego to
pametni pokazivai c1 i c2 prestanu ivjeti (moe se raskinuti preusmjeravanjem neke od veza
izmeu vorova, recimo dodjelom poput c1->veza = 0), curenje memorije je neizbjeno. Naime, u
memoriji e ostati dva objekta koji, iako im niko vie ne moe pristupiti, na neki nain meusobno
pokazuju jedan na drugog i tako spreavaju svoje vlastito unitenje...
Ovaj primjer je vjerovatno najjednostavniji primjer koji ilustrira ta se moe desiti. Meutim,
situacija moe biti znatno sloenija. Moemo, na primjer, imati itav lanac vorova od kojih svaki sadri
pametni pokaziva koji pokazuje na sljedei vor, osim posljednjeg vora, iji pametni pokaziva
pokazuje nazad na prvi vor (ovo se koristi kada se implementiraju recimo krune povezane liste u
kojima se iza posljednjeg elementa ponovo nalazi prvi element, itd.). Ukoliko se takva kruna veza ne
raskine prije kraja ivota posljednjeg pametnog pokazivaa pomou kojeg se moe pristupiti takvom
lancu, curenje memorije je neizbjeno. Problem moe nastati i u situaciji kada imamo samo jedan vor.
Razmotromo recimo sljedei isjeak:
PametniPokazivac<Cvor> c(new Cvor);
c->veza = c;

Atribut veza u novokreiranom voru pokazuje na taj isti vor. Kada pametni pokaziva c prestane
postojati, dinamiki kreirani vor nee biti uniten, jer sadri u sebi pametni pokaziva koji uva upravo
njega (tj. vor u kojem se nalazi).
Problemi ove vrste mogu nastati i kada je u igri vie pametnih pokazivaa u istom voru. Na primjer,
kod implementacije dvostruko povezanih listi svaki vor sadri dva pokazivaa, na prethodni i na
sljedei vor (pri emu 0 tipino oznaava da prethodnog odnosno sljedeeg vora nema):
struct Cvor {
int vrijednost;
PametniPokazivac<Cvor> prethodni, sljedei;
};

Razmotrimo sada sljedei isjeak koji kreira dvostruko povezanu listu koja se sastoji od samo dva
meusobno povezana vora:

17

Dr. eljko Juri: Tehnike programiranja /kroz programski jezik C++/


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Dodatak predavanjima:
Pametni pokazivai

PametniPokazivac<Cvor> c1(new Cvor);


PametniPokazivac<Cvor> c2(new Cvor);
c1->prethodni = 0;
c1->sljedeci = c1;
c2->prethodni = c1;
c2->sljedeci = 0;

Ovdje smo opet napravili ciklus u koji su upetljani pametni pokazivai. Prvi vor sadri pametni
pokaziva sljedeci koji pokazuje na drugi vor dok drugi vor sadri pametni pokaziva
prethodni koji pokazuje na prvi vor. Kada c1 i c2 prestanu postojati, svaki od ova dva vora
spreava da onaj drugi bude uniten, analogno kao u prethodnom primjeru. to je najgore, ovakve
konstrukcije su nam neophodne ukoliko elimo kreirati dvostruko povezanu listu.
Opisani primjeri se mogu generalizirati. Do problema oito moe doi kada god imamo strukture ili
klase koje sadre pametne pokazivae na druge strukture i klase (to uope ne moraju biti primjerci istih
struktura i klasa kao to je to sluaj kod vorova) ukoliko se izmeu njih uspostavi neka vrsta krunog
odnosa. Formalni opis problema moemo iskazati ovako. Nacrtajmo jedan graf u kojem vorovi grafa
predstavljaju pametne pokazivae ili objekte koji u sebi sadre pametne pokazivae i u kojem strelica
(grana) izmeu vorova X i Y postoji ako i samo ako X pokazuje na Y (ako je X pametni pokaziva) ili
ako X u sebi sadri pametni pokaziva koji pokazuje Y. Problem krunog referenciranja nastaje ako i
samo ako tako kreirani graf u sebi sadri kruni put (ciklus odnosno konturu).
ta da se radi? Do sada niko nije uspio kreirati pametne pokazivae koji automatski mogu rijeiti
problem krunog referenciranja a da pri tome ne doe do drastinog pada perfomansi. Slijedi da ovakve
probleme moramo rjeavati runo. Postoje samo dva naina za rjeavanje ovog problema. Prvi nain je
dopustiti kreiranje krunih veza, ali koje obavezno moraju biti runo raskinute prije nego nestane
posljednji pametni pokaziva koji pokazuje na neki objekat unutar zaaranog kruga. U nekim
situacijama to ba nije praktino (recimo, kod dvostruko povezanih listi postoji mnotvo takvih
zaaranih krugova, pri emu su u svakom krugu samo 2 vora). Drugi nain je uope ne dozvoliti da
doe do uspostavljanja krunih veza. Poto u stvaranju krunih veza uestvuju samo pametni pokazivai,
najjednostavniji nain spreavanja uspostavljanja krunih veza je na neko kritino mjesto unutar ciklusa
umjesto pametnog pokazivaa upotrijebiti obini pokaziva. Recimo, kod kreiranja dvostruko povezanih
listi jedno od moguih rjeenja je da se za vezu unaprijed iskoristi pametni pokaziva, ali da se za vezu
unazad koriste obini (glupi) pokazivai. Oba naina zahtijevaju potpunu svjesnost programera o
problemu i odreenu disciplinu za njegovo rjeavanje. Ovo pokazuje da se pametni pokazivai ne smiju
koristiti posve rutinski i mehaniki, bez imalo razmiljanja. U tom smislu su sakupljai smea ipak
jednostavniji za upotrebu (mada su manje efikasni i fleksibilni), ali i pametni pokazivai svakako
predstavljaju ogromnu pomo u programiranju, a trae samo minimum discipline i razmiljanja.
Ovim smo doli do kraja nae prie o pametnim pokazivaima. S obzirom da je predmetni nastavnik
uloio veliki trud u pripremi ovog materijala (a nije bio duan da to uradi), stalo mu je da vidi da li je i
koliko je ovaj tekst probudio interesovanje studenata. Stoga predmetni nastavnik moli sve studente koji
su proitali ovaj tekst do kraja da poalju predmetnom nastavniku email na adresu zjuric@utic.net.ba sa
kratkim sadrajem Proitao(la) sam tekst o pametnim pokazivaima (ne treba nita vie). Oni studenti
koji su ne samo proitali tekst nego su i u potpunosti razumili koncepte izloene u tekstu neka poalju
poruku Proitao(la) sam tekst o pametnim pokazivaima i razumio(la) sam ga u potpunosti. Naravno,
nikakva nagrada ne slijedi onima koji poalju ovaj email niti ikakva sankcija onima koji ga ne poalju,
ali tek da se zna...

18

You might also like