You are on page 1of 13

Dr.

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


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_b
Akademska godina 2013/14

Predavanje 12_b
Sve do sada, operatorske funkcije smo definirali kao obine funkcije, koje nisu funkcije lanice
klase. Meutim, operatorske funkcije se mogu definirati i kao funkcije lanice klase. U tom sluaju,
interpretacija operatora se neznatno mijenja. Naime, neka je neki binarni operator. Ve smo vidjeli
da se izraz x y interpretira kao operator (x, y) u sluaju da je odgovarajua operatorska
funkcija definirana kao obina funkcija. Meutim, ukoliko istu operatorsku funkciju definiramo kao
funkciju lanicu, tada se izraz x y interpretira kao x.operator (y), odnosno uzima se da
operatorska funkcija djeluje nad prvim operandom, a drugi operand prihvata kao parametar. Slino,
ukoliko je neki unarni operator, tada se izraz x interpretira kao operator (x) u sluaju da
je odgovarajua operatorska funkcija definirana kao obina funkcija, a kao x.operator () u
sluaju da je definirana kao funkcija lanica. Za sluaj operatora ++ odnosno , za njihove
prefiksne verzije vrijedi isto to i za sve ostale unarne operatore, dok se njihove postfiksne verzije u
sluaju da su odgovarajue operatorske funkcije definirane kao funkcije lanice interpretiraju kao
x.operator ++(0) odnosno x.operator (0) (tj. sa fiktivnim cjelobrojnim argumentom).
Pogledajmo kako bi mogle izgledati definicije operatorskih funkcija za binarni operator + i unarni
minus za klasu Vektor3d ukoliko bismo se odluili da ih implementiramo kao funkcije lanice klase.
Za tu svrhu bismo morali neznatno izmijeniti deklaraciju same klase, da dodamo odgovarajue
prototipove (ili ak i kompletne definicije) za operatorske funkcije lanice:
class Vektor3d {
double x, y, z;
public:
Vektor3d(double x, double y, double z) : x(x), y(y), z(z) {}
double DajX() const { return x; }
double DajY() const { return y; }
double DajZ() const { return z; }
Vektor3d operator +(const Vektor3d &v) const;
Vektor3d operator () const { return {-x, -y, -z}; }
};

Operatorsku funkciju lanicu za unarni operator smo implementirali odmah unutar deklaracije
klase, radi njene kratkoe. Ni definicija operatorske funkcije za binarni operator + nije mnogo dua, ali
smo se ipak odluili da je implementiramo izvan deklaracije klase, sa ciljem da uoimo kako treba
izgledati zaglavlje operatorske funkcije lanice klase u sluaju kada se ona implementira izvan klase
(primijetimo da prva pojava rijei Vektor3d oznaava povratni tip funkcije, dok druga pojava rijei
Vektor3d zajedno sa operatorom :: oznaava da se radi o funkciji lanici klase Vektor3d):
Vektor3d Vektor3d::operator +(const Vektor3d &v) const {
return {x + v.x, y + v.y, z + v.z};
}

Pri susretu sa ovako definiranom operatorskom funkcijom poetnici se u prvi mah zbune na ta se
odnose imena atributa poput x, y i z koja su upotrijebljena samostalno, bez operatora . koji
oznaava pripadnost konkretnom objektu. Meutim, s obzirom da se radi o funkciji lanici, ova imena se
odnose na imena onog konkretnog objekta nad kojim je metoda primijenjena. Na primjer, izraz
v1 + v2 gdje su v1 i v2 vektori, bie interpretiran kao v1.operator +(v2), tako da e se
x, y i z odnositi na atribute objekta v1.
Obino je stvar programera da li e neku operatorsku funkciju definirati kao obinu funkciju ili kao
funkciju lanicu. Meutim, izmeu ova dva naina ipak postoje izvjesne razlike. Da bismo uoili ovu
razliku, podsjetimo se da se izraz poput x y, gdje je neki binarni operator, interpretira kao
operator (x, y) ili kao x.operator (y), u ovisnosti da li je odgovarajua operatorska
funkcija obina funkcija ili funkcija lanica klase. Razmotrimo sada binarni operator + koji smo
definirali u klasi Kompleksni. Vidjeli smo da su zahvaljujui postojanju konstruktora sa jednim
parametrom i automatskoj pretvorbi tipova koja se ostvaruje konstruktorom, izrazi poput a + 3 i
3 + a gdje je a objekat tipa Kompleksni potpuno legalni. Pretpostavimo sada da smo umjesto
1

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


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_b
Akademska godina 2013/14

kao obinu funkciju, operatorsku funkciju za operator + definirali kao funkciju lanicu klase
Kompleksni. U tom sluaju bi se dva prethodna izraza interpretirala kao a.operator +(3) i
3.operator +(a). Prvi izraz bi i dalje bio legalan (jer bi se on, zahvaljujui automatskoj pretvorbi
tipova pri prenosu parametara u funkcije, dalje interpretirao kao a.operator +(Kompleksni(3))),
dok bi drugi izraz bio ilegalan, jer 3 nije objekat nad kojim bi se mogla primijeniti neka metoda.
Drugim rijeima, izraz 3 + a postao bi ilegalan mada bi izraz a + 3 i dalje bio legalan!
Generalno, kada se god neki binarni operator definira pomou operatorske funkcije koja je funkcija
lanica klase, tada se na prvi operand ne vre nikakve automatske pretvorbe tipova, ime se uvodi
izrazita asimetrija u nain kako se tretiraju lijevi i desni operand (to je vidljivo ve i iz injenice da se
operatorska funkcija tad izvodi nad lijevim operandom, dok se desni operand prenosi kao parametar).
Stoga, ukoliko nam ova asimetrija nije poeljna, operatorsku funkciju treba definirati kao obinu
funkciju, dok u sluaju da nam ova asimetrija odgovara, tada je bolje definirati operatorsku funkciju kao
funkciju lanicu. Kako za proste ugraene tipove veina binarnih operatora poput +, , *, /,
%, <, <=, >, >=, ==, !=, &, ^, |, && i || tretira oba svoja operanda
ravnopravno, ove operatore je bolje preklapati operatorskim funkcijama definiranim kao obine funkcije
(tipino prijatelje razmatrane klase). S druge strane, neki od operatora kao to su =, +=, =, *=,
/=, %=, &=, ^=, |=, <<=, >>= su sami po sebi izrazito asimetrini, zbog toga to
modificiraju svoj lijevi operand (koji zbog toga mora biti l-vrijednost) a ne kvare sadraj svog desnog
operanda. Zbog toga je ove operatore mnogo bolje preklapati operatorskim funkcijama koje su lanice
odgovarajue klase (napomenimo da za operator = uope nemamo izbora odgovarajua operatorska
funkcija mora biti lanica klase).
Ovo se, na izvjestan nain, sasvim uklapa u opu filozofiju objektno zasnovanog programiranja o
emu smo ranije govorili. Naime, izraz poput v1 += v2 se na izvjestan nain moe posmatrati kao
nareenje upueno objektu v1 da izmijeni svoj sadraj nadodavanjem sadraja objekta v2 na sebe,
odnosno kao poziv neke mutatorske funkcije lanice nad objektom v1. Stoga bi bilo posve prirodno da
se ovaj izraz interpretira kao v1.operator +=(v2) gdje je operator += odgovarajua funkcija
lanica. S druge strane, izraz poput v1 + v2 nije prirodno posmatrati kao nareenje niti jednom od
objekata v1 i v2, nego kao nalog za kreiranje posve novog objekta koji je jednak zbiru objekata
v1 i v2. Stoga bi bilo prirodno da se ovaj operator interpretira kao operator +(v1, v2) gdje je
operator + odgovarajua obina funkcija koja obavlja kreiranje novog objekta. U sutini, operatori
+= i + u klasi Vektor3d su operatorski pandan funkcija SaberiSa i ZbirVektora koje
smo ranije pisali za istu klasu, pri emu smo takoer zakljuili da je prirodnije da se funkcionalnost koju
obavlja funkcija SaberiSa izvodi putem funkcije lanice, dok se funkcionalnost koju obavlja funkcija
ZbirVektora prirodnije obavlja putem obine funkcije.
Pogledajmo kako bi mogla izgledati definicija operatorske funkcije za operator += za klasu
Vektor3d izvedene kao funkcija lanica (pri tome pretpostavljamo da smo u deklaraciji klase
Vektor3d dodali odgovarajui prototip):
Vektor3d &Vektor3d::operator +=(const Vektor3d &v) {
x += v.x; y += v.y; z += v.z;
return *this;
}

Interesantno je razmotriti posljednju naredbu ove funkcije. Podsjetimo se da bi izraz poput


v1 += v2 trebao da vrati kao rezultat modificiranu vrijednost objekta v1 (da bi konstrukcije poput
v3 = v1 += v2 bile mogue). Meutim, kako se ovaj izraz interpretira kao v1.operator +=(v2),
on bi trebao da vrati kao rezultat modificiranu vrijednost objekta nad kojim je operatorska funkcija
pozvana, koji je mogue dohvatiti jedino preko pokazivaa this. Zapravo, sa ovakvom situacijom
smo se susreli i ranije, u jednoj od ranijih definicija klase Vektor3d, u kojoj smo imali funkciju
lanicu SaberiSa, u kojoj smo koristili istu tehniku. U sutini, maloas smo rekli da operatorska
funkcija za operator += nije nita drugo nego preruena verzija metode SaberiSa.
S obzirom da ve imamo definiranu operatorsku funkciju za operator +, mnogi e doi u napast da
operatorsku funkciju za operator +=napiu krae kao

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


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_b
Akademska godina 2013/14

Vektor3d &Vektor3d::operator +=(const Vektor3d &v) {


return *this = *this + v;
}
// Radi, ali nije dobra ideja!!!

Meutim, ovakva izvedba se nikako ne moe preporuiti zbog neefikasnosti (mnotvo bespotrebnih
stvaranja objekata, kopiranja i unitavanja), o emu smo ve raspravljali. Ukoliko elimo iskoristiti
zajedniki kd i na jednostavan nain odrati konzistenciju izmeu definicija operatora + i +=, ve
smo rekli da je mnogo bolje operator + definirati preko operatora += nego obrnuto, to moemo
uraditi na jedan od ranije opisanih naina (nema nikakve smetnje to je ovaj put operatorska funkcija za
operator += funkcija lanica). Meutim, u sluaju kada je operatorska funkcija za operator +=
funkcija lanica, mogua je jo jedna vrlo kompaktna izvedba operatorske funkcije za operator + preko
operatora +=, koja izgleda ovako:
inline Vektor3d operator +(const Vektor3d &v1, const Vektor3d &v2) {
return Vektor3d(v1) += v2;
}

S obzirom da je ovdje operatorska funkcija za operator += funkcija lanica, izraz iza naredbe
return interpretira se kao Vektor3d(v1).operator +=(v2). Ovdje je ideja da se pomou
konstrukcije Vektor3d(v1) kreira privremeni bezimeni objekat koji je kopija objekta v1 (ovo je
zapravo poziv kopirajueg konstruktora kao funkcije), nakon ega se na njega primjenjuje operatorska
funkcija lanica za operator += (rezultat te primjene se vraa kao konani rezultat iz funkcije). Mada
se ova izvedba moe nai u mnogim dobrim knjigama o jeziku C++ (preporuuju je mnogi C++
gurui), veliki broj dananjih kompajlera nije u stanju dobro optimizirati ovako prljave konstrukcije,
tako da ranije predloena rjeenja tipino generiraju znatno efikasniji mainski kd na veini raspoloivih
kompajlera. Primijetimo jo da opisana konstrukcija ne bi bila mogua da je operatorska funkcija za
operator += bila izvedena kao obina funkcija, jer bi njen prvi parametar tada bio referenca (na
nekonstantni objekat) koja se ne bi mogla vezati na privremeni objekat koji bi nastao kao rezultat
konstrukcije Vektor3d(v1), jer on nema status l-vrijednosti.
Operatori << i >> takoer su asimetrini po prirodi. Meutim, njih ne moemo definirati kao
funkcije lanice klase (bar ne ukoliko elimo da obavljaju funkciju ispisa na izlazni tok, odnosno unosa
sa ulaznog toka). Naime, kako je prvi parametar operatorske funkcije za operator << obino referenca
na objekat tipa ostream, a za operator >> referenca na objekat tipa istream, odgovarajue
operatorske funkcije bi trebale biti funkcije lanice klasa ostream odnosno istream, s obzirom da
se operatorske funkcije lanice primjenjuju nad svojim lijevim operandom. Meutim, kako nije poeljno
(a esto nije ni mogue, jer nam njihova implementacija najee nije dostupna) mijenjati definicije
klasa ostream i istream i dopunjivati ih novim funkcijama lanicama (ove klase su napisane sa
ciljem da se koriste, a ne da se mijenjaju), kao rjeenje nam ostaje koritenje klasinih funkcija.
to se unarnih operatora tie, za njih je uglavnom manje bitno da li se definiraju preko obinih
operatorskih funkcija ili operatorskih funkcija lanica klase, mada i dalje vai da ukoliko se za tu svrhu
koriste obine funkcije, operand je po potrebi podloan automatskoj konverziji tipova (osim ukoliko je
operand nekog od prostih ugraenih tipova, jer se nad njima uvijek primjenjuju podrazumijevane
definicije operatora). Mnogi programeri sve unarne operatore definiraju preko operatorskih funkcija
lanica, jer takva praksa esto vodi ka neznatno kraoj implementaciji. Drugi opet definiraju kao obine
funkcije one i samo one operatore koji ne modificiraju objekat na koji se primjenjuju, kao to je recimo
unarni operator (za razliku od recimo modificirajueg operatora ++). Operatore ++ i
gotovo svi programeri definiraju preko operatorskih funkcija lanica (vjerovatan razlog je to se oni
uglavnom mogu smatrati kao specijalni sluajevi operatora += i =, koji se tipino izvode kao
operatorske funkcije lanice).
Mogunost preklapanja nekih specijalnih operatora moe takoer imati veoma interesatne primjene.
Posebno je interesantna mogunost preklapanja operatora [] i () koji se normalno koriste za
pristup elementima niza, odnosno za pozivanje funkcija. Kako klase nisu niti nizovi, niti funkcije, nad
primjercima neke klase ne mogu se normalno primjenjivati ovi operatori. Na primjer, ukoliko je a
primjerak neke klase, sami po sebi izrazi a[5] odnosno a(3, 2) nemaju smisla (naravno, sasvim
druga situacija bi bila da je a niz iji su elementi primjerci neke klase). Meutim, esta situacija je da

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


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_b
Akademska godina 2013/14

imamo klase iji se primjerci po svojoj prirodi ponaaju poput nizova (tipian primjer su klase
VektorNd i Razred koje smo ranije razvijali). Primjerci takvih klasa ili u sebi zaista uvaju
izvjesne elemente (recimo u nekom atributu nizovnog tipa) ili se logiki posmatrano ponaaju kao da u
sebi uvaju izvjesne elemente (mada su ti elementi zapravo pohranjeni u dinamiki alociranim
blokovima memorije). Takve klase se openito nazivaju kontejnerske klase. Takoer, mogue je
napraviti klase iji se primjerci po svojoj prirodi ponaaju poput funkcija (primjer bi mogla biti klasa
koja bi opisivala polinom iji se koeficijenti mogu zadavati) i one se nazivaju funkcijske klase.
Preklapanje operatora [] i () omoguava da se primjerci klasa iji se primjerci ponaaju kao nizovi
koriste kao da se radi o nizovima, odnosno da se primjerci klasa iji se primjerci ponaaju poput funkcija
koriste kao da se radi o funkcijama.
Razmotrimo jedan konkretan primjer. U klasi VektorNd koju smo ranije definirali, imali smo
funkciju lanicu DajKoordinatu koju smo koristili za pristup elementima n-dimenzionalnog vektora.
Na primjer, ukoliko je v objekat klase VektorNd, morali smo koristiti rogobatne konstrukcije poput
std::cout << v.DajKoordinatu(2) ili v.DajKoordinatu(3) = 10 (pri emu smo u ovom
drugom sluaju mogli koristiti i konstrukciju v.PostaviKoordinatu(3, 10). Bilo bi mnogo
prirodnije kada bismo mogli pisati samo std::cout << v[2] ili v[3] = 10. U normalnim
okolnostima ovo nije mogue, s obzirom da v nije niz. Preklapanje operatora [] e omoguiti da
ovo postane mogue. Preklapanje ovog operatora sasvim je jednostavno, a izvodi se definiranjem
operatorske funkcije operator [] koja obavezno mora biti funkcija lanica klase za koju se definira
ovaj operator. Pri tome se izraz oblika x[y] interpretira kao x.operator [](y). Odavde
neposredno slijedi da ukoliko elimo podrati da umjesto v.DajKoordinatu(i) moemo pisati
prosto v[i], sve to treba uradti je promijeniti imena funkcija lanica Koordinata u
operator [], dok njihova definicija moe ostati ista (pri tome su nam potrebne dvije verzije funkcije
lanice operator [], jedna konstantna i jedna nekonstantna, iz istih razloga zbog kojeg su nam bile
potrebne i dvije verzike funkcije lanice DajKoordinatu. Slijedi poboljana izvedba klase
VektorNd, u kojoj smo pored operatora [] usput definirali i operatore + i << umjesto funkcije
ZbirVektora i metode Ispisi, koje smo koristili u ranijim definicijama, kao i operator += koji
smo uveli radi konzistentnosti, s obzirom da definiramo i operator +:
class VektorNd {
int dimenzija;
double *koordinate;
void TestIndeksa(int indeks) const {
if(indeks < 1 || indeks > dimenzija)
throw std::range_error("Pogrean indeks!");
}
public:
explicit VektorNd(int dimenzija);
VektorNd(std::initalizer_list<double> lista);
void PromijeniDimenziju(int nova_dimenzija);
~VektorNd() { delete[] koordinate; }
VektorNd(const VektorNd &v);
VektorNd(VektorNd &&v);
VektorNd &operator =(const VektorNd &v);
VektorNd &operator =(VektorNd &&v);
double operator [](int indeks) const {
TestIndeksa(indeks); return koordinate[indeks - 1];
}
double &operator [](int indeks) {
TestIndeksa(indeks); return koordinate[indeks - 1];
}
double *begin() const { return koordinate; }
double *end() const { return koordinate + dimenzija; }
friend ostream &operator <<(ostream &tok, const VektorNd &v);
friend VektorNd operator +(const VektorNd &v1, const VektorNd &v2);
VektorNd operator +=(const VektorNd &v);
};

Implementacije svih konstruktora, operatora dodjele i metode PromijeniDimenziju ostaju iste


kao to su bile i ranije. Metoda PostaviKoordinatu je izbaena, jer je postala nepotrebna. Ostaje da
4

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


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_b
Akademska godina 2013/14

se prikau implementacije operatorskih funkcija za operatore <<, + i += (ova posljednja je


izvedena kao funkcija lanica):
ostream &operator <<(ostream &tok, const VektorNd &v) {
tok << "{";
for(int i = 0; i < v.dimenzija - 1; i++) {
tok << v.koordinate[i] << ",";
if(i != v.dimenzija - 1) tok << ",";
return tok << "}";
}
VektorNd operator +(const VektorNd &v1, const VektorNd &v2) {
if(v1.dimenzija != v2.dimenzija)
throw std::domain_error("Nesaglasne dimenzije!");
VektorNd v3(v1.dimenzija);
for(int i = 0; i < v1.dimenzija; i++)
v3.koordinate[i] = v1.koordinate[i] + v2.koordinate[i];
return v3;
}
VektorNd &operator +=(const VektorNd &v) {
if(dimenzija != v.dimenzija)
throw std::domain_error("Nesaglasne dimenzije!");
for(int i = 0; i < dimenzija; i++) koordinate[i] += v.koordinate[i];
return *this;
}

Jasno je da objekte ovakve klase, zbog injenice da je definiran operator [] moemo koristiti kao
nizove i to kao pametne nizove kod kojih se provjerava opseg indeksa i iji indeksi poinju od jedinice.
Nije preteko zakljuiti da je funkcioniranje tipa vector definiranog u istoimenoj biblioteci, kao i jo
nekih srodnih tipova, kao to su deque, string i valarray (o kojem nismo govorili) takoer
zasnovano na preklapanju operatora [].
Bitno je naglasiti da kada smo za neku klasu definirali operator [], izraz x[y] gdje je x neki
primjerak te klase strogo gledano ne predstavlja indeksiranje (jer x nije niz) nego poziv funkcije
lanice operator [], mada izgleda poput indeksiranja i obino se implementira tako da zaista
indeksira neki niz (ili neki drugi tip koji je izveden da se ponaa poput niza) kojem se pristupa preko
nekog od internih atributa klase. Meutim, kao i za sve operatore, programer ima pravo da operator []
definira kako god eli. Na primjer, sasvim je mogue definirati ovaj operator tako da indeks (pod
navodnicima, jer se ne radi o pravom indeksu) u zagradama uope nije cijeli broj, nego npr. realni broj,
string ili ak neki drugi objekat. Na primjer, neka smo definirali klasu Tablica koja uva vrijednost
neke funkcije zadane tabelarno u takama 1, 2, 3 itd. i neka je t objekat klase Tablica. Prirodno je
definirati operator [] tako da izraz t[2] daje vrijednost funkcije u taki 2. Meutim, isto tako ima
smisla definirati ovaj operator i za necjelobrojne indekse, tako da npr. t[2.65] daje procjenu
vrijednosti funkcije u taki 2.65 uz izvjesne pretpostavke, npr. da je funkcija priblino linearna izmeu
zadanih taaka (ovaj postupak poznat je u numerikoj matematici kao linearna interpolacija). Na ovom
mjestu neemo implementirati takvu klasu, nego smo samo eljeli dati ideju za ta bi se indeksiranje
realnim brojem moglo upotrijebiti. Isto tako, asocijativni nizovi odnosno mape o kojima smo ranije
govorili (tj. primjerci standarnog bibliotekog tipa map) definiraju operator [] koji kao indeks
prihvata onaj tip koji je naveden kao tip kljunog polja mape, a koji vraa odgovarajuu pridruenu
vrijednost. Recimo, ako je stanovnistvo primjerak tipa std::map<std::string, int>, taj
objekat e imati definiran operator [] koji kao indeks prihvata string (ili neto to se moe
automatski konvertirati u string, poput klasinog niza znakova), to omoguava recimo da izraz poput
stanovnistvo["Tuzla"] predstavljao broj stanovnika Tuzle.
Preklapanje operatora () izvodi se veoma slino kao preklapanje operatora []. Njegovo
preklapanje izvodi se definiranjem operatorske funkcije lanice operator (), a izraz oblika x(y)
gdje je x primjerak klase u kojoj je ona definirana interpretira se kao x.operator ()(y).
Definiranje ovog operatora omoguava da se primjerci neke klase (koji naravno nisu funkcije) koriste
kao da su funkcije, tj. da se nad njima koristi sintaksa koja izgleda kao poziv funkcije. Klase koje
definiraju operator () nazivaju se funkcijske klase (ponekad i funktorske klase). One se mogu koristiti

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


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_b
Akademska godina 2013/14

kao fabrike za definiranje objekata koji se ponaaju poput funkcija, ali na ije se specifinost moe
uticati (recimo, u trenutku njihovog kreiranja, ili pozivom neke funkcije lanice nad njima). Takvi
objekti nazivaju se funkcijski objekti ili krae funktori. Oni se ponaaju kao pametne funkcije, u smislu
da se oni mogu koristiti i pozivati poput obinih funkcija, ali je sa njima mogue izvoditi i druge
operacije, zavisno od njihove definicije. Funktori se, poput primjeraka svih drugih klasa, mogu kreirati
konstruktorima, unitavati destruktorima, mogu se prenositi kao parametri u funkcije, vraati kao
rezultati iz funkcija, itd. Slijedi jedan vrlo jednostavan primjer funkcijske klase:
class KvadratnaFunkcija {
double a, b, c;
public:
KvadratnaFunkcija(double a, double b, double c) : a(a), b(b), c(c) {}
double operator ()(double x) const { return a * x * x + b * x + c; }
};

Ova klasa moe posluiti za kreiranje objekata koji se ponaaju kao kvadratne funkcije, ali iji se
koeficijenti mogu zadavati prilikom kreiranja. Slijedi primjer upotrebe ove klase, koji e ispisati
vrijednosti 6 i 43, jer je 3 (2)2 + 5 (2) + 4 = 4 i 1 72 + 0 7 6 = 43
KvadratnaFunkcija kvf1(3, 5, 4), kvf2(1, 0, -6);
std::cout << kvf1(-2) << " " << kvf2(7) << std::endl;

// 4 43

Kao neto sloeniji primjer, kreiraemo funkcijsku klasu Polinom koja omoguava definiranje
polinoma sa jednom promjenljivom, ijim se koeficijentima moe pristupati putem uglastih zagrada, a
koji se mogu pozivati poput funkcija (jer u matetmatskom smislu, polinomi jesu funkcije). Da se ne
bismo zamarali sa dinamikom alokacijom memorije, koeficijente polunoma uvaemo u atributu koji je
tipa vector (samim tim, nee nam biti potrebne stvari poput destuktora, kopirajuih konstrutora, itd.).
Radi jednostavnosti, ova klasa je dizajnirana minimalistiki i podrava samo klasini konstruktor (iji je
parametar eljeni stepen polinoma), sekvencijski konstruktor koji omoguava da se koeficijenti
polinoma zadaju putem inicijalizacione liste (za njegovu implementaciju iskoritena je injenica da se
vektori mogu inicijalizirati inicijalizacionim listama), metodu DajStepen koja omoguava da se
sazna stepen polinoma (primijetimo da je stepen polinoma za 1 vei od broja njegovih koeficijenata,
zbog postojanja slobodnog lana), te operatore [] i () za pristup koeficijentima polinoma, odnosno
za raunanje vrijednosti polinoma. Neka realistinija klasa za modeliranje polinoma mogla bi imati i
brojne druge elemente, poput artimetikih operatora +,* itd. koji bi omoguavali recimo sabiranje i
mnoenje polinoma, zatim operator << za ispis polinoma na ekran, i jo mnoge druge elemente:
class Polinom {
std::vector<double> koeficijenti;
void TestIndeksa(int indeks) const {
if(indeks < 0 || indeks > koeficijenti.size())
throw std::range_error("Pogrean indeks!");
}
public:
explicit Polinom(int stepen) : koeficijenti(stepen + 1) {}
Polinom(std::initializer_list<double> lista) : koeficijenti(lista) {}
double DajStepen() { return koeficijenti.size() - 1; }
double operator [](int indeks) const {
TestIndeksa(indeks); return koeficijenti[indeks];
}
double &operator [](int indeks) {
TestIndeksa(indeks); return koeficijenti[indeks];
}
double operator ()(double x) const;
};

Implementacija operatorske funkcije za operator () neto sloenija, pa emo je izvesti izvan


klase. Za efikasno raunanje vrijednosti polinoma iskoristiemo formulu prema kojoj je
pN xN + pN1 xN1 + ... + p2 x2 + p1 x + p0 = ((( ... ( pN x + pN1) x + ... ) x + p2) x + p1) x + p0

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


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_b
Akademska godina 2013/14

jer se na taj nain izvri mnogo manje raunskih operacija nego ukoliko bismo raunali vrijednost
polinoma po definiciji, pogotovo ukoliko bismo stepene xi raunali pomou funkcije pow (inae,
postupak koji se zasniva na ovoj formuli poznat je kao Hornerov postupak):
double Polinom::operator ()(double x) const {
double suma(0);
for(int i = koeficijenti.size() - 1; i >= 0; i--)
suma = suma * x + koeficijenti[i];
return suma;
}

Slijedi i kratka demonstracija napisane klase, koja e na ekraju ispisati broj 2 (stepen polinoma p2) a
nakon toga, brojeve 79 i 3, jer je 8 23 + 1 22 + 4 2 + 3 = 79 i 3 (1)2 + 7 (1) + 1 = 3:
Polinom poli1(3), poli2{3, 7, 1};
poli1[0] = 3; poli1[1] = 4; poli1[2] = 1; poli1[3] = 8;
std::cout << poli2.DajStepen() << std::endl;
std::cout << poli1(2) << " " << poli2(-1) << std::endl;

// 2
// 79 -3

Funkcijski objekti nisu funkcije, mada se po nekim aspektima ponaaju poput funkcija. Stoga nije
mogue pokazivau na funkcije dodijeliti funkcijski objekat, niti njegovu adresu. S druge strane,
polimorfnim funkcijskim omotaima (definiranim u biblioteci functional) se bez ikakvih problema
mogu dodjeljivati i funkcijski objekti. Recimo, za prethodno definirane funkcijske objekte kvf1 i
kvf2 (tipa KvadratnaFunkcija) te poli1 i poli2 (tipa Polinom), sasvim je legalno
pisati neto poput sljedeeg:
std::function<double(double)> pfo1, pfo2, pfo3, pfo4;
pfo1 = kvf1; pfo2 = kfv2; pfo3 = poli1; pfo4 = poli2;
std::cout << pfo1(-2) << " " << pfo2(7) << " "
<< pfo3(2) << " " << pfo4(1) << std::endl;

// 4 43 79 -3

Za razliku od operatorske funkcije za operator [] koja iskljuivo prima jedan i samo jedan
parametar, operatorska funkcija za operator () moe imati proizvoljan broj parametara. Tako se izraz
oblika x(p, q, r ...) u opem sluaju interpretira kao x.operator ()(p, q, r ...). Na ovaj
nain je omogueno da se primjerci neke klase mogu ponaati poput funkcija sa proizvoljnim brojem
parametara. Ova mogunost je naroito korisna ukoliko elimo da definiramo klase koje predstavljaju
matrice (to smo ve vie puta radili). Naime, bilo bi lijepo podrati mogunosti pristupa individualnim
elementima matrice, ali uz prirodnije indeksiranje (tako da indeksi redova i kolona idu od jedinice, a ne
od nule) i provjeru ispravnosti indeksa. Naalost, dosta je teko (mada ne i nemogue) preklopiti
operator [] da radi sa matricama onako kao to smo navikli pri radu sa dvodimenzionalnim nizovima.
Naime, znamo da se izraz oblika x[i][j] interpretira kao (x[i])[j], tako da se on u sluaju
kada je x primjerak neke klase zapravo interpretira kao (x.operator [](i))[j]. Odavde vidimo
da ukoliko elimo preklopiti operator [] za klase koje oponaaju matrice tako da on radi onako kako
smo navikli pri radu sa dvodimenzionalnim nizovima, rezultat operatorske funkcije za ovaj operator
mora biti nekog tipa za koji je takoer definiran operator []. S obzirom da niti jedna funkcija ne moe
kao rezultat vratiti niz, ostaju nam samo dvije mogunosti. Prva i posve jednostavna mogunost je da
operatorska funkcija za operator [] vrati kao rezultat pokaziva na traeni red matrice, tako da se
drugi par uglastih zagrada primjenjuje na vraeni pokaziva. Na alost, tada se na drugi indeks
primjenjuje standardno tumaenje operatora [] primijenjenog na pokazivae, tako da nemamo
nikakvu mogunost da utiemo na njegovu interpretaciju (npr. nije mogue ugraditi provjeru ispravnosti
drugog indeksa, niti postii da se on kree u opsegu od jedinice a ne nule). Znatno fleksibilnija
mogunost je izvesti da operatorska funkcija za operator [] vrati kao rezultat primjerak neke druge
klase koja takoer ima preklopljen operator [], tako da e izraz x[i][j] biti interpretiran kao
(x.operator [](i)).operator [](j). Ovo rjeenje je, naalost, dosta sloeno za realizaciju
(jedna mogunost je da redove matrice ne uvamo u obinim nizovima, nego u nekoj klasi poput
VektorNd koja se ponaa poput niza i ima definiran operator []), pogotovo ukoliko elimo
konzistentan tretman bez obzira jesu li objekti konstantni ili ne. Meutim, mnogo jednostavnije,
elegantnije i efikasnije rjeenje je umjesto operatora [] preklopiti operator () koristei operatorsku
funkciju sa dva parametra, to je veoma lako izvesti. Dodue, tada e se elementima neke matrice

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


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_b
Akademska godina 2013/14

(recimo a) umjesto pomou konstrukcije a[i][j] pristupati pomou konstrukcije a(i, j) to


ba nije u duhu jezika C++ (ve vie lii na BASIC ili FORTRAN), ali je ovakva sintaksa moda ak i
ljepa i prirodnija od prirodne C++ sintakse a[i][j].
Funktori su naroito korisni za realizaciju objekata koji se ponaaju kao funkcije koje pamte stanje
svog izvravanja. Na primjer, neka je potrebno definirati funkciju KumulativnaSuma sa jednim
parametrom koja vraa kao rezultat ukupnu sumu svih dotada zadanih vrijednosti njenih stvarnih
argumenata. Na primjer, elimo da naredba poput
for(int i = 1; i <= 5; i++)
std::cout << KumulativnaSuma(i) << " ";

ispie sekvencu brojeva 1, 3, 6, 10 i 15 (1 + 2 = 3, 1 + 2 + 3 = 6, 1 + 2 + 3 + 4 = 10, 1 + 2 + 3 + 4 + 5 = 15).


Njena definicija mogla bi izgledati recimo ovako:
int KumulativnaSuma(int n) {
static int suma(0);
return suma += n;
}

// Vano: statika promjenljiva!

Vidimo da ova funkcija svoje stanje (dotatanju sumu) uva u statikoj promjenljivoj suma unutar
definicije funkcije (promjenljiva je oznaena kao statika da bi njena vrijednost ostala nepromijenjena
izmeu dva poziva funkcije, a statike promjenljive se inicijaliziraju samo po prvom nailasku na
inicijalizaciju). Pretpostavimo sada da je, iz nekog razloga, potrebno vratiti akumuliranu sumu na nulu,
odnosno resetirati funkciju tako da ona zaboravi prethodno sabrane argumente i nastavi rad kao da
je prvi put pozvana. Ovako, kako je funkcija napisana, to je gotovo nemogue, jer nemamo nikakvu
mogunost pristupu promjenljivoj suma izvan same funkcije. Iskreno reeno, to ipak nije posve
nemogue, jer e konstrukcija
KumulativnaSuma(KumulativnaSuma(0));

// Prljav trik!

ostvariti traeni cilj (razmislite zato). Ipak, ovo je oigledno samo prljav trik, a ne neko univerzalno
rjeenje. Mogue univerzalno rjeenje moglo bi se zasnivati na definiranju promjenljive suma kao
globalne promjenljive, tako da bismo resetiranje funkcije uvijek mogli ostvariti dodjelom poput
suma = 0. Meutim, nakon svega to smo nauili o konceptima enkapsulacije i sakrivanja informacija,
suvino je i govoriti koliko je takvo rjeenje loe. Pravo rjeenje je definirati odgovarajui funktor koji e
posjedovati i metodu nazvanu recimo Resetiraj, koja e vriti njegovo resetiranje (tj. vraanje
akumulirane sume na nulu). Za tu svrhu, moemo definirati funkcijsku klasu poput sljedee:
class Akumulator {
int suma;
public:
Akumulator() : suma(0) {}
void Resetiraj() { suma = 0; }
int operator ()(int n) { return suma += n; }
};

Nakon toga, traeni funktor moemo deklarirati prosto kao instancu ove klase:
Akumulator kumulativna suma;

Slijedi i jednostavan primjer upotrebe:


for(int i = 1; i <= 5; i++) std::cout << kumulativna suma(i) << " ";
kumulativna suma.Resetiraj();
for(int i = 1; i <= 3; i++) std::cout << kumulativna suma(i) << " ";

Ova sekvenca naredbi ispisae slijed brojeva 1, 3, 6, 10, 15, 1, 3, 6 (bez poziva metode Resetiraj
ispisani slijed bio bi 1, 3, 6, 10, 15, 16, 18, 21). Pored toga, dodatna prednost ovakvog pristupa je to je
mogue definirati nekoliko razliitih primjeraka klase Akumulator koje bi se ponaale kao razliite
funkcije od kojih svaka uva svoju vlastitu akumuliranu sumu, neovisno od ostalih.
8

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


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_b
Akademska godina 2013/14

Sve funkcije iz standardne biblioteke algorithm, koje kao parametre prihvataju funkcije,
takoer prihvataju i funktore odgovarajueg tipa, to omoguava znatno veu fleksibilnost. Biblioteka
functional sadri, izmeu ostalog, nekoliko jednostavnih funkcijskih klasa (definiranih u imeniku
std, poput svih drugih stvari definiranih u standardnim bibliotekama), pomou kojih se mogu lako
kreirati funkcijski objekti koji obavljaju neke este i uobiajene operacije. Tako je, na primjer, definirana
generika funkcijska klasa greater, koja je definirana otprilike ovako (kljuna rije struct je
upotrijebljena da ne bismo morali pisati public:, s obzirom da u ovoj klasi nema nita privatno):
template <typename UporediviTip>
struct greater {
bool operator ()(UporediviTip x, UporediviTip y) { return x > y; }
};

Na primjer, ukoliko izvrimo deklaraciju


std::greater<int> veci;

tada e izraz veci(x, y) za cjelobrojne argumente x i y imati isto znaenje kao izraz x > y, s
obzirom da se on interpretira kao veci.operator()(x, y). Isto znaenje ima i izraz poput
std::greater<int>()(x, y). Naime, podizraz std::greater<int>() predstavlja poziv
automatski generiranog podrazumijevanog konstruktora za klasu std::greater<int> koji kreira
bezimeni primjerak te klase, na koji se dalje primjenjuje operatorska funkcija za operator (), odnosno
itav izraz se interpretira kao std::greater<int>().operator()(x, y)). Sve je ovo lijepo, ali
emu slue ovakve komplikacije? One pojednostavljuju koritenje mnogih funkcija iz biblioteke
algorithm. Na primjer, ukoliko elimo sortirati niz niz od 100 realnih brojeva u opadajui
poredak, to moemo izvesti prostim pozivom
std::sort(niz, niz + 100, std::greater<double>());

bez ikakve potrebe da samostalno definiramo odgovarajuu funkciju kriterija (napomenimo jo jednom
da konstrukcija std::greater<double>() kreira odgovarajui bezimeni funktor, koji se dalje
prosljeuje kao parametar u funkciju sort). Ovako neto je bilo posebno interesantno prije pojave
C++11 standarda i uvoenja lambda funkcija, jer je do tada jedina alternativa bilo kreiranje imenovane
funkcije kriterija, to je prilino nepraktino. Sada, kada su u C++11 uvedene lambda funkcije, istu stvar
moemo uraditi pomou lambda funkcija na dobro poznati nain
std::sort(niz, niz + 100, [](double x, double y) { return x > y; });

za koji e mnogi rei da je prirodniji, jasniji i itljiviji. Ipak, i dalje ostaje injenica da je konstrukcija
poput [](double x, double y) { return x > y; } donekle nezgrapnija i dua za pisanje od
konstrukcije std::greater<double>() koja je u ovom kontekstu moe zamijeniti.
Pored generike funkcijske klase greater, definirane su i generike funkcijske klase less,
equal to, not equal to, greater equal i less equal, koje respektivno odgovaraju
relacionim operatorima <,==,!=,>= i <=, zatim generike funkcijske klase plus, minus,
multiplies, divides, modulus, logical and i logical or, koje odgovaraju binarnim
operatorima +, , *, /, %, && i ||, i konano, generike funkcijske klase negate i
logical not koje odgovaraju unarnim operatorima i ! Kao primjer primjene ovih funkcijskih
klasa, navedimo da naredba
std::transform(niz1, niz1 + 10, niz2, niz3, std::multiplies<int>());

prepisuje u niz niz3 produkte odgovarajuih elemenata iz nizova niz1 i niz2 (pretpostavljajui
da su sva tri niza nizovi od 10 cijelih brojeva), bez ikakve potrebe da definiramo odgovarajuu funkciju
koju prosljeujemo kao parametar. Do pojave C++11 jedina alternativa je bila definiranje odgovarajue
imenovane funkcije za transformaciju, dok u C++11 moemo koristiti odgovarajuu lambda funkciju:
std::transform(niz1, niz1 + 10, niz2, niz3,
[](int x, int y) { return x * y; }

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


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_b
Akademska godina 2013/14

I ovdje vrijedi da je konstrukcija std::multiplies<int>() kraa od ekvivalentne konstrukcije


[](int x, int y) { return x * y; }, mada vjerovatno manje itljiva. Na slian nain se mogu
iskoristiti i ostale funkcijske klase.
Sad kada znamo ta su funkcijski objekti, moemo demistificirati lambda funkcije koje zarobljavaju
promjenljive iz svog okruenja (lambda zatvorenja). Naime, lambda fukcije nisu nita drugo nego vrsta
funkcijskih objekata, koje kompajler automatski kreira. Naime, za svaku lambda funkciju upotrijebljenu
u programu, kompajler generira po jednu pomonu funkcijsku klasu, iji su atributi vrijednosti
zarobljenih promjeljivih, a ije tijelo predstavlja tijelo operatorske funkcije za operator () u toj klasi.
Tako, kada mi napiemo neto poput
std::cout << std::count if(niz, niz + 100,
[n](int x) { return x < n; });

kompajler automatski generira neku pomonu funkcijsku klasu poput sljedee


class PomocnaFunkcijskaKlasa {
const int n;
// Zarobljena vrijednost iz okruenja
public:
PomocnaFunkcijskaKlasa(int n) : n(n) {}
bool operator ()(int x) { return x < n; }
};

dok se sama upotreba lambda funkcije zamijenjuje pozivom konstruktora koji kreira odgovarajui
(bezimeni) funkcijski objekat na osnovu vrijednosti promjenljivih koje se zarobljavaju. Tako se
prethodni poziv funkcije count if zapravo prevodi u
std::cout << std::count if(niz, niz + 100, PomocnaFunkcijskaKlasa(n));

Slina stvar se deava i pri zarobljavanju po referenci, samo to su tada odgovarajui atributi reference
na promjenljive zarobljene iz okruenja, a ne njihove kopije. Ovim ujedno postaje posve jasno zbog ega
se lambda funkcije koje zarobljavaju promjenljive iz svog okruenja ne mogu dodjeljivati pokazivaima
na funkcije, nego samo polimorfnim funkcijskim omotaima.
Standardna biblioteka functional takoer sadri nekoliko vrlo neobinih ali interesantnih
funkcija koje primaju funktore kao parametre i vraaju druge funktore kao rezultat. Do pojave C++11,
najvanije funkcije iz ove biblioteke su bind1st i bind2nd. Funkcija bind1st prima dva
parametra, nazovimo ih f i x0, pri emu je f funktor koji prima dva parametra (nazovimo ih x i y). Ova
funkcija vraa kao rezultat novi funktor, nazovimo ga g, koji prima samo jedan parametar (nazovimo ga
y) takav da vrijedi g(y) = f (x0, y). Na primjer, ako je f neki konkretan funktor, izraz poput
std::bind1st(f, 5)(y) ima isto dejstvo kao izraz f(5, y). Slino, funkcija bind2nd prima
dva parametra f i y0, i vraa kao rezultat novi funktor g, takav da vrijedi g(x) = f (x, y0), odnosno izraz
poput std::bind2nd(f, 5)(x) ima isto dejstvo kao izraz f(x, 5). Kao primjer primjene,
maloprijanji primjer u kojem smo koristili count if funkciju moe se napisati i ovako:
std::cout << std::count if(niz, niz + 100,
std::bind2nd(std::less<int>(), n));

Naime, poziv std::less<int>() kreira funktor (nazovimo ga f ) takav da f (x, y) ima vrijednost
true ako i samo ako je x < y. Stoga e izraz std::bind2nd(std::less<int>(), n) kreirati
novi funktor (nazovimo ga g) takav da je g(x) = f (x, n) pri emu je n vrijednost promjenljive n, odnosno
g(x) ima vrijednost true ako i samo ako je x < n. Tako kreirani funktor prosljeuje se funkciji
count if kao kriterij, a dalje se sve deava u skladu sa poznatim dejstvom funkcije count if.
Mnogi e prigovoriti da su ovakve konstrukcije neitke i konfuzne. To u osnovi jeste tano,
pogotovo dok se ovjek ne navikne. Meutim, sve do uvoenja lambda funkcija u C++, nismo imali
mnogo drugih alternativa ukoliko smo eljeli koristiti funkcije poput count if kod kojih se unutar
kriterija treba pristupati promjenljivim iz okruenja. Jedna mogunost bila je koritenje imenovanih
funkcija kriterija, ali koje moraju pristupati globalnim promjenljivim, jer nema drugog naina da sa
10

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


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_b
Akademska godina 2013/14

mjesta na kojem koristimo funkciju count if prenesemo ikakvu dodatnu informaciju u funkciju
kriterija (konkretno, vrijednost promjenljive n). Ovakvo rjeenje je jako neelegantno. Druga
alternativa bila bi runo kreiranje odgovarajue funkcijske klase i funkcijskog objekta, to je takoer
vrlo nepraktino. Sreom, nakon uvoenja lambda funkcija, potreba za ovakvim konstrukcijama se u
znaajnoj mjeri smanjila.
Poev od verzije C++11, izrazita je preporuka da se funkcije bind1st i bind2nd vie ne
koriste, nego da se, u sluaju da su nam one potrebne, umjesto njih koristi znatno univerzalnija (ali i
komplikovanija) funkcija bind, uvedena u C++11. Naime, zbog izvjesnih ogranienja jezika C++
koja su postojala prije standarda C++11, funkcije bind1st i bind2nd napravljene su prilino
traljavo. Na prvom mjestu, ove funkcije kao prvi parametar zahtijevaju iskljuivo funktor, i to ak ne
bilo kakav, nego funktor koji je napisan tano u skladu sa izvjesnim specifikcijama ta taj funktor sadri.
Ovim funkcijama se nije kao parametar mogla poslati obina funkcija. Dodue, to se moglo prevazii
pozivom (zastarjele) adapterske funkcije ptr fun koja kao parametar prima proizvoljnu funkciju od
jednog ili dva argumenta, a vraa kao rezultat funktor potpuno identinog dejstva kao i funkcija zadana
kao parametar, koji se moe iskoristiti kao argument u funkcijama poput bind1st i bind2nd. Ovi
nedostaci otklonjeni su u funkciji bind, koju uvijek treba koristiti umjesto bind1st i bind2nd.
Naalost, ova funkcija ima komplikovaniju sintaksu. Konkretno, treba pisati
std::bind( f, x0, std::placeholders::_1)
std::bind( f, std::placeholders::_1, y0)

umjesto
umjesto

std::bind1st( f, x0)
std::bind2nd( f, y0)

Inae, u teoriji programiranja, funkcije poput bind1st, bind2nd i bind nazivaju se veznici
(engl. binders). Stoga, ukoliko elimo gore opisani primjer sa funkcijom count if da rijeimo uz
pomo veznika (mada je rjeenje pomou lambda funkcija znatno jednostavnije), umjesto funkcije
bind2nd trebali bismo koristiti funkciju bind, na sljedei nain:
std::cout << std::count if(niz, niz + 100,
std::bind(std::less<int>(), std::placeholders::_1, n));

Kad smo ve spomenuli funkciju bind, red bi bio rei i neto o tome ta ona tano radi. Ona od
funkcije ili funktora koja(i) prima neke parametre pravi novi funktor koji prima manje parametara, a koji
se ponaa isto kao izvorna funkcija (ili funktor) kod koje(g) su neki parametri zamrznuti (ili vezani)
na neke fiksne vrijednosti. Konkretno, funkcija bind kao svoj prvi parametar prima neku funkciju
(nazovimo je f ) ili funktor, a nakon toga moe imati proizvoljan broj parametara. Neki od tih parametara
mogu biti posebni objekti nazvani drai (engl. placeholders) koji imaju specijalna imena poput
placeholders::_1 (prvi dra), placeholders::_2 (drugi dra), itd. Kao rezultat funkcija
bind vraa novi funkcijski objekat (nazovimo ga g) koji kad se pozove sa nekim argumentima, daje
isti rezultat kao kada se funkciji (ili funktoru) f proslijede preostali parametri (osim prvog) navedeni u
pozivu funkcije bind, ali uz zamjenu prvog draa sa prvim argumentom funktora g, drugog draa sa
drugim argumentom funktora g, itd. Na primjer, ukoliko ije f neka funkcija (ili funktor) koja prima 7
argumenata, tada poziv
std::bind(f, 3, std::placeholders:_1, std::placeholders:_2, 4, 6,
std::placeholders:_1, 0)

daje kao rezultat novi funktor (nazovimo ga g) koji prima dva argumenta, takav da poziv g(x, y)
daje isti rezultat kao i poziv f(3, x, y, 4, 6, x, 0). Ovo nije ba tako jednostavno shvatiti na
prvo itanje, ali ne trebate se pretjerano uzbuivati ukoliko ne razumijete to odmah, s obzirom da se
ovakva upotreba funkcije bind susree uglavnom u vrlo naprednim programskim tehnikama.
O preklapanju operatora = ve smo govorili, i vidjeli smo da se izraz oblika x = y interpretira
kao x.operator =(y) kad god je x primjerak neke klase. Tada smo rekli da odgovarajua
operatorska funkcija operator = za neku klasu gotovo po pravilu prima parametar koji je tipa
reference na konstantni primjerak te klase, i kao rezultat vraa referencu na primjerak te klase. Meutim,
postoje situacije u kojima je korisno definirati i operatorske funkcije operator = koje prihvataju
parametre drugaijeg tipa. Recimo, neka su objekti x i y razliitog tipa, pri emu je objekat x tipa
Tip1, a objekat y tipa Tip2. Ukoliko postoji samo operatorska funkcija operator = koja
11

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


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_b
Akademska godina 2013/14

prima parametar koji je referenca na tip Tip1, tada e se pri izvravanju izraza x = y pokuati
pretvorba objekta y u tip Tip1 (recimo, pomou neeksplicitnog konstruktora sa jednim parametrom
klase Tip1), nakon ega e se operatorska funkcija pozvati samo ukoliko je pretvorba uspjela.
Meutim, ukoliko unutar klase Tip1 postoji operatorska funkcija operator = koja prihvata
parametar tipa Tip2, ona e odmah biti iskoritena za realizaciju dodjele x = y, bez pokuaja
pretvorbe tipa. Na taj nain je mogue realizirati dodjelu izmeu objekata razliitih tipova ak i u
sluajevima kada pretvorba jednog u drugi tip nije uope podrana. Pored toga, direktna implementacija
dodjele izmeu objekata razliitog tipa moe biti osjetno efikasnija nego indirektna dodjela koja se
izvodi prvo pretvorbom objekta jednog tipa u drugi, a zatim dodjelom izmeu objekata jednakog tipa.
Ovo je jo jedan razlog kada moe biti korisno preklopiti operator = za objekte razliitih tipova.
Razumije se da, kao i za sve ostale operatore, programer ima pravo definirati operatorsku funkciju za
operator = da radi ta god mu je volja, ali nije nimalo mudra ideja davati ovom operatoru znaenje
koje u sutini nema nikakve veze sa dodjeljivanjem.
Ovim smo objasnili postupak preklapanja svih operatora koji se mogu preklopiti osim operatora
-> i operatora new i delete. Operator -> preklapa se operatorskom funkcijom lanicom
operator ->. S obzirom da drugi parametar operatora -> ne moe biti bilo ta nego iskljuivo ime
nekog atributa ili metode, izraz oblika x->y u sluaju kada je x primjerak neke klase ne interpretira
se kao x.operator->(y), nego kao (x.operator->())->y. Dakle, operatorska funkcija
operator -> ne treba imati parametre i kao rezultat mora vratiti neto na ta se takoer moe
primijeniti operator ->. Preklapanje ovog operatora omoguava korisniku da kreira objekte koje se
ponaaju poput pokazivaa na primjerke drugih struktura ili klasa (takvi objekti tipino imaju
preklopljen i operator dereferenciranja, tj. unarni operator *). Recimo, klase kojima se realiziraju
iteratori i pametni pokazivai imaju preklopljen ovaj operator. Konano, preklapanje operatora new i
delete omoguava korisniku da kreira vlastiti mehanizam za dinamiko alociranje memorije (npr.
umjesto u radnoj memoriji, mogue je alocirati objekte u datotekama na disku). Kako je za realizaciju
preklapanja ovih operatora potrebno mnogo vee znanje o samom mehanizmu dinamike alokacije
memorije nego to je dosada prezentirano, opis takvih postupaka izlazi izvan okvira ovog kursa.
Postoji jo jedna vrsta operatorskih funkcija lanica, koje se koriste za pretvorbu tipova (slino
konstruktorima sa jednim parametrom). Recimo, neka imamo dva tipa Tip1 i Tip2, od kojih je
Tip1 neka klasa. Ukoliko Tip1 posjeduje konstruktor sa jednim parametrom tipa Tip2, tada se
on koristi za konverziju objekata tipa Tip2 u tip Tip1 (osim ako je deklariran sa kljunom rijei
explicit). Meutim, kako definirati konverziju objekata Tip1 u tip Tip2? Ukoliko je i Tip2
neka klasa, dovoljno je definirati njen konstruktor sa jednim parametrom tipa Tip1. Problemi nastaju
ukoliko tip Tip2 nije klasa (nego npr. neki prosti ugraeni tip poput double). Da bi se mogao
definirati i postupak konverzije u proste ugraene tipove, uvode se operatorske funkcije za konverziju
tipa. One se isto piu kao funkcije lanice neke klase, a omoguavaju pretvorbu objekata te klase u bilo
koji drugi tip, koji tipino nije klasa. Kod njih se ime tipa u koji se vri pretvorba pie iza kljune rijei
operator, a povratni tip funkcije se ne navodi, nego se podrazumijeva da on mora biti onog tipa kao
tip u koji se vri pretvorba. Pored toga, operatorske funkcije za konverziju tipa nemaju parametre.
Slijedi primjer kako bismo mogli deklarirati i implementirati operatorsku funkciju za pretvorbu tipa
VektorNd u tip double, tako da se kao rezultat pretvorbe vektora u realni broj dobija njegova
duina (razumije se da je ovakva pretvorba upitna sa aspekta matematske korektnosti):
class VektorNd {
...
operator double() const;
};
VektorNd::operator double() const {
double suma(0);
for(int i = 0; i < dimenzija; i++)
suma += koordinate[i] * koordinate[i];
return std::sqrt(suma);
}

Ovim postaju mogue sljedee stvari: eksplicitna konverzija objekata tipa VektorN u tip
double pomou operatora za pretvorbu tipa (type-castinga), zatim neposredna dodjela promjenljive ili
12

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


Radna skripta za kurs Tehnike programiranja na Elektrotehnikom fakultetu u Sarajevu

Predavanje 12_b
Akademska godina 2013/14

izraza tipa VektorNd promjenljivoj tipa double, kao i upotreba objekata tipa VektorNd u
izrazima na mjestima gdje bi se normalno oekivao operand tipa double (pri tome bi u sva tri sluaja
vektor prvobitno bio pretvoren u realni broj). Na primjer, ukoliko je vpromjenljiva tipa VektorNd a
d promjenljiva tipa double, sljedee konstrukcije su sasvim legalne:
std::cout
std::cout
std::cout
d = v;
std::cout

<< (double)v;
<< double(v);
<< static_cast<double>(v);
<< std::sin(v);

Podsjetimo se da je funkcijska notacija oblika double(v), mogua samo za sluaj tipova ije se ime
sastoji od jedne rijei, tako da ne dolazi u obzir za tipove poput long int, char * itd.
Operatorske funkcije za konverziju klasnih tipova u proste ugraene tipove definitivno mogu biti
korisne, ali sa njima ne treba pretjerivati, jer ovakve konverzije tipino vode ka gubljenju informacija
(npr. zamjenom vektora njegovom duinom gube se sve informacije o njegovom poloaju). Takoer,
previe definiranih konverzija moe da zbuni kompajler. Recimo, ukoliko su podrane konverzije
objekata tipa Tip1 u objekte tipa Tip2 i Tip3, nastaje nejasnoa ukoliko se neki objekat tipa
Tip1 (recimo x) upotrijebi u nekom kontekstu u kojem se ravnopravno mogu upotrijebiti kako
objekti tipa Tip2, tako i objekti tipa Tip3. U takvom sluaju, kompajler ne moe razrijeiti
situaciju, i prijavljuje se greka. Do greke nee doi jedino ukoliko eksplicitno specificiramo koju
konverziju elimo, konstrukcijama poput Tip2(x) (odnosno nekim analognim konstrukcijama poput
(Tip2)x ili static_cast<Tip2>(x)) ili Tip3(x). Stoga, kada god definiramo korisniki
definirane pretvorbe tipova, moramo paziti da predvidimo sve situacije koje mogu nastupiti. Odavde
neposredno slijedi da od previe definiranih konverzija tipova esto moe biti vie tete nego koristi.
Naroit oprez je potreban kada imamo meusobne konverzije izmeu dva tipa. Pretpostavimo, na
primjer, da su Tip1 i Tip2 dva tipa, i da je definirana konverzija iz tipa Tip1 u tip Tip2, kao i
konverzija iz tipa Tip2 u tip Tip1 (nebitno je da li su ove konverzije ostvarene konstruktorom ili
operatorskim funkcijama za konverziju tipa). Pretpostavimo dalje da su za objekte tipova Tip1 i
Tip2 definirani operatori za sabiranje. Postavlja se pitanje kako treba interpretirati izraz oblika
x + y gdje je x tipa Tip1 a y tipa Tip2 (ili obrnuto). Da li treba objekat x pretvoriti u
objekat tipa Tip2 pa obaviti sabiranje objekata tipa Tip2, ili treba objekat y pretvoriti u objekat
tipa Tip1 pa obaviti sabiranje objekata tipa Tip1? Kompajler nema naina da razrijei ovu dilemu,
tako da e biti prijavljena greka. U ovom sluaju bismo morali eksplicitno naglasiti ta elimo izrazima
poput x + Tip1(y) ili Tip2(x) + y. Na primjer, klasa Kompleksni koju smo ranije definirali
omoguava automatsku pretvorbu realnih brojeva u kompleksne. Kada bismo definirali i automatsku
pretvorbu kompleksnih brojeva u realne (npr. uzimanjem modula kompleksnih brojeva) ukinuli bismo
neposrednu mogunost sabiranja kompleksnih brojeva sa realnim brojevima i obrnuto, jer bi upravo
nastala situacija poput maloas opisane. Ipak, do opisane dileme nee doi ukoliko postoji i operatorska
funkcija za operator + koja direktno prihvata parametre tipa Tip1 i Tip2 (npr. jedan kompleksni i
jedan realni broj). Tada e se, pri izvravanju izraza x + y ova operatorska funkcija pozvati prije nego
to se pokua ijedna pretvorba tipa (s obzirom da se pretvorbe provode samo ukoliko se ne nae
operatorska funkcija iji tipovi parametara tano odgovaraju operandima). Meutim, tada treba definirati
i operatorsku funkciju koja prihvata redom parametre tipa Tip2 i Tip1, ako elimo da i izraz
y + x radi ispravno! Slijedi da prisustvo velikog broja korisniki definiranih pretvorbi tipova drastino
poveava broj drugih operatorskih funkcija koje treba definirati da bismo imali konzistentno ponaanje.
Na kraju, recimo da je u C++11 uvedena mogunost da se operatorske funkcije za konverziju tipa
deklariraju sa specifikacijom explicit. U tom sluaju, konverzija putem takve funkcije vrie se
samo ukoliko se eksplicitno zatrai, odnosno nee se vriti automatski. Na taj nain mogu se sprijeiti
one automatske konverzije koje bi mogle biti problematine. Jedini izuzetak od ovog pravila je kod
operatorske funkcije za konverziju u tip bool. Ukoliko se ona definira kao eksplicitna, automatska
pretvorba u tip bool se ipak vri ukoliko se primjerak klase koja ju je definirala upotrijebi kao uvjet u
naredbama if i while, ali nigdje drugdje. To je iskoriteno recimo kod pametnih pokazivaa da se
omogui da se oni mogu koristiti kao uvjeti u naredbama if i while (sa znaenjem da li su nulpokazivai ili ne), ali da se ne mogu koristiti kao logike promjenljive u drugim kontekstima.

13

You might also like