You are on page 1of 1291

Forrs: http://www.doksi.

hu

Bevezets

Ez a bevezets ttekintst ad a C++ programozsi nyelv f fogalmairl, tulajdonsgairl s standard (szabvny) knyvtrrl, valamint bemutatja a knyv szerkezett s elmagyarzza azt a megkzeltst, amelyet a nyelv lehetsgeinek s azok hasznlatnak lersnl alkalmaztunk. Ezenkvl a bevezet fejezetek nmi httrinformcit is adnak a C++-rl, annak felptsrl s felhasznlsrl.

Fejezetek 1. Megjegyzsek az olvashoz 2. Kirnduls a C++-ban 3. Kirnduls a standard knyvtrban

Forrs: http://www.doksi.hu

Forrs: http://www.doksi.hu

1
Megjegyzsek az olvashoz
Szlt a Rozmr: Van m elg, mirl meslni j: ... (L. Carroll ford. Ttfalusi Istvn) A knyv szerkezete Hogyan tanuljuk a C++-t? A C++ jellemzi Hatkonysg s szerkezet Filozfiai megjegyzs Trtneti megjegyzs Mire hasznljuk a C++-t? C s C++ Javaslatok C programozknak Gondolatok a C++ programozsrl Tancsok Hivatkozsok

1.1. A knyv szerkezete


A knyv hat rszbl ll: Bevezets: Els rsz: Msodik rsz: Az 13. fejezetek ttekintik a C++ nyelvet, az ltala tmogatott f programozsi stlusokat, s a C++ standard knyvtrt. A 49. fejezetek oktat jelleg bevezetst adnak a C++ beptett tpusairl s az alapszolgltatsokrl, melyekkel ezekbl programot pthetnk. A 1015. fejezetek bevezetst adnak az objektumorientlt s az ltalnostott programozsba a C++ hasznlatval.

Forrs: http://www.doksi.hu

Bevezets

Harmadik rsz: A 1622. fejezetek bemutatjk a C++ standard knyvtrt. Negyedik rsz: A 2325. fejezetek tervezsi s szoftverfejlesztsi krdseket trgyalnak. Fggelkek: Az AE fggelkek a nyelv technikai rszleteit tartalmazzk. Az 1. fejezet ttekintst ad a knyvrl, nhny tletet ad, hogyan hasznljuk, valamint httrinformcikat szolgltat a C++-rl s annak hasznlatrl. Az olvas btran tfuthat rajta, elolvashatja, ami rdekesnek ltszik, s visszatrhet ide, miutn a knyv ms rszeit elolvasta. A 2. s 3. fejezet ttekinti a C++ programozsi nyelv s a standard knyvtr f fogalmait s nyelvi alaptulajdonsgait, megmutatva, mit lehet kifejezni a teljes C++ nyelvvel. Ha semmi mst nem tesznek, e fejezetek meg kell gyzzk az olvast, hogy a C++ nem (csupn) C, s hogy a C++ hossz utat tett meg e knyv els s msodik kiadsa ta. A 2. fejezet magas szinten ismertet meg a C++-szal. A figyelmet azokra a nyelvi tulajdonsgokra irnytja, melyek tmogatjk az elvont adatbrzolst, illetve az objektumorientlt s az ltalnostott programozst. A 3. fejezet a standard knyvtr alapelveibe s f szolgltatsaiba vezet be, ami lehetv teszi, hogy a szerz a standard knyvtr szolgltatsait hasznlhassa a kvetkez fejezetekben, valamint az olvasnak is lehetsget ad, hogy knyvtri szolgltatsokat hasznljon a gyakorlatokhoz s ne kelljen kzvetlenl a beptett, alacsony szint tulajdonsgokra hagyatkoznia. A bevezet fejezetek egy, a knyv folyamn ltalnosan hasznlt eljrs pldjt adjk: ahhoz, hogy egy mdszert vagy tulajdonsgot mg kzvetlenebb s valszerbb mdon vizsglhassunk, alkalmanknt elszr rviden bemutatunk egy fogalmat, majd ksbb behatbban trgyaljuk azt. Ez a megkzelts lehetv teszi, hogy konkrt pldkat mutassunk be, mieltt egy tmt ltalnosabban trgyalnnk. A knyv felptse gy tkrzi azt a megfigyelst, hogy rendszerint gy tanulunk a legjobban, ha a konkrttl haladunk az elvont fel mg ott is, ahol visszatekintve az elvont egyszernek s magtl rtetdnek ltszik. Az I. rsz a C++-nak azt a rszhalmazt rja le, mely a C-ben vagy a Pascalban kvetett hagyomnyos programozsi stlusokat tmogatja. Trgyalja a C++ programokban szerepl alapvet tpusokat, kifejezseket, vezrlsi szerkezeteket. A modularitst, mint a nvterek, forrsfjlok s a kivtelkezels ltal tmogatott tulajdonsgot, szintn trgyalja. Felttelezzk, hogy az olvasnak mr ismersek az I. fejezetben hasznlt alapvet programozsi fogalmak, gy pldul bemutatjuk a C++ lehetsgeit a rekurzi s iterci kifejezsre, de nem sokig magyarzzuk, milyen hasznosak ezek. A II. rsz a C++ j tpusok ltrehozst s hasznlatt segt szolgltatsait rja le. Itt (10. s 12. fejezet) mutatjuk be a konkrt s absztrakt osztlyokat (felleteket), az opertor-tlterhelssel (11. fejezet), a tbbalaksggal (polimorfizmussal) s az osztlyhierarchik hasz-

Forrs: http://www.doksi.hu

1. Megjegyzsek az olvashoz

nlatval (12. s 15. fejezet) egytt. A 13. fejezet a sablonokat (template) mutatja be, vagyis a C++ lehetsgeit a tpus- s fggvnycsaldok ltrehozsra, valamint szemllteti a trolk ellltsra (pl. listk), valamint az ltalnostott (generikus) programozs tmogatsra hasznlt alapvet eljrsokat. A 14. fejezet a kivtelkezelst, a hibakezelsi mdszereket trgyalja s a hibatrs biztostshoz ad irnyelveket. Felttelezzk, hogy az olvas az objektumorientlt s az ltalnostott programozst nem ismeri jl, illetve hasznt ltn egy magyarzatnak, hogyan tmogatja a C++ a f elvonatkoztatsi (absztrakcis) eljrsokat. gy teht nemcsak bemutatjuk az elvonatkoztatsi mdszereket tmogat nyelvi tulajdonsgokat, hanem magukat az eljrsokat is elmagyarzzuk. A IV. rsz ebben az irnyban halad tovbb. A III. rsz a C++ standard knyvtrt mutatja be. Clja: megrtetni, hogyan hasznljuk a knyvtrat; ltalnos tervezsi s programozsi mdszereket szemlltetni s megmutatni, hogyan bvtsk a knyvtrat. A knyvtr gondoskodik trolkrl (kontnerek list, vector, map, 18. s 19. fejezet), szabvnyos algoritmusokrl (sort, find, merge, 18. s 19. fejezet), karakterlnc-tpusokrl s -mveletekrl (20. fejezet), a bemenet s kimenet kezelsrl (input/output, 21. fejezet), valamint a szmokkal vgzett mveletek (numerikus szmts) tmogatsrl (22. fejezet). A IV. rsz olyan krdseket vizsgl, melyek akkor merlnek fel, amikor nagy szoftverrendszerek tervezsnl s kivitelezsnl a C++-t hasznljuk. A 23. fejezet tervezsi s vezetsi krdsekkel foglalkozik. A 24. fejezet a C++ programozsi nyelv s a tervezsi krdsek kapcsolatt vizsglja, mg a 25. fejezet az osztlyok hasznlatt mutatja be a tervezsben. Az A fggelk a C++ nyelvtana, nhny jegyzettel. A B fggelk a C s a C++ kzti s a szabvnyos C++ (ms nven ISO C++, ANSI C++) illetve az azt megelz C++-vltozatok kzti rokonsgot vizsglja. A C fggelk nhny nyelvtechnikai pldt mutat be, A D fggelk pedig a kulturlis eltrsek kezelst tmogat standard knyvtrbeli elemeket mutatja be. Az E fggelk a standard knyvtr kivtelkezelsel kapcsolatos garanciit s kvetelmnyeit trgyalja.

1.1.1. Pldk s hivatkozsok


Knyvnk az algoritmusok rsa helyett a program felptsre fekteti a hangslyt. Kvetkezskppen elkerli a ravasz vagy nehezebben rthet algoritmusokat. Egy egyszer eljrs alkalmasabb az egyes fogalmak vagy a programszerkezet egy szempontjnak szemlltetsre. Pldul Shell rendezst hasznl, ahol a valdi kdban jobb lenne gyorsrendezst (quicksort) hasznlni. Gyakran j gyakorlat lehet a kd jrarsa egy alkalmasabb algoritmussal. A valdi kdban ltalban jobb egy knyvtri fggvny hvsa, mint a knyvben hasznlt, a nyelvi tulajdonsgok szemlltetsre hasznlt kd.

Forrs: http://www.doksi.hu

Bevezets

A tanknyvi pldk szksgszeren egyoldal kpet adnak a programfejlesztsrl. Tisztzva s egyszerstve a pldkat a felmerlt bonyolultsgok eltnnek. Nincs, ami helyettesten a valdi programok rst, ha benyomst akarunk kapni, igazbl milyen is a programozs s egy programozsi nyelv. Ez a knyv a nyelvi tulajdonsgokra s az alapvet eljrsokra sszpontost, amelyekbl minden program sszetevdik, valamint az sszepts szablyaira. A pldk megvlasztsa tkrzi fordtprogramokkal, alapknyvtrakkal, szimulcikkal jellemezhet htteremet. A pldk egyszerstett vltozatai a valdi kdban tallhatknak. Egyszerstsre van szksg, hogy a programozsi nyelv s a tervezs lnyeges szempontjai el ne vesszenek a rszletekben. Nincs gyes plda, amelynek nincs megfelelje a valdi kdban. Ahol csak lehetsges, a C fggelkben lv nyelvtechnikai pldkat olyan alakra hoztam, ahol a vltozk x s y, a tpusok A s B, a fggvnyek f() s g() nevek. A kdpldkban az azonostkhoz vltoz szlessg betket hasznlunk. Pldul:
#include<iostream> int main() { std::cout << "Hell, vilg!\n"; }

Els ltsra ez termszetellenesnek tnhet a programozk szmra, akik hozzszoktak, hogy a kd lland szlessg betkkel jelenik meg. A vltoz szlessg betket ltalban jobbnak tartjk szveghez, mint az lland szlessgt. A vltoz szlessg betk hasznlata azt is lehetv teszi, hogy a kdban kevesebb legyen a logiktlan sortrs. Ezenkvl sajt ksrleteim azt mutatjk, hogy a legtbb ember kis id elteltvel knnyebben olvashatnak tartja az j stlust. Ahol lehetsges, a C++ nyelv s knyvtr tulajdonsgait a kziknyvek szraz bemutatsi mdja helyett a felhasznlsi krnyezetben mutatjuk be. A bemutatott nyelvi tulajdonsgok s lersuk rszletessge a szerz nzett tkrzik, aki a legfontosabb krdsnek a kvetkezt tartja: mi szksges a C++ hatkony hasznlathoz? A nyelv teljes lersa a knnyebb megkzelts cljbl jegyzetekkel elltva a The Annotated C++ Language Standard cm kziknyvben tallhat, mely Andrew Koenig s a szerz mve. Logikusan kellene hogy legyen egy msik kziknyv is, a The Annotated C++ Standard Library. Mivel azonban mind az id, mind rsi kapacitsom vges, nem tudom meggrni, hogy elkszl. A knyv egyes rszeire val hivatkozsok 2.3.4 (2. fejezet, 3.szakasz, 4. bekezds), B.5.6 (B fggelk, 5.6. bekezds s 6.[10](6. fejezet, 10. gyakorlat) alakban jelennek meg. A dlt betket kiemelsre hasznljuk (pl. egy karakterlnc-literl nem fogadhat el), fon-

Forrs: http://www.doksi.hu

1. Megjegyzsek az olvashoz

tos fogalmak els megjelensnl (pl. tbbalaksg), a C++ nyelv egyes szimblumainl (pl. for utasts), az azonostknl s kulcsszavaknl, illetve a kdpldkban lv megjegyzseknl.

1.1.2. Gyakorlatok
Az egyes fejezetek vgn gyakorlatok tallhatk. A gyakorlatok fleg az rj egy programot tpusba sorolhatk. Mindig annyi kdot rjunk, ami elg ahhoz, hogy a megolds fordthat s korltozott krlmnyek kztt futtathat legyen. A gyakorlatok nehzsgben jelentsen eltrek, ezrt becslt nehzsgi fokukat megjelltk. A nehzsg hatvnyozottan n, teht ha egy (*1) gyakorlat 10 percet ignyel, egy (*2) gyakorlat egy rba, mg egy (*3) egy napba kerlhet. Egy program megrsa s ellenrzse inkbb fgg az ember tapasztaltsgtl, mint magtl a gyakorlattl.

1.1.3. Megjegyzs az egyes C++-vltozatokhoz


A knyvben hasznlt nyelv tiszta C++, ahogyan a C++ szabvnyban lertk [C++, 1998]. Ezrt a pldknak futniuk kell minden C++-vltozaton. A knyvben szerepl nagyobb programrszleteket tbb krnyezetben is kiprbltuk, azok a pldk azonban, melyek a C++-ba csak nemrgiben beptett tulajdonsgokat hasznlnak fel, nem mindenhol fordthatk le. (Azt nem rdemes megemlteni, mely vltozatokon mely pldkat nem sikerlt lefordtani. Az ilyen informcik hamar elavulnak, mert a megvalstson igyekv programozk kemnyen dolgoznak azon, hogy nyelvi vltozataik helyesen fogadjanak el minden C++ tulajdonsgot.) A B fggelkben javaslatok tallhatk, hogyan birkzzunk meg a rgi C++ fordtkkal s a C fordtkra rott kddal.

1.2. Hogyan tanuljuk a C++-t?


A C++ tanulsakor a legfontosabb, hogy a fogalmakra sszpontostsunk s ne vessznk el a rszletekben. A programozsi nyelvek tanulsnak clja az, hogy jobb programozv vljunk; vagyis hatkonyabbak legynk j rendszerek tervezsnl, megvalstsnl s rgi rendszerek karbantartsnl. Ehhez sokkal fontosabb a programozsi s tervezsi mdszerek felfedezse, mint a rszletek megrtse; az utbbi idvel s gyakorlattal megszerezhet.

Forrs: http://www.doksi.hu

Bevezets

A C++ sokfle programozsi stlust tmogat. Ezek mind az ers statikus tpusellenrzsen alapulnak s legtbbjk a magas elvonatkoztatsi szint elrsre s a programoz elkpzelseinek kzvetlen lekpezsre irnyul. Minden stlus el tudja rni a cljt, mikzben hatkony marad futsi id s helyfoglals tekintetben. Egy ms nyelvet (mondjuk C, Fortran, Smalltalk, Lisp, ML, Ada, Eiffel, Pascal vagy Modula-2) hasznl programoz szre kell hogy vegye, hogy a C++ elnyeinek kiaknzshoz idt kell sznnia a C++ programozsi stlusok s mdszerek megtanulsra s megemsztsre. Ugyanez rvnyes azon programozkra is, akik a C++ egy rgebbi, kevsb kifejezkpes vltozatt hasznltk. Ha gondolkods nlkl alkalmazzuk az egyik nyelvben hatkony eljrst egy msik nyelvben, rendszerint nehzkes, gyenge teljestmny s nehezen mdosthat kdot kapunk. Az ilyen kd rsa is csaldst okoz, mivel minden sor kd s minden fordtsi hiba arra emlkeztet, hogy a nyelv, amit hasznlunk, ms, mint a rgi nyelv. rhatunk Fortran, C, Smalltalk stb. stlusban brmely nyelven, de ez egy ms filozfij nyelvben nem lesz sem kellemes, sem gazdasgos. Minden nyelv gazdag forrsa lehet az tleteknek, hogyan rjunk C++ programot. Az tleteket azonban a C++ ltalnos szerkezethez s tpusrendszerhez kell igaztani, hogy hatkony legyen az eltr krnyezetben. Egy nyelv alaptpusai felett csak prroszi gyzelmet arathatunk. A C++ tmogatja a fokozatos tanulst. Az, hogy hogyan kzeltsnk egy j nyelv tanulshoz, attl fgg, mit tudunk mr s mit akarunk mg megtanulni. Nem ltezik egyetlen megkzelts sem, amely mindenkinek j lenne. A szerz felttelezi, hogy az olvas azrt tanulja a C++-t, hogy jobb programoz s tervez legyen. Vagyis nem egyszeren egy j nyelvtant akar megtanulni, mellyel a rgi megszokott mdon vgzi a dolgokat, hanem j s jobb rendszerptsi mdszereket akar elsajttani. Ezt fokozatosan kell csinlni, mert minden j kpessg megszerzse idt s gyakorlst ignyel. Gondoljuk meg, mennyi idbe kerlne jl megtanulni egy j termszetes nyelvet vagy megtanulni jl jtszani egy hangszeren. Knnyen s gyorsan lehetnk jobb rendszertervezk, de nem annyival knnyebben s gyorsabban, mint ahogy azt a legtbben szeretnnk. Kvetkezskppen a C++-t gyakran valdi rendszerek ptsre mr azeltt hasznlni fogjuk, mieltt megrtennk minden nyelvi tulajdonsgot s eljrst. A C++ azltal, hogy tbb programozsi modellt is tmogat (2. fejezet) klnbz szint szakrtelem esetn is tmogatja a termkeny programozst. Minden j programozsi stlus jabb eszkzt ad eszkztrunkhoz, de mindegyik magban is hatkony s mindegyik fokozza a programozi hatkonysgot. A C++-t gy alkottk meg, hogy a fogalmakat nagyjbl sorban egyms utn tanulhassuk meg s ekzben gyakorlati haszonra tehessnk szert. Ez fontos, mert a haszon a kifejtett erfesztssel arnyos.

Forrs: http://www.doksi.hu

1. Megjegyzsek az olvashoz

A folytatd vita sorn kell-e C-t tanulni a C++-szal val ismerkeds eltt szilrd meggyzdsemm vlt, hogy legjobb kzvetlenl a C++-ra ttrni. A C++ biztonsgosabb, kifejezbb, cskkenti annak szksgt, hogy a figyelmet alacsonyszint eljrsokra irnytsuk. Knnyebb a C-ben a magasabb szint lehetsgek hinyt ptl trkksebb rszeket megtanulni, ha elbb megismertk a C s a C++ kzs rszhalmazt s a C++ ltal kzvetlenl tmogatott magasabb szint eljrsokat. A B fggelk vezrfonalat ad azoknak a programozknak, akik a C++ ismeretben vltanak a C-re, pldul azrt, hogy rgebbi kdot kezeljenek. Tbb egymstl fggetlenl fejlesztett s terjesztett C++-vltozat ltezik. Gazdag vlasztk kaphat eszkztrakbl, knyvtrakbl, programfejleszt krnyezetekbl is. Rengeteg tanknyv, kziknyv, folyirat, elektronikus hirdettbla, konferencia, tanfolyam ll rendelkezsnkre a C++ legfrissebb fejlesztseirl, hasznlatrl, segdeszkzeirl, knyvtrairl, megvalstsairl s gy tovbb. Ha az olvas komolyan akarja a C++-t hasznlni, tancsos az ilyen forrsok kztt is bngszni. Mindegyiknek megvan a sajt nzpontja, elfogultsga, ezrt hasznljunk legalbb kettt kzlk. Pldul lsd [Barton,1994], [Booch,1994], [Henricson, 1997], [Koenig, 1997], [Martin, 1995].

1.3. A C++ jellemzi


Az egyszersg fontos tervezsi felttel volt; ahol vlasztani lehetett, hogy a nyelvet vagy a fordtt egyszerstsk-e, az elbbit vlasztottuk. Mindenesetre nagy slyt fektettnk arra, hogy megmaradjon a C-vel val sszeegyeztethetsg, ami eleve kizrta a C nyelvtan kisprst. A C++-nak nincsenek beptett magasszint adattpusai, sem magasszint alapmveletei. A C++-ban pldul nincs mtrixtpus inverzi opertorral, karakterlnc-tpus sszefz mvelettel. Ha a felhasznlnak ilyen tpusra van szksge, magban a nyelvben definlhat ilyet. Alapjban vve a C++-ban a legelemibb programozsi tevkenysg az ltalnos cl vagy alkalmazsfgg tpusok ltrehozsa. Egy jl megtervezett felhasznli tpus a beptett tpusoktl csak abban klnbzik, milyen mdon hatroztk meg, abban nem, hogyan hasznljk. A III. rszben lert standard knyvtr szmos pldt ad az ilyen tpusokra s hasznlatukra. A felhasznl szempontjbl kevs a klnbsg egy beptett s egy standard knyvtrbeli tpus kztt.

Forrs: http://www.doksi.hu

10

Bevezets

A C++-ban kerltk az olyan tulajdonsgokat, melyek akkor is a futsi id nvekedst vagy a tr tlterhelst okoznk, ha nem hasznljuk azokat. Nem megengedettek pldul azok a szerkezetek, melyek hztartsi informci trolst tennk szksgess minden objektumban, gy ha a felhasznl pldul kt 16 bites mennyisgbl ll szerkezetet ad meg, az egy 32 bites regiszterbe tkletesen belefr. A C++-t hagyomnyos fordtsi s futsi krnyezetben val hasznlatra terveztk, vagyis a UNIX rendszer C programozsi krnyezetre. Szerencsre a C++ sohasem volt a UNIX-ra korltozva, a UNIX-ot s a C-t csupn modellknt hasznltuk a nyelv, a knyvtrak, a fordtk, a szerkesztk, a futtatsi krnyezetek stb. rokonsga alapjn. Ez a minimlis modell segtette a C++ sikeres elterjedst lnyegben minden szmtgpes platformon. J okai vannak azonban a C++ hasznlatnak olyan krnyezetekben, melyek jelentsen nagyobb tmogatsrl gondoskodnak. Az olyan szolgltatsok, mint a dinamikus betlts, a fokozatos fordts vagy a tpusmeghatrozsok adatbzisa, anlkl is jl hasznlhatk, hogy befolysolnk a nyelvet. A C++ tpusellenrzsi s adatrejtsi tulajdonsgai a programok fordtsi id alatti elemzsre tmaszkodnak, hogy elkerljk a vletlen adatsrlseket. Nem gondoskodnak titkostsrl vagy az olyan szemlyek elleni vdelemrl, akik szndkosan megszegik a szablyokat. Viszont szabadon hasznlhatk s nem jrnak a futsi id vagy a szksges trhely nvekedsvel. Az alapelv az, hogy ahhoz, hogy egy nyelvi tulajdonsg hasznos legyen, nemcsak elegnsnak, hanem valdi programon bell is elhelyezhetnek kell lennie. A C++ jellemzinek rendszerezett s rszletes lerst lsd [Stroustrup, 1994].

1.3.1. Hatkonysg s szerkezet


A C++-t a C programozsi nyelvbl fejlesztettk ki s nhny kivteltl eltekintve a C-t, mint rszhalmazt, megtartotta. Az alapnyelvet, a C++ C rszhalmazt, gy terveztk, hogy nagyon szoros megfelels van tpusai, mveletei, utastsai, s a szmtgpek ltal kzvetlenl kezelhet objektumok (szmok, karakterek s cmek) kztt. A new, delete, typeid, dynamic_cast s throw opertorok s a try blokk kivtelvel, az egyes C++ kifejezsek s utastsok nem kvnnak futsi idej tmogatst. A C++ ugyanolyan fggvnyhvsi s visszatrsi mdokat hasznlhat, mint a C vagy mg hatkonyabbakat. Amikor mg az ilyen, viszonylag hatkony eljrsok is tl kltsgesek, a C++ fggvnyt a fordtval kifejtethetjk helyben (inline kd), gy lvezhetjk a fggvnyek hasznlatnak knyelmt, a futsi id nvelse nlkl.

Forrs: http://www.doksi.hu

1. Megjegyzsek az olvashoz

11

A C egyik eredeti clja az assembly kd helyettestse volt a legignyesebb rendszerprogramozsi feladatokban. Amikor a C++-t terveztk, vigyztunk, ne legyen megalkuvs e tren. A C s a C++ kzti klnbsg elssorban a tpusokra s adatszerkezetekre fektetett sly mrtkben van. A C kifejez s elnz. A C++ mg kifejezbb. Ezrt a jobb kifejezkpessgrt cserbe azonban nagyobb figyelmet kell fordtanunk az objektumok tpusra. A fordt az objektumok tpusnak ismeretben helyesen tudja kezelni a kifejezseket akkor is, ha egybknt knos precizitssal kellett volna megadni a mveleteket. Az objektumok tpusnak ismerete arra is kpess teszi a fordtt, hogy olyan hibkat fedjen fel, melyek msklnben egszen a tesztelsig vagy mg tovbb megmaradnnak. Vegyk szre, hogy a tpusrendszer hasznlata fggvnyparamterek ellenrzsre az adatok vletlen srlstl val megvdsre, j tpusok vagy opertorok ellltsra s gy tovbb a C++-ban nem nveli a futsi idt vagy a szksges helyet. A C++-ban a szerkezetre fektetett hangsly tkrzi a C megtervezse ta megrt programok slygyarapodst. Egy kis mondjuk 1000 soros programot megrhatunk nyers ervel, mg akkor is, ha felrgjuk a j stlus minden szablyt. Nagyobb programoknl ez egyszeren nincs gy. Ha egy 100 000 soros programnak rossz a felptse, azt fogjuk tallni, hogy ugyanolyan gyorsan keletkeznek az jabb hibk, mint ahogy a rgieket eltvoltjuk. A C++-t gy terveztk, hogy lehetv tegye nagyobb programok sszer mdon val felptst, gy egyetlen szemly is sokkal nagyobb kdmennyisggel kpes megbirkzni. Ezenkvl clkitzs volt, hogy egy tlagos sornyi C++ kd sokkal tbbet fejezzen ki, mint egy tlagos Pascal vagy C kdsor. A C++ mostanra megmutatta, hogy tl is teljesti ezeket a clkitzseket. Nem minden kdrszlet lehet jl szerkesztett, hardverfggetlen vagy knnyen olvashat. A C++-nak vannak tulajdonsgai, melyeket arra szntak, hogy kzvetlen s hatkony mdon kezelhessk a hardver szolgltatsait, anlkl, hogy a biztonsgra vagy az rthetsgre kros hatssal lennnk. Vannak olyan lehetsgei is, melyekkel az ilyen kd elegns s biztonsgos felletek mg rejthet. A C++ nagyobb programokhoz val hasznlata termszetszeren elvezet a C++ nyelv programozcsoportok ltali hasznlathoz. A C++ ltal a modularitsra, az ersen tpusos felletekre s a rugalmassgra fektetetett hangsly itt fizetdik ki. A C++-nak ppen olyan jl kiegyenslyozott szolgltatsai vannak nagy programok rsra, mint brmely nyelvnek. Ahogy nagyobbak lesznek a programok, a fejlesztskkel s fenntartsukkal, mdostsukkal kapcsolatos problmk a nyelvi problma jellegtl az eszkzk s a kezels ltalnosabb problmi fel mozdulnak el. A IV. rsz ilyen jelleg krdseket is trgyal.

Forrs: http://www.doksi.hu

12

Bevezets

Knyvnk kiemeli az ltalnos cl szolgltatsok, tpusok s knyvtrak ksztsnek mdjait. Ezek ppgy szolgljk a kis programok rit, mint a nagy programokit. Ezen tlmenen, mivel minden bonyolultabb program sok, flig-meddig fggetlen rszbl ll, az ilyen rszek rshoz szksges mdszerek ismerete j szolglatot tesz minden alkalmazsprogramoznak. Az olvas azt gondolhatja, a rszletesebb tpusszerkezetek hasznlata nagyobb forrsprogramhoz vezet. A C++ esetben ez nem gy van. Egy C++ program, amely fggvnyparamter-tpusokat vezet be vagy osztlyokat hasznl, rendszerint kiss rvidebb, mint a vele egyenrtk C program, amely nem hasznlja e lehetsgeket. Ott, ahol knyvtrakat hasznlnak, egy C++ program sokkal rvidebb lesz, mint a megfelel C program, feltve termszetesen, hogy kszthet mkdkpes C-beli megfelel.

1.3.2. Filozfiai megjegyzs


A programozsi nyelvek kt rokon clt szolglnak: a programoznak rszben eszkzt adnak, amellyel vgrehajthat mveleteket adhat meg, ugyanakkor egy sereg fogdzt is rendelkezsre bocstanak, amikor arrl gondolkodik, mit lehet tenni. Az els cl idelis esetben gpkzeli nyelvet kvn, amellyel a szmtgp minden fontos oldala egyszeren s hatkonyan kezelhet, a programoz szmra sszer, kzenfekv mdon. A C nyelvet elssorban ebben a szellemben terveztk. A msodik cl viszont olyan nyelvet kvetel meg, mely kzel van a megoldand problmhoz, hogy a megolds kzvetlenl s tmren kifejezhet legyen. A nyelv, melyben gondolkodunk/programozunk s a problmk, megoldsok, melyeket el tudunk kpzelni, szoros kapcsolatban llnak egymssal. Ezrt a nyelvi tulajdonsgok megszortsa azzal a szndkkal, hogy kikszbljk a programozi hibkat, a legjobb esetben is veszlyes. A termszetes nyelvekhez hasonlan nagy elnye van annak, ha az ember legalbb kt nyelvet ismer. A nyelv elltja a programozt a megfelel eszkzkkel, ha azonban ezek nem megfelelek a feladathoz, egyszeren figyelmen kvl hagyjuk azokat. A j tervezs s hibamentessg nem biztosthat csupn az egyedi nyelvi tulajdonsgok jelenltvel vagy tvolltvel. A tpusrendszer klnsen sszetettebb feladatok esetben jelent segtsget. A C++ osztlyai valban ers eszkznek bizonyultak.

Forrs: http://www.doksi.hu

1. Megjegyzsek az olvashoz

13

1.4. Trtneti megjegyzs


A szerz alkotta meg a C++-t, rta meg els definciit, s ksztette el els vltozatt. Megvlasztotta s megfogalmazta a C++ tervezsi feltteleit, megtervezte f szolgltatsait, s volt a felels a C++ szabvnygyi bizottsgban a bvtsi javaslatok feldolgozsrt. Vilgos, hogy a C++ sokat ksznhet a C-nek [Kernighan, 1978]. A C nhny, a tpusellenrzs tern tapasztalt hinyossgt kivve megmaradt, rszhalmazknt (lsd B fggelk). Ugyancsak megmaradt az a C-beli szndk, hogy olyan szolgltatsokra fektessen hangslyt, melyek elg alacsony szintek ahhoz, hogy megbirkzzanak a legignyesebb rendszerprogramozsi feladatokkal is. A C a maga rszrl sokat ksznhet snek, a BCPL-nek [Richards, 1980]; a BCPL // megjegyzs-formtuma (jra) be is kerlt a C++-ba. A C++ msik fontos forrsa a Simula67 volt [Dahl, 1970] [Dahl, 1972]; az osztly fogalmt (a szrmaztatott osztlyokkal s virtulis fggvnyekkel) innen vettem t. A C++ opertor-tlterhelsi lehetsge s a deklarcik szabad elhelyezse az utastsok kztt az Algol68-ra emlkeztet [Woodward, 1974]. A knyv eredeti kiadsa ta a nyelv kiterjedt fellvizsglatokon s finomtsokon ment keresztl. A fellvizsglatok f terlete a tlterhels feloldsa, az sszeszerkesztsi s trkezelsi lehetsgek voltak. Ezenkvl szmos kisebb vltoztats trtnt a C-vel val kompatibilits nvelsre. Szmos ltalnosts s nhny nagy bvts is belekerlt: ezek a tbbszrs rkls, a static s const tagfggvnyek, a protected tagok, a sablonok, a kivtelkezels, a futsi idej tpusazonosts s a nvterek. E bvtsek s fellvizsglatok tfog feladata a C++ olyan nyelvv fejlesztse volt, mellyel jobban lehet knyvtrakat rni s hasznlni. A C++ fejldsnek lerst lsd [Stroustrup, 1994]. A sablonok (template) bevezetsnek elsdleges clja a statikus tpus trolk (kontnerek list, vector, map) s azok hatkony hasznlatnak (ltalnostott vagy generikus programozs) tmogatsa, valamint a makrk s explicit tpusknyszertsek (casting) szksgnek cskkentse volt. Inspircit az Ada ltalnost eszkzei (mind azok erssgei, illetve gyengesgei), valamint rszben a Clu paramteres moduljai szolgltattak. Hasonlan, a C++ kivtelkezelsi eljrsainak eldjei is tbb-kevsb az Ada [Ichbiah, 1979], a Clu [Liskov, 1979] s az ML [Wikstrm, 1987]. Az 1985-1995 kztt bevezetett egyb fejlesztsek tbbszrs rkls, tisztn virtulis fggvnyek s nvterek viszont nem annyira ms nyelvekbl mertett tletek alapjn szlettek, inkbb a C++ hasznlatnak tapasztalataibl leszrt ltalnostsok eredmnyei. A nyelv korbbi vltozatait (sszefoglal nven az osztlyokkal bvtett C-t [Stroustrup, 1994]) 1980 ta hasznljk. Kifejlesztsben eredetileg szerepet jtszott, hogy olyan esemnyvezrelt szimulcikat szerettem volna rni, melyekhez a Simula67 idelis lett volna,

Forrs: http://www.doksi.hu

14

Bevezets

ha elgg hatkony. Az osztlyokkal bvtett C igazi terlett a nagy programok jelentettk, ahol a lehet leggyorsabbnak kell lenni s a lehet legkevesebb helyet foglalni. Az els vltozatokbl mg hinyzott az opertor-tlterhels, valamint hinyoztak a referencik, a virtulis fggvnyek, a sablonok, a kivtelek s sok egyb. A C++-t nem ksrleti krlmnyek kztt elszr 1983-ban hasznltk. A C++ nevet Rick Mascitti adta a nyelvnek az emltett v nyarn. A nv kifejezi mindazt a forradalmi jtst, amit az j nyelv a C-hez kpest hozott: a ++ a C nvel mveleti jele. (A C+-t is hasznljk, de az egy msik, fggetlen nyelv.) A C utastsformit jl ismerk rmutathatnak, hogy a C++ kifejezs nem olyan ers, mint a ++C. Mindazonltal a nyelv neve nem is D, hiszen a C-nek csupn bvtsrl van sz, amely az ott felmerlt problmk elhrtshoz az eredeti nyelv szolgltatsai kzl egyet sem vet el. A C++ nv ms megkzelts elemzshez lsd [Orwell, 1949, fggelk]. A C++ megalkotsnak f oka azonban az volt, hogy bartaimmal egytt nem szerettnk volna assembly, C vagy ms modern, magas szint nyelven programozni. Csak annyit akartunk elrni, hogy knnyebben s lvezetesebben rhassunk jl hasznlhat programokat. Kezdetben nem vetettk paprra rendszerezetten a fejlesztsi terveket: egyszerre terveztnk, dokumentltunk s alkottunk. Nem volt C++ projekt vagy C++ tervezbizottsg. A C++ a felhasznlk tapasztalatai s a bartaimmal, munkatrsaimmal folytatott vitk sorn fejldtt ki. A C++ ksbbi robbansszer elterjedse szksgszeren vltozsokat hozott magval. Valamikor 1987-ben nyilvnvalv vlt, hogy a C++ hivatalos szabvnyostsa immr elkerlhetetlen s haladktalanul meg kell kezdennk az ilyen irny munka elksztst [Stroustrup, 1994]. Folyamatosan prbltuk tartani a kapcsolatot mind hagyomnyos, mind elektronikus levlben, illetve szemlyesen, konferencikat tartva a klnbz C++ fordtk ksztivel s a nyelv f felhasznlival. Ebben a munkban nagy segtsget nyjtott az AT&T Bell Laboratories, lehetv tve, hogy vzlataimat s a C++ hivatkozsi kziknyv jabb s jabb vltozatait megoszthassam a fejlesztkkel s felhasznlkkal. Segtsgk nem albecslend, ha tudjuk, hogy az emltettek nagy rsze olyan vllalatoknl dolgozott, amelyek az AT&T vetlytrsainak tekinthetk. Egy kevsb felvilgosult cg komoly problmkat okozhatott volna s a nyelv tjszlsokra tredezst idzte volna el, pusztn azltal, hogy nem tesz semmit. Szerencsre a tucatnyi cgnl dolgoz mintegy szz kzremkd elolvasta s megjegyzsekkel ltta el a vzlatokat, melyekbl az ltalnosan elfogadott hivatkozsi kziknyv s a szabvnyos ANSI C++ alapdokumentuma megszletett. A munkt segtk neve megtallhat a The Annotated C++ Reference Manual-ban [Ellis, 1989]. Vgl az ANSI X3J16 bizottsga a Hewlett-Packard kezdemnyezsre 1989 decemberben sszelt, 1991 jniusban pedig mr

Forrs: http://www.doksi.hu

1. Megjegyzsek az olvashoz

15

annak rlhettnk, hogy az ANSI (az amerikai nemzeti szabvny) C++ az ISO (nemzetkzi) C++ szabvnyostsi kezdemnyezs rszv vlt. 1990-tl ezek a szabvnygyi bizottsgok vltak a nyelv fejlesztsnek s pontos krlhatrolsnak f frumaiv. Magam mindvgig rszt vettem e bizottsgok munkjban; a bvtmnyekkel foglalkoz munkacsoport elnkeknt kzvetlenl feleltem a C++-t rint lnyegbevg mdostsi javaslatok s az j szolgltatsok bevezetst szorgalmaz krelmek elbrlsrt. Az els szabvnyvzlat 1995 prilisban kerlt a nagykznsg el, a vgleges ISO C++ szabvnyt (ISO/IEC 14882) pedig 1998-ban fogadtk el. A knyvben bemutatott kulcsfontossg osztlyok nmelyike a C++-szal prhuzamosan fejldtt. A complex, vector s stack osztlyokat pldul az opertor-tlterhelsi eljrsokkal egyidben dolgoztam ki. A karakterlnc- s listaosztlyokat (string, list) Jonathan Shopironak ksznhetjk (azrt n is kzremkdtem). Jonathan hasonl osztlyai voltak az elsk, amelyeket egy knyvtr rszeknt szles krben hasznltak; ezekbl a rgi ksrletekbl fejlesztettk ki a C++ standard knyvtrnak string osztlyt. A [Stroustrup, 1987] s a 12.7[11] ltal lert task knyvtr egyike volt az osztlyokkal bvtett C nyelven elszr rt programoknak. (A knyvtrat s a kapcsold osztlyokat n rtam a Simula stlus szimulcik tmogatshoz.) A knyvtrat ksbb Jonathan Shopiro tdolgozta, s mg ma is hasznljk. Az els kiadsban lert stream knyvtrat n terveztem s ksztettem el, Jerry Schwarz pedig Andrew Koenig formz eljrsa (21.4.6) s ms tletek felhasznlsval az e knyv 21. fejezetben bemutatand iostreams knyvtrr alaktotta. A szabvnyosts sorn a knyvtr tovbbi finomtson esett t; a munka dandrjt Jerry Schwarz, Nathan Myers s Norihiro Kumagai vgeztk. A sablonok lehetsgeit az Andrew Koenig, Alex Stepanov, szemlyem s msok ltal tervezett vector, map, list s sort sablonok alapjn dolgoztuk ki. Alex Stepanovnak a sablonokkal trtn ltalnostott programozs tern vgzett munkja emellett elvezetett a trolk bevezetshez s a C++ standard knyvtrnak egyes algoritmusaihoz is (16.3, 17. fejezet, 18. fejezet 19.2). A szmokkal vgzett mveletek valarray knyvtra (22. fejezet) nagyrszt Kent Budge munkja.

1.5. A C++ hasznlata


A C++-t programozk szzezrei hasznljk, lnyegben minden alkalmazsi terleten. Ezt a hasznlatot tmogatja tucatnyi fggetlen megvalsts, tbbszz knyvtr s tanknyv, szmos mszaki folyirat, konferencia, s szmtalan konzultns. Oktats s kpzs minden szinten, szles krben elrhet.

Forrs: http://www.doksi.hu

16

Bevezets

A rgebbi alkalmazsok ersen a rendszerprogramozs fel hajlottak. Tbb nagy opercis rendszer rdott C++-ban: [Campbell, 1987] [Rozier, 1988] [Hamilton, 1993] [Berg, 1995] [Parrington, 1995] s sokan msok kulcsfontossg rszeket rtak. A szerz lnyegesnek tekinti a C++ engedmny nlkli gpkzelisgt, ami lehetv teszi, hogy C++-ban rhassunk eszkzmeghajtkat s ms olyan programokat, melyek valsidej, kzvetlen hardverkezelsre tmaszkodnak. Az ilyen kdban a mkds kiszmthatsga legalbb annyira fontos, mint a sebessg s gyakran gy van az eredmnyl kapott rendszer tmrsgvel is. A C++-t gy terveztk, hogy minden nyelvi tulajdonsg hasznlhat legyen a komoly idbeli s helyfoglalsbeli megszortsoknak kitett kdban is. [Stroustrup, 1994, 4.5]. A legtbb programban vannak kdrszletek, melyek ltfontossgak az elfogadhat teljestmny tekintetben. A kd nagyobb rszt azonban nem ilyen rszek alkotjk. A legtbb kdnl a mdosthatsg, a knny bvthetsg s tesztelhetsg a kulcskrds. A C++ ilyen tren nyjtott tmogatsa vezetett el szleskr hasznlathoz ott, ahol ktelez a megbzhatsg, s ahol az id haladtval jelentsen vltoznak a kvetelmnyek. Pldaknt a bankok, a kereskedelem, a biztostsi szfra, a tvkzls s a katonai alkalmazsok szolglhatnak. Az USA tvolsgi telefonrendszere vek ta a C++-ra tmaszkodik s minden 800-as hvst (vagyis olyan hvst, ahol a hvott fl fizet) C++ program irnyt [Kamath, 1993]. Szmos ilyen program nagy mret s hossz let. Ennek eredmnykppen a stabilits, a kompatibilits s a mretezhetsg lland szempontok a C++ fejlesztsben. Nem szokatlanok a milli soros C++ programok. A C-hez hasonlan a C++-t sem kifejezetten szmokkal vgzett mveletekhez terveztk. Mindazonltal sok szmtani, tudomnyos s mrnki szmtst rtak C++-ban. Ennek f oka, hogy a szmokkal val hagyomnyos munkt gyakran grafikval s olyan szmtsokkal kell prostani, melyek a hagyomnyos Fortran mintba nem illeszked adatszerkezetekre tmaszkodnak [Budge, 1992] [Barton, 1994]. A grafika s a felhasznli fellet olyan terletek, ahol ersen hasznljk a C++-t. Brki, aki akr egy Apple Macintosht, akr egy Windowst futtat PC-t hasznlt, kzvetve a C++-t hasznlta, mert e rendszerek elsdleges felhasznli felleteit C++ programok alkotjk. Ezenkvl a UNIX-ban az X-et tmogat legnpszerbb knyvtrak nmelyike is C++-ban rdott. Ilyenformn a C++ kzsen vlasztott nyelve annak a hatalmas szm alkalmazsnak, ahol a felhasznli fellet kiemelt fontossg. Mindezen szempontok mellett lehet, hogy a C++ legnagyobb erssge az a kpessge, hogy hatkonyan hasznlhat olyan programokhoz, melyek tbbfle alkalmazsi terleten ignyelnek munkt. Egyszer olyan alkalmazst tallni, melyben LAN s WAN hlzatot, szmokkal vgzett mveleteket, grafikt, felhasznli klcsnhatst s adatbzis-hozzfrst hasznlunk. Az ilyen alkalmazsi terleteket rgebben klnllknak tekintettk s ltalban klnll fejlesztkzssgek szolgltk ki, tbbfle programozsi nyelvet hasznlva.

Forrs: http://www.doksi.hu

1. Megjegyzsek az olvashoz

17

A C++-t szles krben hasznljk oktatsra s kutatsra. Ez nhny embert meglepett, akik helyesen rmutattak, hogy a C++ nem a legkisebb s legtisztbb nyelv, amelyet valaha terveztek. Mindazonltal a C++ elg tiszta ahhoz, hogy az alapfogalmakat sikeresen tantsuk, elg valszer, hatkony s rugalmas az ignyes projektekhez is, elrhet olyan szervezetek s egyttmkd csoportok szmra, melyek eltr fejlesztsi s vgrehajtsi krnyezetekre tmaszkodnak, elg rthet ahhoz, hogy bonyolult fogalmak s mdszerek tantsnak hordozja legyen s elg kereskedelmi, hogy segtse a tanultak gyakorlatban val felhasznlst. A C++ olyan nyelv, mellyel gyarapodhatunk.

1.6. C s C++
A C++ alapnyelvnek a C nyelvet vlasztottuk, mert sokoldal, tmr, s viszonylag alacsony szint, megfelel a legtbb rendszerprogramozsi feladatra, mindentt s mindenen fut, s illeszkedik a UNIX programozsi krnyezetbe.

A C-nek megvannak a hibi, de egy jonnan ksztett nyelvnek is lennnek, a C problmit pedig mr ismerjk. Nagy jelentsge van, hogy C-vel val munka vezetett el a hasznos (br nehzkes) eszkzz vl osztlyokkal bvtett C-hez, amikor elszr gondoltunk a C bvtsre Simula-szer osztlyokkal. Ahogy szlesebb krben kezdtk hasznlni a C++-t s az ltala nyjtott, a C lehetsgeit fellml kpessgek jelentsebbek lettek, jra s jra felmerlt a krds, megtartsuk-e a kt nyelv sszeegyeztethetsgt. Vilgos, hogy nhny problma elkerlhet lett volna, ha nmelyik C rksget elutastjuk (lsd pl. [Sethi, 1981]). Ezt nem tettk meg, a kvetkezk miatt:

Forrs: http://www.doksi.hu

18

Bevezets

1. Tbb milli sornyi C kd van, mely lvezheti a C++ elnyeit, feltve, hogy szksgtelen a C-rl C++-ra val teljes trs. 2. Tbb milli sornyi C-ben rt knyvtri fggvny s eszkzilleszt kd van, melyet C++ programokbl/programokban hasznlni lehet, feltve, hogy a C++ program sszeszerkeszthet s formailag sszeegyeztethet a C programmal. 3. Programozk szzezrei lteznek, akik ismerik a C-t s ezrt csak a C++ j tulajdonsgait kell megtanulniuk, vagyis nem kell az alapokkal kezdenik. 4. A C++-t s a C-t ugyanazok, ugyanazokon a rendszereken fogjk vekig hasznlni, teht a klnbsgek vagy nagyon nagyok, vagy nagyon kicsik lesznek, hogy a hibk s a kevereds lehetsge a lehet legkisebbre cskkenjen. A C++-t fellvizsgltuk, hogy biztostsuk, hogy azon szerkezetek, melyek mind a C-ben, mind a C++-ban megengedettek, mindkt nyelvben ugyanazt jelentsk (B.2). A C nyelv maga is fejldtt, rszben a C++ fejlesztsnek hatsra [Rosler, 1984]. Az ANSI C szabvny [C,1990] a fggvnydeklarcik formai kvetelmnyeit az osztlyokkal bvtett C-bl vette t. Az tvtel mindkt irnyban elfordul: a void* mutattpust pldul az ANSI C-hez talltk ki, de elszr a C++-ban valstottk meg. Mint ahogy e knyv els kiadsban meggrtk, a C++-t fellvizsgltuk, hogy eltvoltsuk az indokolatlan eltrseket, gy a C++ ma jobban illeszkedik a C-hez, mint eredetileg. Az elkpzels az volt, hogy a C++ olyan kzel legyen az ANSI C-hez, amennyire csak lehetsges de ne kzelebb [Koenig, 1989]. A szz szzalkos megfelelsg soha nem volt cl, mivel ez megalkuvst jelentene a tpusbiztonsgban, valamint a felhasznli s beptett tpusok zkkensmentes egyeztetsben. A C tudsa nem elfelttele a C++ megtanulsnak. A C programozs sok olyan mdszer s trkk hasznlatra biztat, melyeket a C++ nyelvi tulajdonsgai szksgtelenn tettek. Az explicit tpusknyszerts pldul ritkbban szksges a C++-ban, mint a C-ben (1.6.1). A j C programok azonban hajlanak a C++ programok fel. A Kernighan s Ritchie fle A C programozsi nyelv (Mszaki knyvkiad, msodik kiads, 1994) [Kernighan,1988] cm ktetben pldul minden program C++ program. Brmilyen statikus tpusokkal rendelkez nyelvben szerzett tapasztalat segtsget jelent a C++ tanulsnl.

1.6.1. Javaslatok C programozknak


Minl jobban ismeri valaki a C-t, annl nehezebbnek ltja annak elkerlst, hogy C stlusban rjon C++ programot, lemondva ezltal a C++ elnyeirl. Krjk, vessen az olvas egy pillantst a B fggelkre, mely lerja a C s a C++ kzti klnbsgeket. me nhny terlet, ahol a C++ fejlettebb, mint a C:

Forrs: http://www.doksi.hu

1. Megjegyzsek az olvashoz

19

1. A C++-ban a makrkra majdnem soha sincs szksg. A nvvel elltott llandk meghatrozsra hasznljunk konstanst (const) (5.4) vagy felsorolst (enum) (4.8), a fggvnyhvs okozta tbbletterhels elkerlsre helyben kifejtett fggvnyeket (7.1.1), a fggvny- s tpuscsaldok lersra sablonokat (13. fejezet), a nvtkzsek elkerlsre pedig nvtereket (8.2). 2. Ne vezessnk be egy vltozt, mieltt szksg van r, gy annak azonnal kezdrtket is adhatunk. Deklarci brhol lehet, ahol utasts lehet (6.3.1), gy for utastsok (6.3.3) s elgazsok feltteleiben (6.3.2.1) is. 3. Ne hasznljunk malloc()-ot, a new opertor (6.2.6) ugyanazt jobban elvgzi. A realloc() helyett prbljuk meg a vector-t (3.8). 4. Prbljuk elkerlni a void* mutatkkal val szmtsokat, az unikat s tpuskonverzikat (tpustalaktsokat), kivve, ha valamely fggvny vagy osztly megvalstsnak mlyn tallhatk. A legtbb esetben a tpuskonverzi a tervezsi hiba jele. Ha felttlenl erre van szksg, az j cast-ok (6.2.7) egyikt prbljuk hasznlni szndkunk pontosabb lershoz. 5. Cskkentsk a lehet legkevesebbre a tmbk s a C stlus karakterlncok hasznlatt. A C++ standard knyvtrnak string (3.5) s vector (3.7.1) osztlyai a hagyomnyos C stlushoz kpest gyakrabban hasznlhatk a programozs egyszerbb ttelre. ltalban ne prbljunk magunk pteni olyat, ami megvan a standard knyvtrban. Ahhoz, hogy eleget tegynk a C szerkesztsi szablyainak, a C++ fggvnyeket gy kell megadnunk, hogy szerkesztsk C md legyen. (9.2.4). A legfontosabb, hogy gy prbljunk egy programot elkpzelni, mint egymssal klcsnhatsban lv fogalmakat, melyeket osztlyok s objektumok kpviselnek, nem pedig gy, mint egy halom adatszerkezetet, a bitekkel zsonglrkd fggvnyekkel.

1.6.2. Javaslatok C++ programozknak


Sokan mr egy vtized ta hasznljk a C++-t. Mg tbben hasznljk egyetlen krnyezetben s tanultak meg egytt lni a korai fordtk s els genercis knyvtrak miatti korltozsokkal. Ami a tapasztalt C++ programozk figyelmt gyakran elkerli, nem is annyira az j eszkzk megjelense, mint inkbb ezen eszkzk kapcsolatainak vltozsa, ami alapjaiban j programozsi mdszereket kvetel meg. Ms szval, amire annak idejn nem gondoltunk vagy haszontalannak tartottunk, ma mr kivl mdszerr vlhatott, de ezekre csak az alapok jragondolsval tallunk r.

Forrs: http://www.doksi.hu

20

Bevezets

Olvassuk t a fejezeteket sorban. Ha mr ismerjk a fejezet tartalmt, gondolatban ismteljk t. Ha mg nem ismerjk, valami olyat is megtanulhatunk, amire eredetileg nem szmtottunk. n magam elg sokat tanultam e knyv megrsbl, s az a gyanm, hogy kevs C++ programoz ismeri az sszes itt bemutatott sszes eszkzt s eljrst. Ahhoz, hogy helyesen hasznljunk egy nyelvet, behatan kell ismernnk annak eszkzeit, mdszereit. Felptse s pldi alapjn ez a knyv megfelel rltst biztost.

1.7. Programozsi megfontolsok a C++-ban


A programtervezst idelis esetben hrom fokozatban kzeltjk meg. Elszr tisztn rthetv tesszk a problmt (elemzs, analzis), ezutn azonostjuk a f fogalmakat, melyek egy megoldsban szerepelnek (tervezs), vgl a megoldst egy programban fejezzk ki (programozs). A problma rszletei s a megolds fogalmai azonban gyakran csak akkor vlnak tisztn rthetv, amikor egy elfogadhatan futtathat programban akarjuk kifejezni azokat. Ez az, ahol szmt, milyen programozsi nyelvet vlasztunk. A legtbb alkalmazsban vannak fogalmak, melyeket nem knny a kapcsold adatok nlkl az alaptpusok egyikvel vagy fggvnnyel brzolni. Ha adott egy ilyen fogalom, hozzunk ltre egy osztlyt, amely a programban kpviselni fogja. A C++ osztlyai tpusok, melyek meghatrozzk, hogyan viselkednek az osztlyba tartoz objektumok, hogyan jnnek ltre, hogyan kezelhetk s hogyan sznnek meg. Az osztly lerhatja azt is, hogyan jelennek meg az objektumok, br a programtervezs korai szakaszban ez nem szksgszeren f szempont. J programok rsnl az a legfontosabb, hogy gy hozzunk ltre osztlyokat, hogy mindegyikk egyetlen fogalmat, tisztn brzoljon. Ez ltalban azt jelenti, hogy a kvetkez krdsekre kell sszpontostani: Hogyan hozzuk ltre az osztly objektumait? Msolhatk-e s/vagy megsemmisthetk-e az osztly objektumai? Milyen mveletek alkalmazhatk az objektumokra? Ha nincsenek j vlaszok e krdsekre, az a legvalsznbb, hogy a fogalom nem tiszta. Ekkor j tlet, ha tovbb gondolkodunk a problmn s annak javasolt megoldsn, ahelyett, hogy azonnal elkezdennk a kd kidolgozst. A legknnyebben kezelhet fogalmak azok, amelyeknek hagyomnyos matematikai megfogalmazsuk van: mindenfajta szmok, halmazok, geometriai alakzatok stb. A szvegkzpont bemenet s kimenet, a karakterlncok, az alaptrolk, az ezekre a trolkra alkalmazhat alap-algoritmusok, valamint nhny matematikai osztly a C++ standard knyvtrnak rszt kpezik (3. fejezet, 16.1.2). Ezenkvl elkpeszt vlasztkban lteznek knyvtrak, melyek ltalnos s rszterletekre szakosodott elemeket tmogatnak.

Forrs: http://www.doksi.hu

1. Megjegyzsek az olvashoz

21

Az egyes fogalmak (s a hozzjuk kapcsold elemek) nem lgres trben lteznek, mindig rokonfogalmak csoportjba tartoznak. Az osztlyok kzti kapcsolatok szervezse egy programon bell vagyis az egy megoldsban szerepl klnbz elemek kzti pontos kapcsolatok meghatrozsa gyakran nehezebb, mint az egyes osztlyokat kijellni. Jobb, ha az eredmny nem rendetlensg, melyben minden osztly fgg minden msiktl. Vegynk kt osztlyt: A-t s B-t. Az olyan kapcsolatok, mint az A hv B-beli fggvnyeket, A ltrehoz B-ket s A-nak van egy B tagja ritkn okoznak nagy problmt, mg az olyanok, mint az A hasznl B-beli adatot rendszerint kikszblhetk. Az sszetettsg kezelsnek egyik legersebb eszkze a hierarchikus rendezs, vagyis a rokon elemek faszerkezetbe szervezse, ahol a fa gykere a legltalnosabb elem. A C++-ban a szrmaztatott osztlyok ilyen fastruktrkat kpviselnek. Egy program gyakran gy szervezhet, mint fk halmaza, vagy mint osztlyok irnytott krmentes grfja. Vagyis a programoz nhny alaposztlyt hoz ltre, melyekhez sajt szrmaztatott osztlyaik halmaza tartozik. Az elemek legltalnosabb vltozatnak (a bzisosztlynak) a kezelst vgz mveletek meghatrozsra a virtulis fggvnyeket (2.5.5, 12.2.6) hasznlhatjuk. Szksg esetn ezen mveletek megvalstsa az egyedi esetekben (a szrmaztatott osztlyoknl) finomthat. Nha mg az irnytott krmentes grf sem ltszik kielgtnek a programelemek szervezsre; egyes elemek klcsns sszefggse rklttnek tnik. Ilyen esetben megprbljuk a ciklikus fggsgeket behatrolni, hogy azok ne befolysoljk a program tfog rendszert. Ha nem tudjuk kikszblni vagy behatrolni az ilyen klcsns fggseket, valszn, hogy olyan gondban vagyunk, melybl nincs programozsi nyelv, amely kisegtene. Hacsak ki nem tudunk eszelni knnyen megllapthat kapcsolatokat az alapfogalmak kztt, valszn, hogy a program kezelhetetlenn vlik. A fggsgi grfok kibogozsnak egyik eszkze a fellet (interfsz) tiszta elklntse a megvalststl (implementci). A C++ erre szolgl legfontosabb eszkzei az absztrakt osztlyok (2.5.4, 12.3). A kzs tulajdonsgok kifejezsnek msik formja a sablon (template, 2.7, 13. fejezet). Az osztlysablonok osztlyok csaldjt rjk le. Egy listasablon pldul a T elemek listjt hatrozza meg, ahol T brmilyen tpus lehet. A sablon teht azt adja meg, hogyan hozhatunk ltre egy tpust egy msik tpus, mint paramter tadsval. A legszoksosabb sablonok az olyan trolosztlyok, mint a listk, tmbk s asszociatv tmbk, valamint az ilyen trolkat hasznl alap-algoritmusok. Rendszerint hiba, ha egy osztly s a vele kapcsolatos fggvnyek paramterezst rklst hasznl tpussal fejezzk ki. A legjobb sablonokat hasznlni.

Forrs: http://www.doksi.hu

22

Bevezets

Emlkeztetnk arra, hogy sok programozsi feladat egyszeren s tisztn elvgezhet elemi tpusok, adatszerkezetek, vilgos fggvnyek s nhny knyvtri osztly segtsgvel. Az j tpusok lersban szerepl teljes appartust nem szabad hasznlni, kivve, ha valban szksg van r. A Hogyan rjunk C++-ban j programot? nagyon hasonlt a Hogyan rjunk j przt? krdsre. Kt vlasz van: Tudnunk kell, mit akarunk mondani s Gyakoroljunk. Sznleljk a j rst. Mind a kett ppgy helytll a C++, mint brmely termszetes nyelv esetben s tancsukat ppolyan nehz kvetni.

1.8. Tancsok
me nhny szably, amelyet figyelembe vehetnk a C++ tanulsakor. Ahogy jrtasabbak lesznk, tovbbfejleszthetjk ezeket sajt programfajtinkhoz, programozsi stlusunkhoz illeszkeden. A szablyok szndkosan nagyon egyszerek, gy nlklzik a rszleteket. Ne vegyk ket tlzottan komolyan: a j programok rshoz elssorban intelligencia, zls, trelem kell. Ezeket nem fogjuk elsre elsajttani. Ksrletezznk! [1] Amikor programozunk, valamilyen problma megoldsra szletett tleteink konkrt megvalstst hozzuk ltre. Tkrzze a program szerkezete olyan kzvetlenl ezeket az tleteket, amennyire csak lehetsges: a) Ha valamire gy gondolunk, mint kln tletre, tegyk osztlly. b) Ha klnll egyedknt gondolunk r, tegyk egy osztly objektumv. c) Ha kt osztlynak van kzs fellete, tegyk ezt a felletet absztrakt osztlly. d) Ha kt osztly megvalstsban van valami kzs, tegyk bzisosztlly e kzs tulajdonsgokat. e) Ha egy osztly objektumok trolja, tegyk sablonn. f) Ha egy fggvny egy trol szmra val algoritmust valst meg, tegyk fggvnysablonn, mely egy trolcsald algoritmust rja le. g) Ha osztlyok, sablonok stb. egy halmazn bell logikai rokonsg van, tegyk azokat kzs nvtrbe. [2] Ha olyan osztlyt hozunk ltre, amely nem matematikai egyedet r le (mint egy mtrix vagy komplex szm) vagy nem alacsonyszint tpust (mint egy lncolt lista) a) ne hasznljunk globlis adatokat (hasznljunk tagokat), b) ne hasznljunk globlis fggvnyeket,

Forrs: http://www.doksi.hu

1. Megjegyzsek az olvashoz

23

c) ne hasznljunk nyilvnos adattagokat, d) ne hasznljunk bart (friend) fggvnyeket, kivve a) vagy c) elkerlsre, e) ne tegynk egy osztlyba tpusazonost mezket, hasznljunk inkbb virtulis fggvnyeket, f) ne hasznljunk helyben kifejtett fggvnyeket, kivve ha jelents optimalizlsrl van sz. Egyedi s rszletesebb gyakorlati szablyokat az egyes fejezetek Tancsok rszben tallhatunk. Emlkeztetjk az olvast, hogy ezek a tancsok csak tmutatsul szolglnak, nem megvltoztathatatlan trvnyek. A tancsokat csak ott kvessk, ahol rtelme van. Nincs ptszere az intelligencinak, a tapasztalatnak, a jzan sznek s a j zlsnek. A soha ne tegyk ezt alak szablyokat haszontalannak tekintem. Kvetkezskppen a legtbb tancsot javaslatknt fogalmaztam meg; azt rtam le, mit tegynk. A negatv javaslatokat pedig nem gy kell rteni, mint tiltsokat: nem tudok a C++ olyan f tulajdonsgrl, melyet ne lttam volna jl felhasznlni. A Tancsok nem tartalmaznak magyarzatokat. Helyette minden tancs mellett hivatkozs tallhat a knyv megfelel rszre. Ahol negatv tancs szerepel, a hivatkozott rsz rendszerint alternatv javaslatot tartalmaz.

1.8.1. Hivatkozsok
Barton, 1994 Berg, 1995 Booch, 1994 Budge, 1992 John J. Barton and Lee R. Nackman: Scientific and Engineering C++. AddisonWesley. Reading, Mass. 1994. ISBN 1-201-53393-6. William Berg, Marshall Cline, and Mike Girou: Lessons Learned from the OS/400 OO Project. CACM. Vol. 38 No. 10. October 1995. Grady Booch: Object-Oriented Analysis and Design. Benjamin/Cummings. Menlo Park, Calif. 1994. ISBN 0-8053-5340-2. Kent Budge, J. S. Perry, an A. C. Robinson: High-Performance Scientific Computation using C++. Proc. USENIX C++Conference. Portland, Oregon. August 1992. X3 Secretariat: Standard - The C Language. X3J11/90-013. ISO Standard ISO/IEC 9899. Computer and Business Equipment Manufacturers Association. Washington, DC, USA. X+ Secretariat: International Standard- The C++ Language. X3J16-14882. Information Technology Council (NSITC). Washington, DC, USA. Roy Campbell, et al.: The Design of a Multirocessor Operating System. Proc. USENIX C++ Conference. Santa Fe, New Mexico. November 1987. James O. Coplien and Douglas C. Schmidt (editors): Pattern Languages of Program Design. Addison-Wesley. Reading, Mass. 1995. ISBN 1-201-60734-4.

C, 1990

C++, 1998 Campbell, 1987 Coplien, 1995

Forrs: http://www.doksi.hu

24

Bevezets

O-J. Dahl, B. Myrhaug, and K. Nygaard: SIMULA Common Base Language. Norwegian Computing Center S-22. Oslo, Norway. 1970. Dahl, 1972 O-J. Dahl, and C. A. R. Hoare: Hierarchical Program Consturction in Structured Programming. Academic Press, New York. 1972. Ellis, 1989 Margaret A. Ellis and Bjarne Stroustrup: The Annotated C++ Reference Manual. Addison-Wesley. Reading, Mass. 1990. ISBN 0-201-51459-1. Gamma, 1995 Erich Gamma, et al.: Design Patterns. Addison-Wesley. Reading, Mass. 1995. ISBN 0-201-63361-2. Goldberg, 1983 A. Goldberg and D. Robson: SMALLTALK- 80 - The Language and Its Implementation. Addison-Wesley. Reading, Mass. 1983. Griswold, 1970 R. E. Griswold, et al.: The Snobol4 Programming Language. Prentice-Hall. Englewood Cliffs, New Jersey. 1970. Griswold, 1983 R. E. Grisswold and M. T. Griswold: The ICON Programming Language. PrenticeHall. Englewood Cliffs, New Jersey. 1983. Hamilton, 1993 G. Hamilton and P. Kougiouris: The Spring Nucleus: A Microkernel for Objects. Proc. 1993 Summer USENIX Conference. USENIX. Henricson, 1997 Mats Henricson and Erik Nyquist: Industrial Strenght C++: Rules and Recommendations. Prentice-Hall. Englewood Cliffs, New Jersey. 1997. ISBN 0-13120965-5. Ichbiah, 1979 Jean D. Ichbiah, et al.: Rationale for the Design of the ADA Programming Language. SIGPLAN Notices. Vol. 14 No. 6. June 1979. Kamath, 1993 Yogeesh H. Kamath, Ruth E. Smilan, and Jean G. Smith: Reaping Benefits with Object-Oriented Technology. AT&T Technical Journal. Vol. 72 No. 5. September/October 1993. Kernighan, 1978 Brian W. Kernighan and Dennis M. Ritchie: The C Programming Language. Prentice-Hall. Englewood Cliffs, New Jersey. 1978. Kernighan, 1988 Brian W. Kernighan and Dennis M. Ritchie: The C Programming Language (Second Edition). Prentice-Hall. Enlewood Cliffs, New Jersey. 1988. ISBN 0-13110362-8. Koenig, 1989 Andrew Koenig and Bjarne Stroustrup: C++: As close to C as possible - but no closer. The C++ Report. Vol. 1 No. 7. July 1989. Koenig, 1997 Andrew Koenig and Barbara Moo: Ruminations on C++. Addison Wesley Longman. Reading, Mass. 1997. ISBN 1-201-42339-1. Knuth, 1968 Donald Knuth: The Art of Computer Programming. Addison-Wesley. Reading, Mass. Liskowv, 1979 Barbara Liskov et al.: Clu Reference Manual. MIT/LCS/TR-225. MIT Cambridge. Mass. 1979. Martin, 1995 Robert C. Martin: Designing Object-Oriented C++ Applications Using the Booch Method. Prentice-Hall. Englewood Cliffs, New Jersey. 1995. ISBN 0-13-203837-4. Orwell, 1949 George Orwell: 1984. Secker and Warburg. London. 1949.

Dahl, 1970

Forrs: http://www.doksi.hu

1. Megjegyzsek az olvashoz

25

Parrington, 1995 Graham Parrington et al.: The Design and Implementation of Arjuna. Computer Systems. Vol. 8 No. 3. Summer 1995. Richards, 1980 Martin Richards and Colin Whitby-Strevens: BCPL - The Language and Its Compiler. Cambridge University Press, Cambridge. England. 1980. ISBN 0-52121965-5. Rosler, 1984 L. Rosler: The Evolution of C - Past and Future. AT&T Bell Laboratories Technical Journal. Vol. 63 No. 8. Part 2. October 1984. Rozier, 1988 M. Rozier, et al.: CHORUS Distributed Operating Systems. Computing Systems. Vol. 1 no. 4. Fall 1988. Sethi, 1981 Ravi Sethi: Uniform Syntax for Type Expressions and Declarations. Software Practice & Experience. Vol. 11. 1981. Stepanov, 1994 Alexander Stepanov and Meng Lee: The Standard Template Library. HP Labs Technical Report HPL-94-34 (R. 1). August, 1994. Stroustrup, 1986 Bjarne Stroustrup: The C++ Programming Language. Addison-Wesley. Reading, Mass. 1986. ISBN 0-201-12078-X. Stroustrup, 1987 Bjarne Stroustrup and Jonathan Shopiro: A Set of C Classes for Co-Routine Style Programming. Proc. USENIX C++ conference. Santa Fe, New Mexico. November 1987. Stroustrup, 1991 Bjarne Stroustrup: The C++ Programming Language (Second Edition) AddisonWesley. Reading, Mass. 1991. ISBN 0-201-53992-6. Strostrup, 1994 Bjarne Stroustrup: The Design and Evolution of C++. Addison-Wesley. Reading, Mass. 1994. ISBN 0-201-54330-3. Tarjan, 1983 Robert E. Tarjan: Data Structures and Network Algorithms. Society for Industrial and Applied Mathematics. Philadelphia, Penn. 1983. ISBN 0-898-71187-8. Unicode, 1996 The Unicode Consortium: The Unicode Standard, Version 2.0. Addison-Wesley Developers Press. Reading, Mass. 1996. ISBN 0-201-48345-9. UNIX, 1985 UNIX Time-Sharing System: Programmer's Manual. Research Version, Tenth Edition. AT&T Bell Laboratories, Murray Hill, New Jersey. February 1985. Wilson, 1996 Gregory V. Wilson and Paul Lu (editors): Parallel Progrmming Using C++. The MIT Press. Cambridge. Mass. 1996. ISBN 0-262-73118-5. Wikstrm, 1987 Ake Wikstrm: Functional Programming Using ML. Prentice-Hall. Englewood Cliffs, New Jersey. 1987. Woodward, 1974 P. M. Woodward and S. G. Bond: Algol 68-R Users Guide. Her Majesty's Stationery Office. London. England. 1974.

Forrs: http://www.doksi.hu

2
Kirnduls a C++-ban
Az els tennivalnk: ljnk meg minden trvnytudt (Shakespeare: VI. Henrik, II. rsz ford. Nmeth Lszl) Mi a C++? Programozsi megkzeltsek Eljrskzpont programozs Modularits Kln fordts Kivtelkezels Elvont adatbrzols Felhasznli tpusok Konkrt tpusok Absztrakt tpusok Virtulis fggvnyek Objektumorientlt programozs ltalnostott programozs Trolk Algoritmusok Nyelv s programozs Tancsok

2.1. Mi a C++?
A C++ ltalnos cl programozsi nyelv, melynek f alkalmazsi terlete a rendszerprogramozs s egy jobbfajta C, tmogatja az elvont adatbrzolst, tmogatja az objektumorientlt programozst, valamint az ltalnostott programozst.

Forrs: http://www.doksi.hu

28

Bevezets

Ez a fejezet elmagyarzza, mit jelentenek a fentiek, anlkl, hogy belemenne a nyelv meghatrozsnak finomabb rszleteibe. Clja ltalnos ttekintst adni a C++-rl s hasznlatnak f mdszereirl, nem pedig a C++ programozs elkezdshez szksges rszletes informcit adni az olvasnak. Ha az olvas tl elnagyoltnak tallja e fejezet nmelyik rsznek trgyalsmdjt, egyszeren ugorja t s lpjen tovbb. Ksbb mindenre rszletes magyarzatot kap. Mindenesetre, ha tugrik rszeket a fejezetben, tegye meg magnak azt a szvessget, hogy ksbb visszatr rjuk. A nyelvi tulajdonsgok rszletes megrtse mg ha a nyelv sszes tulajdonsg is nem ellenslyozhatja azt, ha hinyzik az tfog szemlletnk a nyelvrl s hasznlatnak alapvet mdszereirl.

2.2. Programozsi megkzeltsek


Az objektumorientlt (objektumkzpont) programozs egy programozsi md a j programok rsa kzben felmerl sereg problma megoldsnak egy megkzeltse (paradigma). Ha az objektumorientlt programozsi nyelv szakkifejezs egyltaln jelent valamit, olyan programozsi nyelvet kell hogy jelentsen, amely az objektumokat kzppontba helyez programozsi stlust tmogat eljrsokrl gondoskodik. Itt fontos megklnbztetnnk kt fogalmat: egy nyelvrl akkor mondjuk, hogy tmogat egy programozsi stlust, ha olyan szolgltatsai vannak, melyek ltal az adott stlus hasznlata knyelmes (knny, biztonsgos s hatkony) lesz. A tmogats hinyzik, ha kivteles erfeszts vagy gyessg kell az ilyen programok rshoz; ekkor a nyelv csupn megengedi, hogy az adott megkzeltst hasznljuk. Lehet strukturlt programot rni Fortran77-ben s objektumkzpontt C-ben, de szksgtelenl nehezen, mivel ezek a nyelvek nem tmogatjk kzvetlenl az emltett megkzeltseket. Az egyes programozsi mdok tmogatsa nem csak az adott megkzelts kzvetlen hasznlatt lehetv tv nyelvi szolgltatsok magtl rtetd formjban rejlik, hanem a fordtsi/futsi idbeni ellenrzsek finomabb formiban, melyek vdelmet adnak a stlustl val akaratlan eltrs ellen. A tpusellenrzs erre a legkzenfekvbb plda, de a ktrtelmsg szlelse s a futsi idej ellenrzsek szintn a programozsi mdok nyelvi tmogatshoz tartoznak. A nyelven kvli szolgltatsok, mint a knyvtrak s programozsi krnyezetek, tovbbi tmogatst adnak az egyes megkzeltsi mdokhoz.

Forrs: http://www.doksi.hu

2. Kirnduls a C++-ban

29

Egy nyelv nem szksgszeren jobb, mint egy msik, csak azrt, mert olyan tulajdonsgokkal rendelkezik, amelyek a msikban nem tallhatk meg. Sok plda van ennek az ellenkezjre is. A fontos krds nem annyira az, milyen tulajdonsgai vannak egy nyelvnek, hanem inkbb az, hogy a meglv tulajdonsgok elegendek-e a kvnt programozsi stlusok tmogatsra a kvnt alkalmazsi terleteken. Ennek kvnalmai a kvetkezk: 1. 2. Minden tulajdonsg tisztn s elegnsan a nyelv szerves rsze legyen. A tulajdonsgokat egymssal prostva is lehessen hasznlni, hogy olyan megoldst adjanak, melyhez egybknt kln nyelvi tulajdonsgok lennnek szksgesek. A lehet legkevesebb legyen az l- s specilis cl tulajdonsg. Az egyes tulajdonsgok megvalstsa nem okozhat jelents tbbletterhelst olyan programoknl, melyek nem ignylik azokat. A felhasznlnak csak akkor kell tudnia a nyelv valamely rszhalmazrl, ha kifejezetten hasznlja azt egy program rshoz.

3. 4. 5.

A fentiek kzl az els elv az eszttikhoz s a logikhoz val folyamods. A kvetkez kett a minimalizmus gondolatnak kifejezse, az utols kett pedig gy sszesthet: amirl nem tudunk, az nem fj. A C++-t gy terveztk, hogy az elvont adatbrzolst, illetve az objektumorientlt s az ltalnostott programozst tmogassa, mgpedig az e megszortsok mellett tmogatott hagyomnyos C programozsi mdszereken kvl. Nem arra szolgl, hogy minden felhasznlra egyetlen programozsi stlust knyszertsen. A kvetkezkben nhny programozsi stlust s az azokat tmogat fbb tulajdonsgokat vesszk szmba. A bemutats egy sor programozsi eljrssal folytatdik, melyek az eljrskzpont (procedurlis) programozstl elvezetnek az objektumorientlt programozsban hasznlt osztlyhierarchiig s a sablonokat hasznl ltalnostott (generikus) programozsig. Minden megkzelts az eldjre pl, mindegyik hozztesz valamit a C++ programozk eszkztrhoz, s mindegyik egy bevlt tervezsi mdot tkrz. A nyelvi tulajdonsgok bemutatsa nem teljes. A hangsly a tervezsi megkzeltseken s a programok szerkezeti felptsn van, nem a nyelvi rszleteken. Ezen a szinten sokkal fontosabb, hogy fogalmat kapjunk arrl, mit lehet megtenni C++-t hasznlva, mint hogy megrtsk, hogyan.

Forrs: http://www.doksi.hu

30

Bevezets

2.3. Eljrskzpont programozs


Az eredeti programozsi alapelv a kvetkez:

Dntsd el, mely eljrsokra van szksged s hasznld azokhoz a lehet legjobb algoritmusokat.

A kzppontban az eljrs ll a kvnt szmtshoz szksges algoritmus. A nyelvek ezt az alapelvet fggvnyparamterek tadsval s a fggvnyek ltal visszaadott rtkekkel tmogatjk. Az e gondolkodsmddal kapcsolatos irodalom tele van a paramtertads s a klnbz paramterfajtk megklnbztetsi mdjainak (eljrsok, rutinok, makrk stb.) trgyalsval. A j stlus jellegzetes pldja az albbi ngyzetgyk-fggvny. tadva egy ktszeres pontossg lebegpontos paramtert, a fggvny visszaadja az eredmnyt. Ezt egy jl rthet matematikai szmtssal ri el:
double sqrt(double arg) { // a ngyzetgyk kiszmtsnak kdja } void f() { double root2 = sqrt(2); // ... }

A kapcsos zrjelek a C++-ban valamilyen csoportba foglalst fejeznek ki; itt a fggvny trzsnek kezdett s a vgt jelzik. A ketts trtvonal // egy megjegyzs (comment) kezdete, mely a sor vgig tart. A void kulcssz jelzi, hogy az f fggvny nem ad vissza rtket. Programszervezsi szempontbl a fggvnyeket arra hasznljuk, hogy rendet teremtsnk az eljrsok labirintusban. Magukat az algoritmusokat fggvnyhvsokkal s ms nyelvi szolgltatsok hasznlatval rjuk meg. A kvetkez alpontok vzlatos kpet adnak a C++ legalapvetbb szolgltatsairl a szmtsok kifejezshez.

Forrs: http://www.doksi.hu

2. Kirnduls a C++-ban

31

2.3.1. Vltozk s aritmetika


Minden nvnek s kifejezsnek tpusa van, amely meghatrozza a vgrehajthat mveleteket:
int inch;

A fenti deklarci pldul azt adja meg, hogy inch tpusa int (vagyis inch egy egsz tpus vltoz). A deklarci olyan utasts, mely a programba egy nevet vezet be. Ehhez a nvhez egy tpust rendel. A tpus egy nv vagy kifejezs megfelel hasznlatt hatrozza meg. A C++ tbb alaptpussal rendelkezik, melyek kzvetlen megfeleli bizonyos hardverszolgltatsoknak. Pldul:
bool char int double // logikai tpus, lehetsges rtkei: true (igaz) s false (hamis) // karakter, pldul 'a', 'z', vagy '9' // egsz rtk, pldul 1, 42, vagy 1216 // ktszeres pontossg lebegpontos szm, pldul 3.14 vagy 299793.0

A char vltozk termszetes mrete egy karakter mrete az adott gpen (rendesen egy bjt), az int vltozk az adott gpen mkd egsz tpus aritmetikhoz igazodik (rendszerint egy gpi sz). Az aritmetikai mveletek e tpusok brmilyen prostsra hasznlhatk:
+ * / % // sszeads vagy eljel, egy- s ktoperandus is lehet // kivons vagy eljel, egy- s ktoperandus is lehet // szorzs // oszts // maradkkpzs

Ugyangy az sszehasonlt mveletek is:


== != < > <= >= // egyenl // nem egyenl // kisebb // nagyobb // kisebb vagy egyenl // nagyobb vagy egyenl

rtkadsokban s aritmetikai mveletekben a C++ az alaptpusok kztt elvgez minden rtelmes talaktst, gy azokat egymssal tetszs szerint keverhetjk:

Forrs: http://www.doksi.hu

32

Bevezets

void some_function() { double d = 2.2; int i = 7; d = d+i; i = d*i; }

// rtket vissza nem ad fggvny // lebegpontos szm kezdeti rtkadsa // egsz kezdeti rtkadsa // sszeg rtkadsa // szorzat rtkadsa

Itt = az rtkad mvelet jele s == az egyenlsget teszteli, mint a C-ben.

2.3.2. Elgazsok s ciklusok


A C++ az elgazsok s ciklusok kifejezsre rendelkezik a hagyomnyos utastskszlettel. me egy egyszer fggvny, mely a felhasznltl vlaszt kr s a vlasztl fgg logikai rtket ad vissza:
bool accept() { cout << "Do you want to proceed (y or n)?\n"; char answer = 0; cin >> answer; if (answer == 'y') return true; return false;

// krds kirsa // vlasz beolvassa

A << (tedd bele) mveleti jelet kimeneti opertorknt hasznltuk; a cout a szabvnyos kimeneti adatfolyam. A >> (olvasd be) a bemenet mveleti jele, a cin a szabvnyos bemen adatfolyam. A >> jobb oldaln ll kifejezs hatrozza meg, milyen bemenet fogadhat el s ez a beolvass clpontja. A \n karakter a kirt karakterlnc vgn j sort jelent. A plda kiss javthat, ha egy 'n' vlaszt is szmtsba vesznk:
bool accept2() { cout << "Do you want to proceed (y or n)?\n"; char answer = 0; cin >> answer;

// krds kirsa // vlasz beolvassa

Forrs: http://www.doksi.hu

2. Kirnduls a C++-ban

33

switch (answer) { case 'y': return true; case 'n': return false; default: cout << "I'll take that for a no.\n"; // nemleges vlasznak veszi return false; }

A switch utasts egy rtket ellenriz, llandk halmazt alapul vve. A case konstansoknak klnllknak kell lennik, s ha az rtk egyikkel sem egyezik, a vezrls a default cmkre kerl. A programoznak nem kell alaprtelmezsrl (default) gondoskodnia. Kevs programot rnak ciklusok nlkl. Esetnkben szeretnnk lehetsget adni a felhasznlnak nhny prblkozsra:
bool accept3() { int tries = 1; while (tries < 4) { cout << "Do you want to proceed (y or n)?\n"; char answer = 0; cin >> answer;

// krds kirsa // vlasz beolvassa

} cout << "I'll take that for a no.\n"; // nemleges vlasznak veszi return false;

switch (answer) { case 'y': return true; case 'n': return false; default: cout << "Sorry, I don't understand that.\n" ; // nem rti a vlaszt tries = tries + 1; }

A while utasts addig hajtdik vgre, amg a felttele hamis nem lesz.

Forrs: http://www.doksi.hu

34

Bevezets

2.3.3. Mutatk s tmbk


Egy tmbt gy hatrozhatunk meg:
char v[10]; // 10 karakterbl ll tmb

Egy mutatt gy:


char* p; // mutat karakterre

A deklarcikban a [ ] jelentse tmbje (array of), mg a * jelentse mutatja (pointer to). Minden tmbnek 0 az als hatra, teht v-nek tz eleme van, v[0]v[9]. A mutat vltoz a megfelel tpus objektum cmt tartalmazhatja:
p = &v[3]; // p a v negyedik elemre mutat

A cme jelentssel br opertor az egyoperandus &. Lssuk, hogyan msolhatjuk t egy tmb tz elemt egy msik tmbbe:
void another_function() { int v1[10]; int v2[10]; // ... for (int i=0; i<10; ++i) v1[i]=v2[i]; }

A for utasts gy olvashat: lltsuk i-t 0-ra, amg i kisebb, mint 10, az i-edik elemet msoljuk t, s nveljk meg i-t. Ha egsz tpus vltozra alkalmazzuk, a ++ nvel mveleti jel az rtket egyszeren eggyel nveli.

2.4. Modulris programozs


Az vek sorn a programtervezs slypontja az eljrsok fell az adatszervezs irnyba toldott el. Egyebek mellett ez a programok nagyobb mretben tkrzdik. Az egymssal rokon eljrsokat az ltaluk kezelt adatokkal egytt gyakran modul-nak nevezzk. A megkzelts alapelve ez lesz:

Forrs: http://www.doksi.hu

2. Kirnduls a C++-ban

35

Dntsd el, mely modulokra van szksg s oszd fel a programot gy, hogy az adatokat modulokba helyezed.

Ez az adatrejts elve. Ahol az eljrsok nincsenek az adatokkal egy csoportban, az eljrskzpont programozs megfelel a clnak. Az egyes modulokon belli eljrsokra a j eljrsok tervezsnek mdja is alkalmazhat. A modulra a legkznsgesebb plda egy verem (stack) ltrehozsa. A megoldand f problmk: 1. Gondoskodni kell a verem felhasznli felletrl (pl. push() s pop() fggvnyek). 2. Biztostani kell, hogy a verem megjelentse (pl. az elemek tmbje) csak ezen a felhasznli felleten keresztl legyen hozzfrhet. 3. Biztostani kell a verem els hasznlat eltti elksztst (inicializlst). A C++ egymssal rokon adatok, fggvnyek stb. klnll nvterekbe val csoportostsra ad lehetsget. Egy Stack modul felhasznli fellete pldul gy adhat meg s hasznlhat:
namespace Stack { void push(char); char pop(); } // fellet

void f() { Stack::push('c'); if (Stack::pop() != 'c') error("lehetetlen"); }

A Stack:: minsts azt jelzi, hogy a push() s a pop() a Stack nvtrhez tartoznak. E nevek mshol trtn hasznlata nem lesz befolyssal erre a programrszre s nem fog zavart okozni. A Stack kifejtse lehet a program kln fordthat rsze:
namespace Stack { // megvalsts const int max_size = 200; char v[max_size]; int top = 0;

Forrs: http://www.doksi.hu

36

Bevezets

void push(char c) { /* tlcsorduls ellenrzse s c behelyezse */ } char pop() { /* alulcsorduls ellenrzse s a legfels elem kiemelse */ }

A Stack modul fontos jellemzje, hogy a felhasznli kdot a Stack::push()-t s a Stack::pop()-ot megvalst kd elszigeteli a Stack adatbrzolstl. A felhasznlnak nem kell tudnia, hogy a verem egy tmbbel van megvalstva, a megvalsts pedig anlkl mdosthat, hogy hatssal lenne a felhasznli kdra. Mivel az adat csak egyike az elrejtend dolgoknak, az adatrejts elve az informcirejts elvv terjeszthet ki; vagyis a fggvnyek, tpusok stb. nevei szintn modulba helyezhetk. Kvetkezskppen a C++ brmilyen deklarci elhelyezst megengedi egy nvtrben (8.2.). A fenti Stack modul a verem egy brzolsmdja. A kvetkezkben tbbfle vermet hasznlunk a klnbz programozi stlusok szemlltetsre.

2.4.1. Kln fordts


A C++ tmogatja a C kln fordtsi elvt. Ezt arra hasznlhatjuk, hogy egy programot rszben fggetlen rszekre bontsunk. Azokat a deklarcikat, melyek egy modul fellett rjk le, jellemzen egy fjlba rjuk, melynek neve a hasznlatot tkrzi. Ennek kvetkeztben a
namespace Stack { void push(char); char pop(); } // fellet

a stack.h nev fjlba kerl, a felhasznlk pedig ezt az gynevezett fejllomnyt (header) beptik (#include):
#include "stack.h" // a fellet beptse

void f() { Stack::push('c'); if (Stack::pop() != 'c') error("impossible"); }

Forrs: http://www.doksi.hu

2. Kirnduls a C++-ban

37

Ahhoz, hogy a fordtnak segtsnk az egysgessg s kvetkezetessg biztostsban, a Stack modult megvalst fjl szintn tartalmazza a felletet:
#include "stack.h" namespace Stack { const int max_size = 200; char v[max_size]; int top = 0; } // a fellet beptse // brzols

void Stack::push(char c) { /* tlcsorduls ellenrzse s c behelyezse */ } char Stack::pop() { /* alulcsorduls ellenrzse s a legfels elem kiemelse */ }

A felhasznli kd egy harmadik fjlba kerl (user.c). A user.c s a stack.c fjlokban lv kd kzsen hasznlja a stack.h-ban megadott veremfelletet, de a kt fjl egybknt fggetlen s kln-kln lefordthat. A program rszei a kvetkezkppen brzolhatk:

stack.h: veremfellet

user.c: #include "stack.h" verem hasznlata

stack.c: #include "stack.h" verem megvalstsa

A kln fordts kvetelmny minden valdi (tnyleges hasznlatra sznt) program esetben, nem csak a modulris felptseknl (mint pl. a Stack). Pontosabban, a kln fordts hasznlata nem nyelvi kvetelmny; inkbb annak mdja, hogyan lehet egy adott nyelvi megvalsts elnyeit a legjobban kihasznlni. A legjobb, ha a modularitst a lehet legnagyobb mrtkig fokozzuk, nyelvi tulajdonsgok ltal brzoljuk, majd kln-kln hatkonyan fordthat fjlokon keresztl valstjuk meg (8. s 9. fejezet).

Forrs: http://www.doksi.hu

38

Bevezets

2.4.2. Kivtelkezels
Ha egy programot modulokra bontunk, a hibakezelst a modulok szintjn kell elvgeznnk. A krds, melyik modul felels az adott hiba kezelsrt? A hibt szlel modul gyakran nem tudja, mit kell tennie. A helyrelltsi tevkenysg a mveletet kezdemnyez modultl fgg, nem attl, amelyik szlelte a hibt, mikzben megksrelte a mvelet vgrehajtst. A programok nvekedsvel klnsen kiterjedt knyvtrhasznlat esetn a hibk (vagy ltalnosabban: a kivteles esemnyek) kezelsi szabvnyai egyre fontosabb vlnak. Vegyk megint a Stack pldt. Mit kell tennnk, amikor tl sok karaktert prblunk push()sal egy verembe rakni? A verem modul rja nem tudja, hogy a felhasznl mit akar tenni ilyen esetben, a felhasznl pedig nem mindig szleli a hibt (ha gy lenne, a tlcsorduls nem trtnne meg). A megolds: a Stack rja kell, hogy tudatban legyen a tlcsorduls veszlynek s ezutn az (ismeretlen) felhasznlval tudatnia kell ezt. A felhasznl majd megteszi a megfelel lpst:
namespace Stack { void push(char); char pop(); } class Overflow { }; // fellet

// tlcsordulst brzol tpus

Tlcsorduls szlelsekor a Stack::Push() meghvhat egy kivtelkezel kdot; vagyis egy Overflow kivtelt dobhat:
void Stack::push(char c) { if (top == max_size) throw Overflow(); // c behelyezse }

A throw a vezrlst a Stack::Overflow tpus kivtelkezelnek adja t, valamilyen fggvnyben, mely kzvetve vagy kzvetlenl meghvta a Stack::Push()-t. Ehhez visszatekerjk a fggvnyhvsi vermet, ami ahhoz szksges, hogy visszajussunk a hv fggvny krnyezethez. gy a throw gy mkdik, mint egy tbbszint return. Pldul:
void f() { // ... try {

// a kivtelekkel az albb meghatrozott kezel foglalkozik

Forrs: http://www.doksi.hu

2. Kirnduls a C++-ban

39

while (true) Stack::push('c'); } catch (Stack::Overflow) { // hopp: verem-tlcsorduls; a megfelel mvelet vgrehajtsa } // ...

A while ciklus rkk ismtldne, ezrt ha valamelyik Stack::push() hvs egy throw-t vlt ki, a vezrls a Stack::Overflow-t kezel catch rszhez kerl. A kivtelkezel eljrsok hasznlata szablyosabb s olvashatbb teheti a hibakezel kdot. Tovbbi trgyals, rszletek s pldk: 8.3, 14. fejezet, E fggelk.

2.5. Elvont adatbrzols


A modularits alapvet szempont minden sikeres nagy programnl. E knyv minden tervezsi vizsglatban ez marad a kzppontban. Az elzekben lert alak modulok azonban nem elegendek ahhoz, hogy tisztn kifejezznk sszetett rendszereket. Az albbiakban a modulok hasznlatnak egyik mdjval foglalkozunk (felhasznli tpusok ltrehozsa), majd megmutatjuk, hogyan kzdjnk le problmkat a felhasznli tpusok kzvetlen ltrehozsa rvn.

2.5.1. Tpusokat ler modulok


A modulokkal val programozs elvezet az sszes azonos tpus adatnak egyetlen tpuskezel modul ltali kzpontostott kezelshez. Ha pldul sok vermet akarunk az elbbi Stack modulban tallhat egyetlen helyett megadhatunk egy veremkezelt, az albbi fellettel:
namespace Stack { struct Rep; typedef Rep& stack; stack create(); void destroy(stack s); // a verem szerkezetnek meghatrozsa mshol tallhat // j verem ltrehozsa // s trlse

Forrs: http://www.doksi.hu

40

Bevezets
void push(stack s, char c); char pop(stack s); // c behelyezse az s verembe // s legfels elemnek kiemelse

A kvetkez deklarci azt mondja, hogy Rep egy tpus neve, de ksbbre hagyja a tpus meghatrozst (5.7).
struct Rep;

Az albbi deklarci a stack nevet adja egy Rep referencinak (rszletek 5.5-ben).
typedef Rep& stack;

Az tlet az, hogy a vermet sajt Stack::stack-jvel azonostjuk s a tovbbi rszleteket a felhasznl ell elrejtjk. A Stack::stack mkdse nagyon hasonlt egy beptett tpushoz:
struct Bad_pop { }; void f() { Stack::stack s1 = Stack::create(); Stack::stack s2 = Stack::create(); Stack::push(s1,'c'); Stack::push(s2,'k'); if (Stack::pop(s1) != 'c') throw Bad_pop(); if (Stack::pop(s2) != 'k') throw Bad_pop(); Stack::destroy(s1); Stack::destroy(s2);

// j verem ltrehozsa // mg egy verem ltrehozsa

Ezt a Stack-et tbbflekppen megvalsthatnnk. Fontos, hogy a felhasznlnak nem szksges tudnia, hogyan tesszk ezt. Amg a felletet vltozatlanul hagyjuk, a felhasznl nem fogja szrevenni, ha gy dntnk, hogy trjuk a Stack-et. Egy megvalsts pldul elre lefoglalhatna nhny verempldnyt s a Stack::create() egy nem hasznlt pldnyra val hivatkozst adna t. Ezutn a Stack::destroy() egy brzolst nem hasznlt-knt jellhet meg, gy a Stack::create() jra hasznlhatja azt:
namespace Stack { const int max_size = 200; // brzols

Forrs: http://www.doksi.hu

2. Kirnduls a C++-ban

41

struct Rep { char v[max_size]; int top; }; const int max = 16; Rep stacks[max]; bool used[max]; } typedef Rep& stack; // vermek maximlis szma // elre lefoglalt verempldnyok // used[i] igaz, ha stacks[i] hasznlatban van

void Stack::push(stack s, char c) { /* s tlcsordulsnak ellenrzse s c behelyezse */ } char Stack::pop(stack s) { /* s alulcsordulsnak ellenrzse s a legfels elem kiemelse */ } Stack::stack Stack::create() { // hasznlaton kvli Rep kivlasztsa, hasznltknt // megjellse, elksztse, r mutat hivatkozs visszaadsa } void Stack::destroy(stack s) { /* s megjellse nem hasznltknt */ }

Amit tettnk, az brzol tpus becsomagolsa felleti fggvnyek kszletbe. Az, hogy az eredmnyl kapott stack tpus hogyan viselkedik, rszben attl fgg, hogyan adtuk meg ezeket a felleti fggvnyeket, rszben attl, hogyan mutattuk be a Stack-et brzol tpust a verem felhasznlinak, rszben pedig magtl az brzol tpustl. Ez gyakran kevesebb az idelisnl. Jelents problma, hogy az ilyen mtpusoknak a felhasznlk rszre val bemutatsa az brzol tpus rszleteitl fggen nagyon vltoz lehet a felhasznlkat viszont el kell szigetelni az brzol tpus ismerettl. Ha pldul egy jobban kidolgozott adatszerkezetet vlasztottunk volna a verem azonostsra, a Stack::stack-ek rtkadsi s elksztsi (inicializlsi) szablyai drmai mdon megvltoztak volna (ami nha valban kvnatos lehet). Ez azonban azt mutatja, hogy a knyelmes vermek szolgltatsnak problmjt egyszeren ttettk a Stack modulbl a Stack::stack brzol tpusba. Mg lnyegesebb, hogy azok a felhasznli tpusok, melyeket az adott megvalst tpushoz hozzfrst ad modul hatrozott meg, nem gy viselkednek, mint a beptett tpusok, s kisebb vagy ms tmogatst lveznek, mint azok. Azt pldul, hogy mikor hasznlhat egy Stack::Rep, a Stack::create() s a Stack::destroy() fggvny ellenrzi, nem a szoksos nyelvi szablyok.

Forrs: http://www.doksi.hu

42

Bevezets

2.5.2. Felhasznli tpusok


A C++ ezt a problmt gy kzdi le, hogy engedi, hogy a felhasznl kzvetlenl adjon meg tpusokat, melyek kzel gy viselkednek, mint a beptett tpusok. Az ilyen tpusokat gyakran elvont vagy absztrakt adattpusoknak (abstract data type, ADT) nevezzk. A szerz inkbb a felhasznli tpus (user-defined type) megnevezst kedveli. Az elvont adattpus kifejezbb meghatrozshoz absztrakt matematikai lers kellene. Ha adva volna ilyen, azok, amiket itt tpusoknak neveznk, az ilyen valban elvont egyedek konkrt pldnyai lennnek. A programozsi megkzelts most ez lesz:

Dntsd el, mely tpusokra van szksg s mindegyikhez biztosts teljes mveletkszletet.

Ott, ahol egy tpusbl egy pldnynl tbbre nincs szksg, elegend a modulokat hasznl adatrejtsi stlus. Az olyan aritmetikai tpusok, mint a racionlis s komplex szmok, kznsges pldi a felhasznli tpusnak. Vegyk az albbi kdot:
class complex { double re, im; public: complex(double r, double i) { re=r; im=i; } complex(double r) { re=r; im=0; } complex() { re = im = 0; } friend complex operator+(complex, complex); friend complex operator-(complex, complex); friend complex operator-(complex); friend complex operator*(complex, complex); friend complex operator/(complex, complex); friend bool operator==(complex, complex); friend bool operator!=(complex, complex); // ...

// complex ltrehozsa kt skalrbl // complex ltrehozsa egy skalrbl // alaprtelmezett complex: (0,0) // ktoperandus // egyoperandus

// egyenl // nem egyenl

};

A complex osztly (vagyis felhasznli tpus) deklarcija egy komplex szmot s a rajta vgrehajthat mveletek halmazt brzolja. Az brzols privt (private); vagyis a re s az im csak a complex osztly bevezetsekor megadott fggvnyek ltal hozzfrhet. Ezeket az albbi mdon adhatjuk meg:

Forrs: http://www.doksi.hu

2. Kirnduls a C++-ban

43

complex operator+(complex a1, complex a2) { return complex(a1.re+a2.re,a1.im+a2.im); }

Az a tagfggvny, melynek neve megegyezik az osztlyval, a konstruktor. A konstruktor rja le az osztly egy objektumnak elksztsi-ltrehozsi mdjt. A complex osztly hrom konstruktort tartalmaz. Egyikk egy double-bl csinl complex-et, egy msik egy double prbl, a harmadik alaprtelmezett rtk alapjn. A complex osztly gy hasznlhat:
void f(complex z) { complex a = 2.3; complex b = 1/a; complex c = a+b*complex(1,2.3); // ... if (c != b) c = -(b/a)+2*b; }

A fordt a komplex szmokhoz kapcsolt mveleti jeleket megfelel fggvnyhvsokk alaktja. A c!=b jelentse pldul operator!=(c,b), az 1/a jelentse operator/ (complex(1),a). A legtbb de nem minden modul jobban kifejezhet felhasznli tpusknt.

2.5.3. Konkrt tpusok


Felhasznli tpusok vltozatos ignyek kielgtsre kszthetk. Vegynk egy felhasznli veremtpust a complex tpus soraival egytt. Ahhoz, hogy kiss valsghbb tegyk a pldt, ez a Stack tpus paramterknt elemei szmt kapja meg:
class Stack { char* v; int top; int max_size; public: class Underflow { }; class Overflow { }; class Bad_size { }; Stack(int s); ~Stack();

// kivtel // kivtel // kivtel // konstruktor // destruktor

Forrs: http://www.doksi.hu

44

Bevezets

};

void push(char c); char pop();

A Stack(int) konstruktor meghvdik, valahnyszor ltrehozzuk az osztly egy pldnyt. Ez a fggvny gondoskodik a kezdeti rtkadsrl. Ha brmilyen takartsra van szksg, amikor az osztly egy objektuma kikerl a hatkrbl, megadhatjuk a konstruktor ellenttt, a destruktort:
Stack::Stack(int s) // konstruktor { top = 0; if (s<0 || 10000<s) throw Bad_size(); // "||" jelentse "vagy" max_size = s; v = new char[s]; // az elemek szabad trba helyezse } Stack::~Stack() { delete[ ] v; } // destruktor // elemek trlse, hely felszabadtsa jrafelhasznls cljra (6.2.6)

A konstruktor egy j Stack vltozt hoz ltre. Ehhez lefoglal nmi helyet a szabad trbl (heap halom, kupac vagy dinamikus tr) a new opertor hasznlatval. A destruktor takart, felszabadtva a trat. Az egsz a Stack-ek felhasznlinak beavatkozsa nlkl trtnik. A felhasznlk a vermeket ugyangy hozzk ltre s hasznljk, ahogy a beptett tpus vltozkat szoktk. Pldul:
Stack s_var1(10); void f(Stack& s_ref, int i) { Stack s_var2(i); Stack* s_ptr = new Stack(20); s_var1.push('a'); s_var2.push('b'); s_ref.push('c'); s_ptr->push('d'); // ... // 10 elemet trolni kpes globlis verem // hivatkozs a Stack veremre // loklis verem i szm elemmel // mutat a szabad trban lev Stack-re

Ez a Stack tpus ugyanolyan nvadsra, hatkrre, lettartamra, msolsra stb. vonatkoz szablyoknak engedelmeskedik, mint az int vagy a char beptett tpusok.

Forrs: http://www.doksi.hu

2. Kirnduls a C++-ban

45

Termszetszeren a push() s pop() tagfggvnyeket valahol szintn meg kell adni:


void Stack::push(char c) { if (top == max_size) throw Overflow(); v[top] = c; top = top + 1; } char Stack::pop() { if (top == 0) throw Underflow(); top = top - 1; return v[top]; }

A complex s Stack tpusokat konkrt tpusnak nevezzk, ellenttben az absztrakt tpusokkal, ahol a fellet tkletesebben elszigeteli a felhasznlt a megvalsts rszleteitl.

2.5.4. Absztrakt tpusok


Amikor a Stackrl, mint egy modul (2.5.1) ltal megvalstott mtpusrl ttrtnk egy sajt tpusra (2.5.3), egy tulajdonsgot elvesztettnk. Az brzols nem vlik el a felhasznli fellettl, hanem rsze annak, amit be kellene pteni (#include) a vermeket hasznl programrszbe. Az brzols privt, ezrt csak a tagfggvnyeken keresztl hozzfrhet, de jelen van. Ha brmilyen jelents vltozst szenved, a felhasznl jra le kell, hogy fordtsa. Ezt az rat kell fizetni, hogy a konkrt tpusok pontosan ugyangy viselkedjenek, mint a beptettek. Nevezetesen egy tpusbl nem lehetnek valdi loklis (helyi) vltozink, ha nem tudjuk a tpus brzolsnak mrett. Azon tpusoknl, melyek nem vltoznak gyakran, s ahol loklis vltozk gondoskodnak a szksges tisztasgrl s hatkonysgrl, ez elfogadhat s gyakran idelis. Ha azonban teljesen el akarjuk szigetelni az adott verem felhasznljt a megvalsts vltozsaitl, a legutols Stack nem elegend. Ekkor a megolds levlasztani a felletet az brzolsrl s lemondani a valdi loklis vltozkrl. Elszr hatrozzuk meg a felletet:
class Stack { public: class Underflow { }; class Overflow { };

// kivtel // kivtel

Forrs: http://www.doksi.hu

46

Bevezets

};

virtual void push(char c) = 0; virtual char pop() = 0;

A virtual sz a Simulban s a C++-ban azt jelenti, hogy az adott osztlybl szrmaztatott osztlyban ksbb fellrhat. Egy Stack-bl szrmaztatott osztly a Stack felletet valstja meg. A furcsa =0 kifejezs azt mondja, hogy a verembl szrmaztatott osztlynak meg kell hatroznia a fggvnyt. Ilyenformn a Stack felletknt szolgl brmilyen osztly rszre, mely tartalmazza a push() s pop() fggvnyeket.Ezt a Stack-et gy hasznlhatnnk:
void f(Stack& s_ref) { s_ref.push('c'); if (s_ref.pop() != 'c') throw Bad_pop(); }

Vegyk szre, hogyan hasznlja f() a Stack felletet, a megvalsts mikntjrl mit sem tudva. Az olyan osztlyt, mely ms osztlyoknak felletet ad, gyakran tbbalak (polimorf) tpusnak nevezzk. Nem meglep, hogy a megvalsts a konkrt Stack osztlybl mindent tartalmazhat, amit kihagytunk a Stack felletbl:
class Array_stack : public Stack { char* p; int max_size; int top; public: Array_stack(int s); ~Array_stack(); void push(char c); char pop(); // Array_stack megvalstja Stack-et

};

A :public olvashat gy, mint szrmaztatva -bl, megvalstja -t, vagy altpusa -nak. Az f() fggvny rszre, mely a megvalsts ismeretnek teljes hinyban egy Stacket akar hasznlni, valamilyen msik fggvny kell ltrehozzon egy objektumot, amelyen az f() mveletet hajthat vgre:
void g() {

Forrs: http://www.doksi.hu

2. Kirnduls a C++-ban

47

Array_stack as(200); f(as);

Mivel f() nem tud az Array_stack-ekrl, csak a Stack felletet ismeri, ugyanolyan jl fog mkdni a Stack egy msik megvalstsval is:
class List_stack : public Stack { list<char> lc; public: List_stack() { } // List_stack megvalstja Stack-et // (standard knyvtrbeli) karakterlista (3.7.3)

};

void push(char c) { lc.push_front(c); } char pop();

char List_stack::pop() { char x = lc.front(); lc.pop_front(); return x; }

// az els elem lekrse // az els elem eltvoltsa

Itt az brzols egy karakterlista. Az lc.push_front(c) beteszi c-t, mint lc els elemt, az lc.pop_front hvs eltvoltja az els elemet, az lc.front() pedig lc els elemre utal. Egy fggvny ltre tud hozni egy List_stack-et s f() hasznlhatja azt:
void h() { List_stack ls; f(ls); }

2.5.5. Virtulis fggvnyek


Hogyan trtnik az f()-en belli s_ref.pop() hvs feloldsa a megfelel fggvnydefinci hvsra? Amikor h()-bl hvjuk f()-et, a List_stack::pop()-ot kell meghvni, amikor g()-bl, az Array_stack::pop()-ot. Ahhoz, hogy ezt feloldhassuk, a Stack objektumnak informcit kell tartalmaznia arrl, hogy futsi idben mely fggvnyt kell meghvni. A fordtknl szoksos eljrs egy virtulis fggvny nevnek egy tblzat valamely sorszmrtkv alaktsa, amely tblzat fggvnyekre hivatkoz mutatkat tartalmaz. A tblzatot virtulis

Forrs: http://www.doksi.hu

48

Bevezets

fggvnytblnak vagy egyszeren vtbl-nek szoks nevezni. Minden virtulis fggvnyeket tartalmaz osztlynak sajt vtbl-je van, mely azonostja az osztly virtulis fggvnyeit. Ez grafikusan gy brzolhat:

Array_stack objektum: p max_size top List_stack objektum: lc

vtbl:

Array_stack::push() Array_stack::pop()

vtbl:

List_stack::push() List_stack::pop()

A vtbl-ben lv fggvnyek lehetv teszik, hogy az objektumot akkor is helyesen hasznljuk, ha a hv nem ismeri annak mrett s adatainak elrendezst. A hvnak mindssze a vtbl helyt kell tudnia a Stack-en bell, illetve a virtulis fggvnyek sorszmt. Ez a virtulis hvsi eljrs lnyegben ugyanolyan hatkonny tehet, mint a normlis fggvnyhvs. Tbblet helyszksglete: a virtulis fggvnyeket tartalmaz osztly minden objektumban egy-egy mutat, valamint egy-egy vtbl minden osztlyhoz.

2.6. Objektumorientlt programozs


Az elvont adatbrzols a j tervezshez alapfontossg, a knyvben pedig a tervezs vgig kzponti krds marad. A felhasznli tpusok azonban nmagukban nem elg rugalmasak ahhoz, hogy kiszolgljk ignyeinket. E rszben elszr egyszer felhasznli tpusokkal mutatunk be egy problmt, majd megmutatjuk, hogyan lehet azt megoldani osztlyhierarchik hasznlatval.

2.6.1. Problmk a konkrt tpusokkal


A konkrt tpusok a modulokban megadott mtpusokhoz hasonlan egyfajta fekete dobozt rnak le. Ha egy fekete dobozt ltrehozunk, az nem lp igazi klcsnhatsba a program tbbi rszvel. Nincs md arra, hogy j felhasznlshoz igaztsuk, kivve, ha de-

Forrs: http://www.doksi.hu

2. Kirnduls a C++-ban

49

fincijt mdostjuk. Ez a helyzet idelis is lehet, de komoly rugalmatlansghoz is vezethet. Vegyk pldul egy grafikus rendszerben hasznlni kvnt Shape (Alakzat) tpus meghatrozst. Tegyk fel, hogy pillanatnyilag a rendszernek krket, hromszgeket s ngyzeteket kell tmogatnia. Tegyk fel azt is, hogy lteznek az albbiak:
class Point { /* ... */ }; class Color { /* ... */ };

A /* s */ egy megjegyzs kezdett, illetve vgt jelli. A jells tbbsoros megjegyzsekhez is hasznlhat. Egy alakzatot az albbi mdon adhatunk meg:
enum Kind { circle, triangle, square }; // felsorols (4.8) class Shape { Kind k; Point center; Color col; // ... public: void draw(); void rotate(int); // ... }; // tpusmez

A k tpusazonost mez azrt szksges, hogy az olyan mveletek szmra, mint a draw() (rajzols) vagy a rotate() (forgats) meghatrozhatv tegyk, milyen fajta alakzattal van dolguk. (A Pascal-szer nyelvekben egy vltoz rekordtpust hasznlhatnnk, k cmkvel). A draw() fggvnyt gy adhatnnk meg:
void Shape::draw() { switch (k) { case circle: // kr rajzolsa break; case triangle: // hromszg rajzolsa break;

Forrs: http://www.doksi.hu

50

Bevezets

case square: // ngyzet rajzolsa break; }

Ez azonban rendetlensg. A fggvnyeknek mint a draw() tudniuk kell arrl, milyen alakzatfajtk lteznek. Ezrt az ilyen fggvnynl mindig nvekszik a kd, valahnyszor a rendszerhez egy j alakzatot adunk. Ha j alakzatot hozunk ltre, minden mveletet meg kell vizsglni s (lehetsg szerint) mdostani kell azokat. A rendszerhez nem adhatunk j alakzatot, hacsak hozz nem frnk minden mvelet forrskdjhoz. Mivel egy j alakzat hozzadsa magval vonja minden fontos alakzat-mvelet kdjnak mdostst, az ilyen munka nagy gyessget kvn s hibkat vihet be a ms (rgebbi) alakzatokat kezel kdba. Az egyes alakzat-brzolsok kivlasztst komolyan megbnthatja az a kvetelmny, hogy az brzolsoknak (legalbb is nhnynak) illeszkednie kell abba a jellemzen rgztett mret keretbe, melyet az ltalnos Shape tpus lersa kpvisel.

2.6.2. Osztlyhierarchik
A problma az, hogy nincs megklnbztets az egyes alakzatok ltalnos tulajdonsgai (szn, rajzolhatsg stb.) s egy adott alakzatfajta tulajdonsgai kzt. (A kr pldul olyan alakzat, melynek sugara van, egy krrajzol fggvnnyel lehet megrajzolni stb.). E megklnbztets kifejezse s elnyeinek kihasznlsa az objektumorientlt programozs lnyege. Azok a nyelvek, melyek e megklnbztets kifejezst s hasznlatt lehetv tv szerkezetekkel rendelkeznek, tmogatjk az objektumkzpontsgot, ms nyelvek nem. A megoldsrl a Simulbl klcsnztt rkls gondoskodik. Elszr ltrehozunk egy osztlyt, mely minden alakzat ltalnos tulajdonsgait lerja:
class Shape { Point center; Color col; // ... public: Point where() { return center; } void move(Point to) { center = to; /* ... */ draw(); } virtual void draw() = 0; virtual void rotate(int angle) = 0; // ...

};

Forrs: http://www.doksi.hu

2. Kirnduls a C++-ban

51

Akrcsak a 2.5.4 absztrakt Stack tpusban, azokat a fggvnyeket, melyeknl a hvsi fellet meghatrozhat, de a konkrt megvalsts mg nem ismert, virtulisknt (virtual) vezetjk be. A fenti meghatrozs alapjn mr rhatunk ltalnos fggvnyeket, melyek alakzatokra hivatkoz mutatkbl ll vektorokat kezelnek:
void rotate_all(vector<Shape*>& v, int angle) // v elemeinek elforgatsa angle szggel { for (int i = 0; i<v.size(); ++i) v[i]->rotate(angle); }

Egy konkrt alakzat meghatrozshoz meg kell mondanunk, hogy alakzatrl van sz s meg kell hatroznunk konkrt tulajdonsgait (belertve a virtulis fggvnyeket is):
class Circle : public Shape { int radius; public: void draw() { /* ... */ } void rotate(int) {} // igen, res fggvny };

A C++-ban a Circle osztlyrl azt mondjuk, hogy a Shape osztlybl szrmazik (derived), a Shape osztlyrl pedig azt, hogy a Circle osztly se ill. bzisosztlya (alaposztlya, base). Ms szhasznlat szerint a Circle s a Shape alosztly (subclass), illetve fosztly (superclass). A szrmaztatott osztlyrl azt mondjuk, hogy rkli (inherit) a bzisosztly tagjait, ezrt a bzis- s szrmaztatott osztlyok hasznlatt ltalban rklsknt emltjk.A programozsi megkzelts itt a kvetkez:

Dntsd el, mely osztlyokra van szksged, biztosts mindegyikhez teljes mveletkszletet, az rkls segtsgvel pedig hatrold krl pontosan a kzs tulajdonsgokat.

Ahol nincs ilyen kzs tulajdonsg, elegend az elvont adatbrzols. A tpusok kzti, rkls s virtulis fggvnyek hasznlatval kiaknzhat kzssg mrtke mutatja, mennyire alkalmazhat egy problmra az objektumorientlt megkzelts. Nmely terleten, pldul az interaktv grafikban, vilgos, hogy az objektumkzpontsgnak risi le-

Forrs: http://www.doksi.hu

52

Bevezets

hetsgei vannak. Ms terleteken, mint a klasszikus aritmetikai tpusoknl s az azokon alapul szmtsoknl, alig ltszik tbb lehetsg, mint az elvont adatbrzols, gy az objektumkzpontsg tmogatshoz szksges szolgltatsok feleslegesnek tnnek. Az egyes tpusok kzs tulajdonsgait megtallni nem egyszer. A kihasznlhat kzssg mrtkt befolysolja a rendszer tervezsi mdja. Amikor egy rendszert terveznk s akkor is, amikor a rendszerkvetelmnyeket lerjuk aktvan kell keresnnk a kzs tulajdonsgokat. Osztlyokat lehet kifejezetten ms tpusok ptkockiknt tervezni, a ltez osztlyokat pedig meg lehet vizsglni, mutatnak-e olyan hasonlsgokat, amelyeket egy kzs bzisosztlyban kihasznlhatnnk. Az objektumorientlt programozs konkrt programozsi nyelvi szerkezetek nlkl val elemzsre irnyul ksrleteket lsd [Kerr,1987] s [Booch, 1994] a 23.6-ban. Az osztlyhierarchik s az absztrakt osztlyok (2.5.4) nem klcsnsen kizrjk, hanem kiegsztik egymst (12.5), gy az itt felsorolt irnyelvek is inkbb egymst kiegszt, klcsnsen tmogat jellegek. Az osztlyok s modulok pldul fggvnyeket tartalmaznak, mg a modulok osztlyokat s fggvnyeket. A tapasztalt tervez sokfle megkzeltst hasznl ahogy a szksg parancsolja.

2.7. ltalnostott programozs


Ha valakinek egy verem kell, nem felttlenl karaktereket tartalmaz veremre van szksge. A verem ltalnos fogalom, fggetlen a karakter fogalmtl. Kvetkezskppen fggetlenl kell brzolni is. Mg ltalnosabban, ha egy algoritmus az brzolstl fggetlenl s logikai torzuls nlkl kifejezhet, akkor gy is kell tenni. A programozsi irnyelv a kvetkez: Dntsd el, mely algoritmusokra van szksg, s gy lsd el azokat paramterekkel, hogy minl tbb tpussal s adatszerkezettel mkdjenek.

2.7.1. Trolk
Egy karakterverem-tpust ltalnosthatunk, ha sablont (template) hozunk ltre belle s a konkrt char tpus helyett sablonparamtert hasznlunk. Pldul:

Forrs: http://www.doksi.hu

2. Kirnduls a C++-ban

53

template<class T> class Stack { T* v; int max_size; int top; public: class Underflow { }; class Overflow { }; Stack(int s); ~Stack(); void push(T); T pop(); // konstruktor // destruktor

};

A template<class T> eltag T-t az utna kvetkez deklarci paramterv teszi. Hasonlkppen adhatjuk meg a tagfggvnyeket is:
template<class T> void Stack<T>::push(T c) { if (top == max_size) throw Overflow(); v[top] = c; top = top + 1; } template<class T> T Stack<T>::pop() { if (top == 0) throw Underflow(); top = top - 1; return v[top]; }

Ha a defincik adottak, a vermet az albbi mdon hasznlhatjuk:


Stack<char> sc(200); Stack<complex> scplx(30); Stack< list<int> > sli(45); void f() { sc.push('c'); if (sc.pop() != 'c') throw Bad_pop(); scplx.push(complex(1,2)); if (scplx.pop() != complex(1,2)) throw Bad_pop(); // verem 200 karakter szmra // verem 30 komplex szm rszre // verem 45, egszekbl ll lista szmra

Forrs: http://www.doksi.hu

54

Bevezets

Hasonl mdon, sablonokknt adhatunk meg listkat, vektorokat, asszociatv tmbket (map) s gy tovbb. Az olyan osztlyt, amely valamilyen tpus elemek gyjtemnyt tartalmazza, ltalban container class-nak vagy egyszeren trolnak (kontnernek) hvjuk. A sablonoknak fordtsi idben van jelentsgk, teht hasznlatuk a kzzel rott kdhoz kpest nem nveli a futsi idt.

2.7.2. ltalnostott algoritmusok


A C++ standard knyvtra tbbfle trolrl gondoskodik, de a felhasznlk sajtokat is rhatnak (3., 17. s 18. fejezetek). Ismt hasznlhatjuk teht az ltalnostott (generikus) programozs irnyelveit algoritmusok trolk ltali paramterezsre. Tegyk fel, hogy vektorokat, listkat s tmbket akarunk rendezni, msolni s tkutatni, anlkl, hogy minden egyes trolra megrnnk a sort(), copy() s search() fggvnyeket. Konvertlni nem akarunk egyetlen adott adatszerkezetre sem, melyet egy konkrt sort fggvny elfogad, ezrt tallnunk kell egy ltalnos mdot a trolk lersra, mgpedig olyat, amely megengedi, hogy egy trolt anlkl hasznljunk, hogy pontosan tudnnk, milyen fajta trolrl van sz. Az egyik megolds, amelyet a C++ standard knyvtrban a trolk s nem numerikus algoritmusok megkzeltsbl (18. fej. 3.8) vettnk t, a sorozatokra sszpontost s azokat bejrkkal ( iterator) kezeli. me a sorozat fogalmnak grafikus brzolsa:

Kezdet

Vg

elemek:

...

A sorozatnak van egy kezdete s egy vge. A bejr (iterator) valamely elemre hivatkozik s gondoskodik arrl a mveletrl, melynek hatsra legkzelebb a sorozat soron kvetkez elemre fog hivatkozni. A sorozat vge ugyancsak egy bejr, mely a sorozat utols elemn tlra hivatkozik. A vge fizikai brzolsa lehet egy r (sentinel) elem, de elkpzelhet ms is. A lnyeg, hogy a sorozat szmos mdon brzolhat, gy listkkal s tmbkkel is.

Forrs: http://www.doksi.hu

2. Kirnduls a C++-ban

55

Az olyan mveletekhez, mint egy bejr ltal frjnk hozz egy elemhez s a bejr hivatkozzon a kvetkez elemre szksgnk van valamilyen szabvnyos jellsre. Ha az alaptletet megrtettk, az els helyn kzenfekv vlaszts a * dereferencia (hivatkoz vagy mutat) opertort hasznlni, a msodiknl pedig a ++ nvel mveleti jelet. A fentieket adottnak tekintve, az albbi mdon rhatunk kdot:
template<class In, class Out> void copy(In from, In too_far, Out to) { while (from != too_far) { *to = *from; // hivatkozott elemek msolsa ++to; // kvetkez cl ++from; // kvetkez forrs } }

Ez tmsol brmilyen trolt, amelyre a formai kvetelmnyek betartsval bejrt adhatunk meg. A C++ beptett, alacsonyszint tmb s mutat tpusai rendelkeznek a megfelel mveletekkel:
char vc1[200]; char vc2[500]; // 200 karakter tmbje // 500 karakter tmbje

void f() { copy(&vc1[0],&vc1[200],&vc2[0]); }

Ez vc1-et els elemtl az utolsig vc2-be msolja, vc2 els elemtl kezdden. Minden standard knyvtrbeli trol (17. Fej., 16.3) tmogatja ezt a bejr- (iterator) s sorozatjellst. A forrs s a cl tpusait egyetlen paramter helyett kt sablonparamter, az In s Out jelli. Ezt azrt tesszk, mert gyakran akarunk msolni egy fajta trolbl egy msik fajtba. Pldul:
complex ac[200]; void g(vector<complex>& vc, list<complex>& lc) { copy(&ac[0],&ac[200],lc.begin()); copy(lc.begin(),lc.end(),vc.begin()); }

Forrs: http://www.doksi.hu

56

Bevezets

Itt a tmbt a list-be msoljuk, a list-et pedig a vector-ba. Egy szabvnyos trolnl a begin() a bejr (iterator), amely az els elemre mutat.

2.8. Utirat
Egyetlen programozsi nyelv sem tkletes. Szerencsre egy programozsi nyelvnek nem kell tkletesnek lennie ahhoz, hogy j eszkzknt szolgljon nagyszer rendszerek ptshez. Valjban egy ltalnos cl programozsi nyelv nem is lehet minden feladatra tkletes, amire csak hasznljk. Ami egy feladatra tkletes, gyakran komoly fogyatkossgokat mutathat egy msiknl, mivel az egy terleten val tkletessg magval vonja a szakosodst. A C++-t ezrt gy terveztk, hogy j pteszkz legyen a rendszerek szles vlasztkhoz s a fogalmak szles krt kzvetlenl kifejezhessk vele. Nem mindent lehet kzvetlenl kifejezni egy nyelv beptett tulajdonsgait felhasznlva. Valjban ez nem is lenne idelis. A nyelvi tulajdonsgok egy sereg programozsi stlus s mdszer tmogatsra valk. Kvetkezskppen egy nyelv megtanulsnl a feladat a nyelv sajtos s termszetes stlusainak elsajttsra val sszpontosts, nem az sszes nyelvi tulajdonsg minden rszletre kiterjed megrtse. A gyakorlati programozsnl kevs az elnye, ha ismerjk a legrejtettebb nyelvi tulajdonsgokat vagy ha a legtbb tulajdonsgot kihasznljuk. Egyetlen nyelvi tulajdonsg nmagban nem tl rdekes. Csak akkor lesz jelentkeny, ha az egyes programozsi mdszerek s ms tulajdonsgok krnyezetben tekintjk. Amikor teht az Olvas a kvetkez fejezeteket olvassa, krjk, emlkezzen arra, hogy a C++ rszletekbe men vizsglatnak igazi clja az, hogy kpesek legynk egyttesen hasznlni a nyelv szolgltatsait, j programozsi stlusban, egszsges tervezsi krnyezetben.

2.9. Tancsok
[1] Ne essnk ktsgbe! Idvel minden kitisztul. 2.1. [2] J programok rshoz nem kell ismernnk a C++ minden rszlett. 1.7. [3] A programozsi mdszerekre sszpontostsunk, ne a nyelvi tulajdonsgokra. 2.1.

Forrs: http://www.doksi.hu

3
Kirnduls a standard knyvtrban
Minek vesztegessk az idt tanulsra, mikor a tudatlansg azonnali? (Hobbes) Szabvnyos knyvtrak Kimenet Karakterlncok Bemenet Vektorok Tartomnyellenrzs Listk Asszociatv tmbk Trolk (ttekints) Algoritmusok Bejrk Bemeneti/kimeneti bejrk Bejrsok s prediktumok Tagfggvnyeket hasznl algoritmusok Algoritmusok (ttekints) Komplex szmok Vektoraritmetika A standard knyvtr (ttekints) Tancsok

3.1. Bevezets
Nincs olyan jelents program, mely csak a puszta programnyelven rdik. Elszr a nyelvet tmogat knyvtrakat fejlesztik ki, ezek kpezik a tovbbi munka alapjt. A 2. fejezet folytatsaknt ez a fejezet gyors krutazst tesz a f knyvtri szolgltatsokban, hogy fogalmat adjon, mit lehet a C++ s standard knyvtrnak segtsgvel megtenni. Bemutat olyan hasznos knyvtri tpusokat, mint a string, vector, list s map, valamint hasznlatuk legltalnosabb mdjait. Ez lehetv teszi, hogy a kvetkez fejezetekben jobb

Forrs: http://www.doksi.hu

58

Bevezets

pldkat s gyakorlatokat adjak az olvasnak. A 2. fejezethez hasonlan btortani akarom az olvast, ne zavarja, ne kedvetlentse el, ha a rszleteket nem rti tkletesen. E fejezet clja, hogy megzleljk, mi kvetkezik, s megrtsk a leghasznosabb knyvtri szolgltatsok legegyszerbb hasznlatt. A standard knyvtrat rszletesebben a 16.1.2 mutatja be. Az e knyvben lert standard knyvtrbeli szolgltatsok minden teljes C++-vltozat rszt kpezik. A C++ standard knyvtrn kvl a legtbb megvalsts a felhasznl s a program kzti prbeszdre grafikus felhasznli felleteket is knl, melyeket gyakran GUI-knak vagy ablakoz rendszernek neveznek. Hasonlkppen, a legtbb programfejleszt krnyezetet alapknyvtrakkal (foundation library) is ellttk, amelyek a szabvnyos fejlesztsi s/vagy futtatsi krnyezeteket tmogatjk. Ilyeneket nem fogunk lerni. A szndkunk a C++ nll lerst adni, gy, ahogy a szabvnyban szerepel, s megrizni a pldk hordozhatsgt (ms rendszerekre val tltetsnek lehetsgt), kivve a kln megjellteket. Termszetesen biztatjuk az olvast, fedezze fel a legtbb rendszerben meglv, kiterjedt lehetsgeket ezt azonban a gyakorlatokra hagytuk.

3.2. Hell, vilg!


A legkisebb C++ program:
int main() { }

A program megadja a main nev fggvnyt, melynek nincsenek paramterei s nem tesz semmit. Minden C++ programban kell, hogy legyen egy main() nev fggvny. A program e fggvny vgrehajtsval indul. A main() ltal visszaadott int rtk, ha van ilyen, a program visszatrsi rtke a rendszerhez. Ha nincs visszatrsi rtk, a rendszer a sikeres befejezst jelz rtket kap vissza. Ha a main() nem nulla rtket ad vissza, az hibt jelent. A programok jellemzen valamilyen kimenetet lltanak el. me egy program, amely kirja: Hell, vilg!:
#include <iostream> int main() { std::cout << "Hell, vilg!\n"; }

Forrs: http://www.doksi.hu

3. Kirnduls a standard knyvtrban

59

Az #include <iostream> utastja a fordtt, hogy illessze be az iostream-ben tallhat adatfolyam-bemeneti s -kimeneti szolgltatsok deklarcijt a forrskdba. E deklarcik nlkl az albbi kifejezs
std::cout << "Hell, vilg!\n"

rtelmetlen volna. A << (tedd bele) kimeneti opertor msodik operandust berja az elsbe. Ebben az esetben a Hell, vilg!\n karakterliterl a szabvnyos kimeneti adatfolyamba, az std::cout-ba rdik. A karakterliterl egy " " jelek kz zrt karaktersorozat. A benne szerepl \ (backslash, fordtott perjel) az utna kvetkez karakterrel valamilyen egyedi karaktert jell. Esetnkben az \n az j sor jele, teht a kirt Hell, vilg! szveget sortrs kveti.

3.3. A standard knyvtr nvtere


A standard knyvtr az std nvtrhez (2.4, 8.2) tartozik. Ezrt rtunk std::cout-ot cout helyett. Ez egyrtelmen a standard cout hasznlatt rja el, nem valamilyen ms cout-t. A standard knyvtr minden szolgltatsnak beptsrl valamilyen, az <iostream>-hez hasonl szabvnyos fejllomny ltal gondoskodhatunk:
#include<string> #include<list>

Ez rendelkezsre bocstja a szabvnyos string-et s list-et. Hasznlatukhoz az std:: eltagot alkalmazhatjuk:


std::string s = "Ngy lb j, kt lb rossz!"; std::list<std::string> slogans;

Az egyszersg kedvrt a pldkban ritkn rjuk ki az std:: eltagot, illetve a szksges #include <fejllomny>-okat. Az itt kzlt programrszletek fordtshoz s futtatshoz a megfelel fejllomnyokat be kell pteni (#include, amint a 3.7.5, 8.6 s a 16. fejezetben szerepl felsorolsokban szerepelnek). Ezenkvl vagy az std:: eltagot kell hasznlni, vagy globliss kell tenni minden nevet az std nvtrbl (8.2.3):

Forrs: http://www.doksi.hu

60

Bevezets

#include<string> using namespace std;

// a szabvnyos karakterlnc-szolgltatsok elrhetv ttele // std nevek elrhetv ttele az std:: eltag nlkl // rendben: a string jelentse std::string

string s = "A tudatlansg erny!";

ltalban szegnyes zlsre vall egy nvtrbl minden nevet a globlis nvtrbe helyezni. Mindazonltal, a nyelvi s knyvtri tulajdonsgokat illusztrl programrszletek rvidre fogsa rdekben elhagytuk az ismtld #include-okat s std:: minstseket. E knyvben majdnem kizrlag a standard knyvtrat hasznljuk, ha teht egy nevet hasznlunk onnan, azt vagy a szabvny ajnlja, vagy egy magyarzat rsze (hogyan hatrozhat meg az adott szabvnyos szolgltats).

3.4. Kimenet
Az iostream knyvtr minden beptett tpusra meghatroz kimenetet, de felhasznli tpushoz is knnyen megadhatjuk. Alaprtelmezsben a cout-ra kerl kimeneti rtkek karaktersorozatra alaktdnak t. A kvetkez kd pldul az 1 karaktert a 0 karakterrel kvetve a szabvnyos kimeneti adatfolyamba helyezi.
void f() { cout << 10; }

Ugyanezt teszi az albbi kd is:


void g() { int i = 10; cout << i; }

A klnbz tpus kimenetek termszetesen prosthatk:


void h(int i) { cout << "i rtke "; cout << i; cout << '\n'; }

Forrs: http://www.doksi.hu

3. Kirnduls a standard knyvtrban

61

Ha i rtke 10, a kimenet a kvetkez lesz:


i rtke 10

A karakterkonstans egy karakter, egyszeres idzjelek kz zrva. Vegyk szre, hogy a karakterkonstansok nem szmrtkknt, hanem karakterknt rdnak ki:
void k() { cout << 'a'; cout << 'b'; cout << 'c'; }

A fenti kd kimenete pldul abc lesz.Az ember hamar belefrad a kimeneti adatfolyam nevnek ismtlsbe, amikor tbb rokon ttelt kell kirni. Szerencsre maguk a kimeneti kifejezsek eredmnyei felhasznlhatk tovbbi kimenetekhez:
void h2(int i) { cout << "i rtke " << i << '\n'; }

Ez egyenrtk h()-val. Az adatfolyamok rszletes magyarzata a 21. fejezetben tallhat.

3.5. Karakterlncok
A standard knyvtr gondoskodik a string (karakterlnc) tpusrl, hogy kiegsztse a korbban hasznlt karakterliterlokat. A string tpus egy sereg hasznos karakterlnc-mveletet biztost, ilyen pldul az sszefzs :
string s1 = "Hell"; string s2 = "vilg"; void m1() { string s3 = s1 + ", " + s2 + "!\n"; } cout << s3;

Forrs: http://www.doksi.hu

62

Bevezets

Az s3 kezdeti rtke itt a kvetkez karaktersorozat (j sorral kvetve):


Hell, vilg!

A karakterlncok sszeadsa sszefzst jelent. A karakterlncokhoz karakterliterlokat s karaktereket adhatunk. Sok alkalmazsban az sszefzs legltalnosabb formja valamit egy karakterlnc vghez fzni. Ezt a += mvelet kzvetlenl tmogatja:
void m2(string& s1, string& s2) { s1 = s1 + '\n'; // sortrs s2 += '\n'; // sortrs }

A lnc vghez val hozzads kt mdja egyenrtk, de az utbbit elnyben rszestjk, mert tmrebb s valsznleg hatkonyabban valsthat meg.Termszetesen a karakterlncok sszehasonlthatk egymssal s literlokkal is:
string incantation; void respond(const string& answer) { if (answer == incantation) { } else if (answer == "yes") { // ... } // ... //varzssz

// varzsls megkezdse

A standard knyvtr string osztlyt a 20. fejezet rja le. Ez ms hasznos tulajdonsgai mellett lehetv teszi a rszlncok (substring) kezelst is. Pldul:
string name = "Niels Stroustrup"; void m3() { string s = name.substr(6,10); name.replace(0,5,"Nicholas"); }

// s = "Stroustrup" // a nv j rtke "Nicholas Stroustrup" lesz

A substr() mvelet egy olyan karakterlncot ad vissza, mely a paramtereivel megadott rszlnc msolata. Az els paramter egy, a karakterlnc egy adott helyre mutat sorszm,

Forrs: http://www.doksi.hu

3. Kirnduls a standard knyvtrban

63

a msodik a kvnt rszlnc hossza. Mivel a sorszmozs (az index) 0-tl indul, s a Stroustrup rtket kapja.A replace() mvelet a karakterlnc egy rszt helyettesti egy msik karakterlnccal. Ebben az esetben a 0-val indul, 5 hosszsg rszlnc a Niels; ez helyettestdik a Nicholas-szal. A name vgs rtke teht Nicholas Stroustrup. Vegyk szre, hogy a helyettest karakterlncnak nem kell ugyanolyan mretnek lennie, mint az a rszlnc, amelyet helyettest.

3.5.1.C stlus karakterlncok


A C stlus karakterlnc egy nulla karakterrel vgzd karaktertmb (5.5.2). Meg fogjuk mutatni, hogy egy C stlus karakterlncot knnyen bevihetnk egy string-be. A C stlus karakterlncokat kezel fggvnyek meghvshoz kpesnek kell lennnk egy string rtknek C stlus karakterlnc formban val kinyersre. A c_str() fggvny ezt teszi (20.3.7). A name-et a printf() kir C-fggvnnyel (21.8) pldul az albbi mdon rathatjuk ki:
void f() { printf("name: %s\n",name.c_str()); }

3.6. Bemenet
A standard knyvtr bemenetre az istreams-et ajnlja. Az ostreams-hez hasonlan az istreams is a beptett tpusok karaktersorozatknt trtn brzolsval dolgozik s knnyen bvthet, hogy felhasznli tpusokkal is meg tudjon birkzni. A >> (olvasd be) mveleti jelet bemeneti opertorknt hasznljuk; a cin a szabvnyos bemeneti adatfolyam. A >> jobb oldaln ll tpus hatrozza meg, milyen bemenet fogadhat el s mi a beolvas mvelet clpontja. Az albbi kd egy szmot, pldul 1234-et olvas be a szabvnyos bemenetrl az i egsz vltozba s egy lebegpontos szmot, mondjuk 12.34e5-t r a ktszeres pontossg, lebegpontos d vltozba:
void f() { int i; cin >> i; double d; cin >> d;

// egsz szm beolvassa i-be // ktszeres pontossg lebegpontos szm beolvassa d-be

Forrs: http://www.doksi.hu

64

Bevezets

A kvetkez plda hvelykrl centimterre s centimterrl hvelykre alakt. Bemenetknt egy szmot kap, melynek vgn egy karakter jelzi az egysget (centimter vagy hvelyk). A program vlaszul kiadja a msik egysgnek megfelel rtket:
int main() { const float factor = 2.54; float x, in, cm; char ch = 0; cout << "rja be a hosszsgot: "; cin >> x; cin >> ch; switch (ch) { case 'i': in = x; cm = x*factor; break; case 'c': in = x/factor; cm = x; break; default: in = cm = 0; break; } } // lebegpontos szm beolvassa // mrtkegysg beolvassa // inch (hvelyk)

// 1 hvelyk 2.54 cm-rel egyenl

// cm

cout << in << " in = " << cm << " cm\n";

A switch utasts egy rtket hasonlt ssze llandkkal. A break utastsok a switch utastsbl val kilpsre valk. A case konstansoknak egymstl klnbznik kell. Ha az ellenrztt rtk egyikkel sem egyezik, a vezrls a default-ot vlasztja. A programoznak nem kell szksgszeren errl az alaprtelmezett lehetsgrl gondoskodnia. Gyakran akarunk karaktersorozatot olvasni. Ennek knyelmes mdja egy string -be val helyezs:
int main() { string str;

Forrs: http://www.doksi.hu

3. Kirnduls a standard knyvtrban

65

cout << "rja be a nevt!\n"; cin >> str; cout << "Hell, " << str << "!\n";

Ha begpeljk a kvetkezt
Erik

a vlasz az albbi lesz:


Hell, Erik!

Alaprtelmezs szerint az olvasst egy reshely (whitespace) karakter (5.5.2), pldul egy szkz fejezi be, teht ha berjuk a hrhedt kirly nevt
Erik a Vreskez

a vlasz marad
Hell, Erik!

A getline() fggvny segtsgvel egsz sort is beolvashatunk:


int main() { string str; cout << "rja be a nevt!\n"; getline(cin,str); cout << "Hell, " << str << "!\n";

E programmal az albbi bemenet


Erik a Vreskez

a kvnt kimenetet eredmnyezi:


Hell, Erik a Vreskez!

Forrs: http://www.doksi.hu

66

Bevezets

A szabvnyos karakterlncoknak megvan az a szp tulajdonsguk, hogy rugalmasan bvtik a tartalmukat azzal, amit bevisznk, teht ha nhny megabjtnyi pontosvesszt adunk meg, a program valban tbb oldalnyi pontosvesszt ad vissza hacsak gpnk vagy az opercis rendszer valamilyen kritikus erforrsa elbb el nem fogy.

3.7. Trolk
Sok szmts jr klnbz objektumformkbl ll gyjtemnyek (collection) ltrehozsval s kezelsvel. Egy egyszer plda karakterek karakterlncba helyezse, majd a karakterlnc kiratsa. Az olyan osztlyt, melynek f clja objektumok trolsa, ltalnosan trolnak (container, kontner) nevezzk. Adott feladathoz megfelel trolkrl gondoskodni s ezeket tmogatni brmilyen program ptsnl nagy fontossggal br. A standard knyvtr leghasznosabb trolinak bemutatsra nzznk meg egy egyszer programot, amely neveket s telefonszmokat trol. Ez az a fajta program, amelynek vltozatai az eltr htter emberek szmra is egyszernek s maguktl rtetdnek tnnek.

3.7.1. Vektor
Sok C programoz szmra alkalmas kiindulsnak ltszana egy beptett (nv- vagy szm-) prokbl ll tmb:
struct Entry { string name; int number; }; Entry phone_book[1000]; void print_entry(int i) // egyszer hasznlat { cout << phone_book[i].name << ' ' << phone_book[i].number << '\n'; }

A beptett tmbk mrete azonban rgztett. Ha nagy mretet vlasztunk, helyet pazarolunk; ha kisebbet, a tmb tl fog csordulni. Mindkt esetben alacsonyszint trkezel kdot kell rnunk. A standard knyvtr a vector tpust (16.3) bocstja rendelkezsre, amely megoldja a fentieket:

Forrs: http://www.doksi.hu

3. Kirnduls a standard knyvtrban

67

vector<Entry> phone_book(1000); void print_entry(int i) // egyszer hasznlat, mint a tmbnl { cout << phone_book[i].name << ' ' << phone_book[i].number << '\n'; } void add_entries(int n) // mret nvelse n-nel { phone_book.resize(phone_book.size()+n); }

A vector size() tagfggvnye megadja az elemek szmt. Vegyk szre a () zrjelek hasznlatt a phone_book defincijban. Egyetlen vector<Entry> tpus objektumot hoztunk ltre, melynek megadtuk a kezdeti mrett. Ez nagyon klnbzik a beptett tmbk bevezetstl:
vector<Entry> book(1000); vector<Entry> books[1000]; // vektor 1000 elemmel // 1000 res vektor

Ha hibsan [ ] t (szgletes zrjelet) hasznlnnk ott, ahol egy vector deklarlsban ()-t rtettnk, a fordt majdnem biztos, hogy hibazenetet ad, amikor a vector-t hasznlni prbljuk. A vector egy pldnya objektum, melynek rtket adhatunk:
void f(vector<Entry>& v) { vector<Entry> v2 = phone_book; v = v2; // ... }

A vector-ral val rtkads az elemek msolsval jr. Teht f()-ben az elkszts (inicializls) s rtkads utn v s v2 is egy-egy kln msolatot tartalmaz a phone_book-ban lv minden egyes Entry-rl. Ha egy vektor sok elemet tartalmaz, az ilyen rtatlannak ltsz rtkadsok megengedhetetlenl kltsgesek. Ahol a msols nem kvnatos, referencikat (hivatkozsokat) vagy mutatkat kell hasznlni.

Forrs: http://www.doksi.hu

68

Bevezets

3.7.2. Tartomnyellenrzs
A standard knyvtrbeli vector alaprtelmezs szerint nem gondoskodik tartomnyellenrzsrl (16.3.3). Pldul:
void f() { int i = phone_book[1001].number; // ... }

// az 1001 kvl esik a tartomnyon

A kezdeti rtkads valsznleg inkbb valamilyen vletlenszer rtket tesz i-be, mint hogy hibt okoz. Ez nem kvnatos, ezrt a soron kvetkez fejezetekben a vector egy egyszer tartomnyellenrz talaktst fogjuk hasznlni, Vec nven. A Vec olyan, mint a vector, azzal a klnbsggel, hogy out_of_range tpus kivtelt vlt ki, ha egy index kifut a tartomnybl. A Vec-hez hasonl tpusok megvalstsi mdjait s a kivtelek hatkony hasznlatt 11.12, 8.3 s a 14. fejezet trgyalja. Az itteni definci azonban elegend a knyv pldihoz:
template<class T> class Vec : public vector<T> { public: Vec() : vector<T>() { } Vec(int s) : vector<T>(s) { } T& operator[ ](int i) { return at(i); } const T& operator[ ](int i) const { return at(i); } // tartomnyellenrzs // tartomnyellenrzs

};

Az at(i) egy vector indexmvelet, mely out_of_range tpus kivtelt vlt ki, ha paramtere kifut a vector tartomnybl (16.3.3). Visszatrve a nevek s telefonszmok trolsnak problmjhoz, most mr hasznlhatjuk a Vec-et, biztostva, hogy a tartomnyon kvli hozzfrseket elkapjuk:
Vec<Entry> phone_book(1000); void print_entry(int i) // egyszer hasznlat, mint a vektornl { cout << phone_book[i].name << ' ' << phone_book[i].number << '\n'; }

Forrs: http://www.doksi.hu

3. Kirnduls a standard knyvtrban

69

A tartomnyon kvli hozzfrs kivtelt fog kivltani, melyet a felhasznl elkaphat:


void f() { try {

for (int i = 0; i<10000; i++) print_entry(i); } catch (out_of_range) { cout << "tartomnyhiba\n"; }

A kivtel dobsa majd elkapsa akkor trtnik, amikor a phone_book[i]-re i==1000 rtkkel trtnik hozzfrsi ksrlet. Ha a felhasznl nem kapja el ezt a fajta kivtelt, a program meghatrozott mdon befejezdik; nem folytatja futst s nem vlt ki meghatrozatlan hibt. A kivtelek okozta meglepetsek cskkentsnek egyik mdja, ha a main() trzsben egy try blokkot hozunk ltre:
int main() try { // sajt kd } catch (out_of_range) { cerr << "tartomnyhiba\n"; } catch (...) { cerr << "ismeretlen kivtel\n"; }

Ez gondoskodik az alaprtelmezett kivtelkezelkrl, teht ha elmulasztunk elkapni egy kivtelt, a cerr szabvnyos hibakimeneti adatfolyamon hibajelzs jelenik meg (21.2.1).

3.7.3. Lista
A telefonknyv-bejegyzsek beszrsa s trlse ltalnosabb lehet, ezrt egy egyszer telefonknyv brzolsra egy lista jobban megfelelne, mint egy vektor:
list<Entry> phone_book;

Amikor listt hasznlunk, az elemekhez nem sorszm alapjn szeretnnk hozzfrni, ahogy a vektorok esetben ltalban tesszk. Ehelyett tkutathatjuk a listt, adott rtk elemet keresve.

Forrs: http://www.doksi.hu

70

Bevezets

Ehhez kihasznljuk azt a tnyt, hogy a list egy sorozat (3.8):


void print_entry(const string& s) { typedef list<Entry>::const_iterator LI; for (LI i = phone_book.begin(); i != phone_book.end(); ++i) { Entry& e = *i; // rvidts referencival if (s == e.name) { cout << e.name << ' ' << e.number << '\n'; return; } }

Az s keresse a lista elejnl kezddik, s addig folytatdik, mg az s-t megtalljuk vagy elrnk a lista vghez. Minden standard knyvtrbeli trol tartalmazza a begin() s end() fggvnyeket, melyek egy bejrt (itertort) adnak vissza az els, illetve az utols utni elemre (16.3.2). Ha adott egy i bejr, a kvetkez elem ++i lesz. Az i vltoz a *i elemre hivatkozik. A felhasznlnak nem kell tudnia, pontosan milyen tpus egy szabvnyos trol bejrja. A tpus a trol lersnak rsze s nv szerint lehet hivatkozni r. Ha nincs szksgnk egy trolelem mdostsra, a const_iterator az a tpus, ami neknk kell. Klnben a sima iterator tpust (16.3.1) hasznljuk. Elemek hozzadsa egy list-hez igen knny:
void add_entry(const Entry& e, list<Entry>::iterator i) { phone_book.push_front(e); // hozzads a lista elejhez phone_book.push_back(e); // hozzads a lista vghez phone_book.insert(i,e); // hozzads az i' ltal mutatott elem el }

3.7.4. Asszociatv tmbk


Egy nv- vagy szmprokbl ll listhoz keres kdot rni valjban igen fradsgos munka. Ezenkvl a sorban trtn keress a legrvidebb listk kivtelvel nagyon rossz hatkonysg. Ms adatszerkezetek kzvetlenl tmogatjk a beszrst, a trlst s az rtk szerinti keresst. A standard knyvtr nevezetesen a map tpust biztostja erre a feladatra (17.4.1). A map egy rtkpr-trol. Pldul:
map<string,int> phone_book;

Forrs: http://www.doksi.hu

3. Kirnduls a standard knyvtrban

71

Ms krnyezetekben a map mint asszociatv tmb vagy sztr szerepel. Ha els tpusval (a kulccsal, key) indexeljk, a map a msodik tpus (az rtk, vagyis a hozzrendelt tpus, mapped type) megfelel rtkt adja vissza:
void print_entry(const string& s) { if (int i = phone_book[s]) cout << s << ' ' << i << '\n'; }

Ha nem tall illeszkedst az s kulcsra, a phone_book egy alaprtket ad vissza. A map alaprtke int tpusra 0. Itt felttelezzk, hogy a 0 nem rvnyes telefonszm.

3.7.5. Szabvnyos trolk


Az asszociatv tmb, a lista s a vektor mind hasznlhat telefonknyv brzolsra. Mindegyiknek megvannak az ers s gyenge oldalai. A vektorokat indexelni olcs s knny. Msrszt kt eleme kz egyet beszrni kltsgesebb lehet. A lista tulajdonsgai ezzel pontosan ellenttesek. A map emlkeztet egy (kulcsrtk) prokbl ll listra, azzal a kivtellel, hogy rtkei a kulcs szerinti keresshez a legmegfelelbbek. A standard knyvtr rendelkezik a legltalnosabb s leghasznlhatbb troltpusokkal, ami lehetv teszi, hogy a programozk olyan trolt vlasszanak, mely az adott alkalmazs ignyeit a legjobban kiszolglja: Szabvnyos trolk sszefoglalsa Vector<T> list<T> Queue<T> Stack<T> Deque<T> Priority_queue<T> set<T> Multiset<T> Map<kulcs,rtk> Multimap<kulcs,rtk> Vltoz hosszsg vektor (16.3) Ktirny lncolt lista (17.2.2) Sor (17.3.2) Verem (17.3.1) Ktvg sor (17.2.3) rtk szerint rendezett sor (17.3.3) Halmaz (17.4.3) Halmaz, melyben egy rtk tbbszr is elfordulhat (17.4.4) Asszociatv tmb (17.4.1) Asszociatv tmb, melyben egy kulcs tbbszr elfordulhat (17.4.2)

Forrs: http://www.doksi.hu

72

Bevezets

A szabvnyos trolkat 16.2, 16.3 s a 17. fejezet mutatja be. A trolk az std nvtrhez tartoznak, lersuk a <vector>, <list>, <map> stb. fejllomnyokban szerepel. A szabvnyos trolk s alapmveleteik jells szempontjbl hasonlak, tovbb a mveletek jelentse a klnbz trolkra nzve egyforma. Az alapmveletek ltalban mindenfajta trolra alkalmazhatk. A push_back() pldul meglehetsen hatkony mdon egyarnt hasznlhat elemeknek egy vektor vagy lista vghez fzsre, s minden trolnak van size() tagfggvnye, mely visszaadja az elemek a szmt.Ez a jellsbeli s jelentsbeli egysgessg lehetv teszi, hogy a programozk j troltpusokat ksztsenek, melyek a szabvnyos tpusokhoz nagyon hasonl mdon hasznlhatk. A Vec tartomnyellenrzssel elltott vektor (3.7.6) ennek egy pldja. A 17. fejezet bemutatja, hogyan lehet egy hash_map-et a szerkezethez hozztenni. A trolfelletek egysges volta emellett lehetv teszi azt is, hogy az egyes troltpusoktl fggetlenl adjunk meg algoritmusokat.

3.8. Algoritmusok
Az adatszerkezetek, mint a list vagy a vector, nmagukban nem tl hasznosak. Hasznlatukhoz olyan alapvet hozzfrsi mveletekre van szksg, mint az elemek hozzadsa s eltvoltsa. Emellett ritkn hasznlunk egy trolt pusztn trolsra. Rendezzk, kiratjuk, rszhalmazokat vonunk ki bellk, elemeket tvoltunk el, objektumokat keresnk bennk s gy tovbb. Emiatt a standard knyvtr az ltalnos troltpusokon kvl biztostja a trolk legltalnosabb eljrsait is. A kvetkez kdrszlet pldul egy vector-t rendez s minden egyedi vector elem msolatt egy list-be teszi:
void f(vector<Entry>& ve, list<Entry>& le) { sort(ve.begin(),ve.end()); unique_copy(ve.begin(),ve.end(),le.begin()); }

A szabvnyos algoritmusok lerst a 18. fejezetben talljuk. Az algoritmusok elemek sorozatval mkdnek (2.7.2). Az ilyen sorozatok bejr-prokkal brzolhatk, melyek az els, illetve az utols utni elemet adjk meg. A pldban a sort() rendezi a sorozatot, ve.begin()-tl ve.end()-ig, ami ppen a vector sszes elemt jelenti. rshoz csak az els rand elemet szksges megadni. Ha tbb mint egy elemet runk, a kezd elemet kvet elemek fellrdnak. Ha az j elemeket egy trol vghez kvnnnk adni, az albbit rhatnnk:

Forrs: http://www.doksi.hu

3. Kirnduls a standard knyvtrban

73

void f(vector<Entry>& ve, list<Entry>& le) { sort(ve.begin(),ve.end()); unique_copy(ve.begin(),ve.end(),back_inserter(le)); }

// hozzfzs le-hez

A back_inserter() elemeket ad egy trol vghez, bvtve a trolt, hogy helyet csinljon rszkre (19.2.4). A szabvnyos trolk s a back_inserter()-ek kikszblik a hibalehetsget jelent, C stlus realloc()-ot hasznl trkezelst (16.3.5). Ha a hozzfzskor elfelejtjk a back_inserter()-t hasznlni, az hibkhoz vezethet:
void f(vector<Entry>& ve, list<Entry>& le) { copy(ve.begin(),ve.end(),le); copy(ve.begin(),ve.end(),le.end()); copy(ve.begin(),ve.end(),le.begin()); }

// hiba: le nem bejr // rossz: tlr a vgn // elemek fellrsa

3.8.1. Bejrk hasznlata


Amikor elszr tallkozunk egy trolval, megkaphatjuk nhny hasznos elemre hivatkoz bejrjt (itertort); a legjobb plda a begin() s az end(). Ezenkvl sok algoritmus ad vissza bejrkat. A find szabvnyos algoritmus pldul egy sorozatban keres egy rtket s azt a bejrt adja vissza, amely a megtallt elemre mutat. Ha a find-ot hasznljuk, megszmllhatjuk valamely karakter elfordulsait egy karakterlncban:
int count(const string& s, char c) // c elfordulsainak megszmllsa s-ben { int n = 0; string::const_iterator i = find(s.begin(),s.end(),c); while (i != s.end()) { ++n; i = find(i+1,s.end(),c); } return n; }

A find algoritmus valamely rtk egy sorozatban val els elfordulsra vagy a sorozat utols utni elemre mutat bejrt ad vissza. Lssuk, mi trtnik a count egyszer meghvsakor:

Forrs: http://www.doksi.hu

74

Bevezets

void f() { string m = "Mary had a little lamb"; int a_count = count(m,'a'); }

A find() els hvsa megtallja 'a'-t a Mary-ben. A bejr teht e karakterre mutat s nem az s.end()-re, gy belptnk a ciklusba. A ciklusban i+1-gyel kezdjk a keresst; vagyis eggyel a megtallt 'a' utn. Ezutn folytatjuk a ciklust s megtalljuk a msik hrom 'a'-t. A find() ekkor elr a sorozat vghez s az s.end()-et adja vissza, gy nem teljesl az i!=s.end() felttel s kilpnk a ciklusbl. Ezt a count() hvst grafikusan gy brzolhatnnk:

a m b

A nyilak az i kezdeti, kzbens s vgs rtkeit mutatjk. Termszetesen a find algoritmus minden szabvnyos troln egyformn fog mkdni. Kvetkezskppen ugyanilyen mdon ltalnosthatnnk a count() fggvnyt:
template<class C, class T> int count(const C& v, T val) { typename C::const_iterator i = find(v.begin(),v.end(),val); // typename, lsd C.13.5 int n = 0; while (i != v.end()) { ++n; ++i; // az elbb megtallt elem tugrsa i = find(i,v.end(),val); } return n; }

Ez mkdik, gy mondhatjuk:
void f(list<complex>& lc, vector<string>& vc, string s) { int i1 = count(lc,complex(1,3)); int i2 = count(vc,"Diogensz"); int i3 = count(s,'x'); }

Forrs: http://www.doksi.hu

3. Kirnduls a standard knyvtrban

75

A count sablont azonban nem kell definilnunk. Az elemek elfordulsainak megszmllsa annyira ltalnos s hasznos, hogy a standard knyvtr tartalmazza ezt a mveletet. Hogy teljesen ltalnos legyen a megolds, a standard knyvtri count paramterknt trol helyett egy sorozatot kap:
void f(list<complex>& lc, vector<string>& vs, string s) { int i1 = count(lc.begin(),lc.end(),complex(1,3)); int i2 = count(vs.begin(),vs.end(),"Diogensz"); int i3 = count(s.begin(),s.end(),'x'); }

A sorozat hasznlata megengedi, hogy a szmllst beptett tmbre hasznljuk s azt is, hogy egy trol valamely rszt szmlljuk:
void g(char cs[ ], int sz) { int i1 = count(&cs[0],&cs[sz],'z'); int i2 = count(&cs[0],&cs[sz/2],'z'); }

// 'z'-k a tmbben // 'z'-k a tmb els felben

3.8.2. Bejrtpusok
Valjban mik is azok a bejrk (itertorok)? Minden bejr valamilyen tpus objektum. Azonban sok klnbz tpusuk ltezik, mert a bejrnak azt az informcit kell trolnia, melyre az adott troltpusnl feladata elltshoz szksge van. Ezek a bejrtpusok olyan klnbzk lehetnek, mint a trolk s azok az egyedi ignyek, melyeket kiszolglnak. Egy vector bejrja pldul minden bizonnyal egy kznsges mutat, mivel az nagyon sszer hivatkozsi md a vector elemeire:

bejr: vektor:

Forrs: http://www.doksi.hu

76

Bevezets

Egy vector-bejr megvalsthat gy is, mint a vector-ra hivatkoz mutat, meg egy sorszm: bejr: (kezdet == p, pozici == 3)

vektor:

Az ilyen bejrk hasznlata tartomnyellenrzsre is lehetsget ad (19.3). A listk bejrinak valamivel bonyolultabbnak kell lennik, mint egy egyszer mutat a listban trolt elemre, mivel egy ilyen elem ltalban nem tudja, hol van a lista kvetkez tagja. A lista-bejr teht inkbb a lista valamely csompontjra hivatkoz mutat: bejr: p

lista:

csompont

cs.

cs.

cs.

...

elemek:

Ami kzs minden bejrnl, az a jelentsk s a mveleteik elnevezse. A ++ alkalmazsa brmely bejrra pldul olyan bejrt ad, mely a kvetkez elemre hivatkozik. Hasonlkppen * azt az elemet adja meg, melyre a bejr hivatkozik. Valjban bejr lehet brmely objektum, amely nhny, az elzekhez hasonl egyszer szablynak eleget tesz (19.2.1). Tovbb, a felhasznl ritkn kell, hogy ismerje egy adott bejrtpust. Sajt bejr-tpusait minden trol ismeri s azokat az egyezmnyes iterator s const_iterator neveken rendelkezsre bocstja. Pldul a list<Entry>::iterator a list<Entry> ltalnos bejrtpusa. Ritkn kell aggdnunk az adott tpus meghatrozsa miatt.

Forrs: http://www.doksi.hu

3. Kirnduls a standard knyvtrban

77

3.8.3. Bemeneti s kimeneti bejrk


A bejrk fogalma ltalnos s hasznos a trolk elemeibl ll sorozatok kezelsnl. A trolk azonban nem az egyetlen helyet jelentik, ahol elemek sorozatt talljuk. A bemeneti adatfolyamok is rtkek sorozatbl llnak, s rtkek sorozatt rjuk a kimeneti adatfolyamba is. Kvetkezskppen a bejrk hasznosan alkalmazhatk a bemenetnl s kimenetnl is. Egy ostream_iterator ltrehozshoz meg kell hatroznunk, melyik adatfolyamot hasznljuk s milyen tpus objektumokat runk bele. Megadhatunk pldul egy bejrt, mely a cout szabvnyos kimeneti adatfolyamra hivatkozik:
ostream_iterator<string> oo(cout);

Az rtkads *oo-nak azt jelenti, hogy az rtkad adatot a cout-ra rjuk ki. Pldul:
int main() { *oo = "Hell, "; // jelentse cout << "Hell, " ++oo; *oo = "vilg!\n"; // jelentse cout << "vilg!\n" }

Ez egy jabb md szabvnyos zenetek kirsra a szabvnyos kimenetre. A ++oo clja: utnozni egy tmbbe mutatn keresztl trtn rst. A szerz elsnek nem ezt a mdot vlasztan erre az egyszer feladatra, de ez az eszkz alkalmas arra, hogy a kimenetet gy kezeljk, mint egy csak rhat trolt, ami hamarosan magtl rtetd lesz ha eddig mg nem lenne az. Hasonlkppen az istream_iterator olyasvalami, ami lehetv teszi, hogy a bemeneti adatfolyamot gy kezeljk, mint egy csak olvashat trolt. Itt is meg kell adnunk a hasznlni kvnt adatfolyamot s a vrt rtkek tpust:
istream_iterator<string> ii(cin);

Mivel a bejrk mindig prokban brzolnak egy sorozatot, a bemenet vgnek jelzshez egy istream_iterator-rl kell gondoskodni. Az alaprtelmezett istream_iterator a kvetkez:
istream_iterator<string> eos;

Forrs: http://www.doksi.hu

78

Bevezets

Most jra beolvashatnnk a Hell, vilgot!-ot a bemenetrl s kirathatnnk az albbi mdon:


int main() { string s1 = *ii; ++ii; string s2 = *ii; } cout << s1 << ' ' << s2 << '\n';

Valjban az istream_iterator-okat s ostream_ iterator-okat nem kzvetlen hasznlatra talltk ki. Jellemzen algoritmusok paramtereiknt szolglnak. rjunk pldul egy egyszer programot, mely egy fjlbl olvas, az olvasott adatokat rendezi, a ktszer szerepl elemeket eltvoltja, majd az eredmnyt egy msik fjlba rja:
int main() { string from, to; cin >> from >> to; ifstream is(from.c_str()); istream_iterator<string> ii(is); istream_iterator<string> eos; vector<string> b(ii,eos); sort(b.begin(),b.end());

// a forrs- s clfjl nevnek beolvassa // bemeneti adatfolyam (c_str(), lsd 3.5.1 s 20.3.7) // bemeneti bejr az adatfolyam szmra // bemenet-ellenrzs

// b egy vektor, melynek a bemenetrl adunk kezdrtket // az tmeneti tr (b) rendezse

ofstream os(to.c_str()); // kimeneti adatfolyam ostream_iterator<string> oo(os,"\n"); // kimeneti bejr az adatfolyam szmra unique_copy(b.begin(),b.end(),oo); return !is.eof() || !os; // b tartalmnak a kimenetre msolsa, // a kettztt rtkek elvetse // hiballapot visszaadsa (3.2, 21.3.3)

Az ifstream egy istream, mely egy fjlhoz kapcsolhat, az ofstream pedig egy ostream, mely szintn egy fjlhoz kapcsolhat. Az ostream_iterator msodik paramtere a kimeneti rtkeket elvlaszt jel.

Forrs: http://www.doksi.hu

3. Kirnduls a standard knyvtrban

79

3.8.4. Bejrsok s prediktumok


A bejrk lehetv teszik, hogy ciklusokat rjunk egy sorozat bejrsra. A ciklusok megrsa azonban fradsgos lehet, ezrt a standard knyvtr mdot ad arra, hogy egy adott fggvnyt a sorozat minden egyes elemre meghvjunk. Tegyk fel, hogy runk egy programot, mely a bemenetrl szavakat olvas s feljegyzi elfordulsuk gyakorisgt. A karakterlncok s a hozzjuk tartoz gyakorisgok kzenfekv brzolsa egy map-pel trtnhet:
map<string,int> histogram;

Az egyes karakterlncok gyakorisgnak feljegyzsre termszetes mvelet a kvetkez:


void record(const string& s) { histogram[s]++; // "s'' gyakorisgnak rgztse }

Ha beolvastuk a bemenetet, szeretnnk az sszegyjttt adatokat a kimenetre kldeni. A map (string,int) prokbl ll sorozat. Kvetkezskppen szeretnnk meghvni az albbi fggvnyt
void print(const pair<const string,int>& r) { cout << r.first << ' ' << r.second << '\n'; }

a map minden elemre (a prok (pair) els elemt first-nek, msodik elemt second-nak nevezzk). A pair els eleme const string, nem sima string, mert minden map kulcs konstans. A fprogram teht a kvetkez:
int main() { istream_iterator<string> ii(cin); istream_iterator<string> eos; for_each(ii,eos,record); for_each(histogram.begin(),histogram.end(),print);

Forrs: http://www.doksi.hu

80

Bevezets

Vegyk szre, hogy nem kell rendeznnk a map-et ahhoz, hogy a kimenet rendezett legyen. A map rendezve trolja az elemeket s a ciklus is (nvekv) sorrendben jrja vgig a map-et. Sok programozsi feladat szl arrl, hogy meg kell keresni valamit egy trolban, ahelyett, hogy minden elemen vgrehajtannk egy feladatot. A find algoritmus (18.5.2) knyelmes mdot ad egy adott rtk megkeressre. Ennek az tletnek egy ltalnosabb vltozata olyan elemet keres, mely egy bizonyos kvetelmnynek felel meg. Pldul meg akarjuk keresni egy map els 42-nl nagyobb rtkt:
bool gt_42(const pair<const string,int>& r) { return r.second>42; } void f(map<string,int>& m) { typedef map<string,int>::const_iterator MI; MI i = find_if(m.begin(),m.end(),gt_42); // ... }

Mskor megszmllhatnnk azon szavakat, melyek gyakorisga nagyobb, mint 42:


void g(const map<string,int>& m) { int c42 = count_if(m.begin(),m.end(),gt_42); // ... }

Az olyan fggvnyeket, mint a gt_42(), melyet az algoritmus vezrlsre hasznlunk, prediktumnak (lltmny, vezrlfggvny, predicate) nevezzk. Ezek minden elemre meghvdnak s logikai rtket adnak vissza, melyet az algoritmus szndkolt tevkenysgnek elvgzshez felhasznl. A find_if() pldul addig keres, amg a prediktuma true-t nem ad vissza, jelezvn, hogy a krt elemet megtallta. Hasonl mdon a count_if() annyit szmll, ahnyszor a prediktuma true. A standard knyvtr nhny hasznos prediktumot is biztost, valamint olyan sablonokat, melyek tovbbiak alkotsra hasznlhatk (18.4.2).

Forrs: http://www.doksi.hu

3. Kirnduls a standard knyvtrban

81

3.8.5. Tagfggvnyeket hasznl algoritmusok


Sok algoritmus alkalmaz fggvnyt egy sorozat elemeire. Pldul 3.8.4-ben a
for_each(ii,eos,record);

meghvja a record()-ot minden egyes, a bemenetrl beolvasott karakterlncra. Gyakran mutatk trolival van dolgunk, s sokkal inkbb a hivatkozott objektum egy tagfggvnyt szeretnnk meghvni, nem pedig egy globlis fggvnyt, a mutatt paramterknt tadva. Tegyk fel, hogy a Shape::draw() tagfggvnyt akarjuk meghvni egy list<Shape>* elemeire. A plda kezelsre egyszeren egy nem tag fggvnyt runk, mely meghvja a tagfggvnyt:
void draw(Shape* p) { p->draw(); } void f(list<Shape*>& sh) { for_each(sh.begin(),sh.end(),draw); }

A mdszert gy ltalnosthatjuk:
void g(list<Shape*>& sh) { for_each(sh.begin(),sh.end(),mem_fun(&Shape::draw)); }

A standard knyvtri mem_fun() sablon (18.4.4.2) paramterknt egy tagfggvny mutatjt kapja (15.5) s valami olyasmit hoz ltre, amit a tag osztlyra hivatkoz mutatn keresztl hvhatunk meg. A mem_fun(&Shape::draw) eredmnye egy Shape* paramtert kap s visszaadja, amit a Shape::draw() visszaad. A mem_fun() azrt fontos, mert megengedi, hogy a szabvnyos algoritmusokat tbbalak (polimorf) objektumok trolira hasznljuk.

Forrs: http://www.doksi.hu

82

Bevezets

3.8.6. A standard knyvtr algoritmusai


Mi az algoritmus? ltalnos meghatrozsa szerint szablyok vges halmaza, mely adott problmahalmaz megoldshoz mveletek sorozatt hatrozza meg s t fontos jellemzje van: vgessg, meghatrozottsg, bemenet, kimenet, hatkonysg [Knuth,1968, 1.1]. A C++ standard knyvtrnak viszonylatban az algoritmus elemek sorozatn mveleteket vgz sablonok (template-ek) halmaza. A standard knyvtr tbb tucat algoritmust tartalmaz. Az algoritmusok az std nvtrhez tartoznak, lersuk az <algorithm> fejllomnyban szerepel. me nhny, melyeket klnsen hasznosnak talltam:

Vlogatott szabvnyos algoritmusok for_each() find() find_if() count() count_if() replace() replace_if() copy() unique_copy() sort() equal_range() merge() Hvd meg a fggvnyt minden elemre (18.5.1) Keresd meg a paramterek els elfordulst (18.5.2) Keresd meg a prediktumra az els illeszkedst (18.5.2) Szmlld meg az elem elfordulsait (18.5.3) Szmlld meg az illeszkedseket a prediktumra (18.5.3) Helyettestsd be az elemet j rtkkel (18.6.4) Helyettestsd be a prediktumra illeszked elemet j rtkkel (18.6.4) Msold az elemeket (18.6.1) Msold a csak egyszer szerepl elemeket (18.6.1) Rendezd az elemeket (18.7.1) Keresd meg az sszes egyez rtk elemet (18.7.2) Fsld ssze a rendezett sorozatokat (18.7.3)

3.9. Matematika
A C-hez hasonlan a C++ nyelvet sem elssorban szmokkal vgzett mveletekre terveztk. Mindemellett rengeteg numerikus munkt vgeztek C++-ban s ez tkrzdik a standard knyvtrban is.

Forrs: http://www.doksi.hu

3. Kirnduls a standard knyvtrban

83

3.9.1. Komplex szmok


A standard knyvtr a komplex szmok egy tpuscsaldjt tartalmazza, a 2.5.2-ben lert complex osztly alakjban. Az egyszeres pontossg lebegpontos (float), a ktszeres pontossg (double) stb. skalrokat tartalmaz komplex szmok tmogatsra a standard knyvtrbeli complex egy sablon:
template<class scalar> class complex { public: complex(scalar re, scalar im); // ... };

A szoksos aritmetikai mveletek s a leggyakrabban hasznlt matematikai fggvnyek komplex szmokkal is mkdnek:
// szabvnyos exponencilis fggvny a <complex> sablonbl: template<class C> complex<C> pow(const complex<C>&, int); void f(complex<float> fl, complex<double> db) { complex<long double> ld = fl+sqrt(db); db += fl*3; fl = pow(1/fl,2); // ... }

Rszletesebben lsd 22.5.

3.9.2. Vektoraritmetika
A 3.7.1-ben lert vector-t ltalnos rtktrolsra terveztk; kellen rugalmas s illeszkedik a trolk, bejrk s algoritmusok szerkezetbe, ugyanakkor nem tmogatja a matematikai vektormveleteket. Ilyen mveleteket knnyen be lehetett volna pteni a vector-ba, de az ltalnossg s rugalmassg eleve kizr olyan optimalizlsokat, melyeket komolyabb, szmokkal vgzett munknl gyakran lnyegesnek tekintnk. Emiatt a standard knyvtrban megtalljuk a valarray nev vektort is, mely kevsb ltalnos s a szmmveletekhez jobban megfelel:
template<class T> class valarray { // ... T& operator[ ](size_t); // ... };

Forrs: http://www.doksi.hu

84

Bevezets

A size_t eljel nlkli egsz tpus, melyet a nyelv tmbk indexelsre hasznl. A szoksos aritmetikai mveleteket s a leggyakoribb matematikai fggvnyeket megrtk a valarray-kre is:
// szabvnyos abszoltrtk-fggvny a <valarray> sablonbl: template<class T> valarray<T> abs(const valarray<T>&); void f(valarray<double>& a1, valarray<double>& a2) { valarray<double> a = a1*3.14+a2/a1; a2 += a1*3.14; a = abs(a); double d = a2[7]; // ... }

Rszletesebben lsd: 22.4

3.9.3. Alapszint numerikus tmogats


A standard knyvtr a lebegpontos tpusokhoz termszetesen tartalmazza a leggyakoribb matematikai fggvnyeket (log(), pow() s cos(), lsd 2.2.3). Ezenkvl azonban tartalmaz olyan osztlyokat is, melyek beptett tpusok tulajdonsgait pldul egy float kitevjnek lehetsges legnagyobb rtkt rjk le (lsd 22.2).

3.10. A standard knyvtr szolgltatsai


A standard knyvtr szolgltatsait az albbi mdon osztlyozhatjuk: 1. Alapvet futsi idej tmogats (pl. trlefoglals s futsi idej tpusinformci), lsd 16.1.3. 2. A szabvnyos C knyvtr (nagyon csekly mdostsokkal, a tpusrendszer megsrtsnek elkerlsre), lsd 16.1.2. 3. Karakterlncok s bemeneti/kimeneti adatfolyamok (nemzetkzi karakterkszlet s nyelvi tmogatssal), lsd 20. s 21. fejezet. 4. Trolk (vector, list s map) s trolkat hasznl algoritmusok (ltalnos bejrsok, rendezsek s sszefslsek) rendszere, lsd 16., 17., 18. s 19.fejezet.

Forrs: http://www.doksi.hu

3. Kirnduls a standard knyvtrban

85

5. Szmokkal vgzett mveletek tmogatsa (komplex szmok s vektorok aritmetikai mveletekkel), BLAS-szer s ltalnostott szeletek, valamint az optimalizlst megknnyt szerkezetek, lsd 22. fejezet. Annak f felttele, hogy egy osztly bekerlhet-e a knyvtrba, az volt, hogy valamilyen mdon hasznlta-e mr majdnem minden C++ programoz (kezdk s szakrtk egyarnt), hogy ltalnos alakban megadhat-e, hogy nem jelent-e jelents tbbletterhelst ugyanennek a szolgltatsnak valamely egyszerbb vltozathoz viszonytva, s hogy knnyen megtanulhat-e a hasznlata. A C++ standard knyvtra teht az alapvet adatszerkezeteket s az azokon alkalmazhat alapvet algoritmusokat tartalmazza. Minden algoritmus talakts nlkl mkdik minden trolra. Ez az egyezmnyesen STLnek (Standard Template Library, szabvnyos sablonknyvtr) [Stepanov, 1994] nevezett vz bvthet, abban az rtelemben, hogy a felhasznlk a knyvtr rszeknt megadottakon kvl knnyen kszthetnek sajt trolkat s algoritmusokat, s ezeket azonnal mkdtethetik is a szabvnyos trolkkal s algoritmusokkal egytt.

3.11. Tancsok
[1] Ne talljunk fel a melegvizet hasznljunk knyvtrakat. [2] Ne higgynk a csodkban. rtsk meg, mit tesznek knyvtraink, hogyan teszik, s milyen ron teszik. [3] Amikor vlaszthatunk, rszestsk elnyben a standard knyvtrat ms knyvtrakkal szemben. [4] Ne gondoljuk, hogy a standard knyvtr mindenre idelis. [5] Ne felejtsk el bepteni (#include) a felhasznlt szolgltatsok fejllomnyait. 3.3. [6] Ne felejtsk el, hogy a standard knyvtr szolgltatsai az std nvtrhez tartoznak. 3.3. [7] Hasznljunk string-et char* helyett. 3.5, 3.6. [8] Ha ktsgeink vannak, hasznljunk tartomnyellenrz vektort (mint a Vec). 3.7.2. [9] Rszestsk elnyben a vector<T>-t, a list<T>-t s a map<key,value>-t a T[ ]-vel szemben. 3.7.1, 3.7.3, 3.7.4.

Forrs: http://www.doksi.hu

86

Bevezets

[10] Amikor elemeket tesznk egy trolba, hasznljunk push_back()-et vagy back_inserter()-t. 3.7.3, 3.8. [11] Hasznljunk vektoron push_back()-et a realloc() tmbre val alkalmazsa helyett. 3.8. [12] Az ltalnos kivteleket a main()-ben kapjuk el. 3.7.2.

Forrs: http://www.doksi.hu

Els rsz Alapok

Ez a rsz a C++ beptett tpusait s azokat az alapvet lehetsgeket rja le, amelyekkel programokat hozhatunk ltre. A C++-nak a C nyelvre visszautal rsze a hagyomnyos programozsi stlusok tmogatsval egytt kerl bemutatsra, valamint ez a rsz trgyalja azokat az alapvet eszkzket is, amelyekkel C++ programot hozhatunk ltre logikai s fizikai elemekbl.

Fejezetek 4. 5. 6. 7. 8. 9. Tpusok s deklarcik Mutatk, tmbk s struktrk Kifejezsek s utastsok Fggvnyek Nvterek s kivtelek Forrsfjlok s programok

Forrs: http://www.doksi.hu

Forrs: http://www.doksi.hu

4
Tpusok s deklarcik
Ne fogadj el semmit, ami nem tkletes! (ismeretlen szerz) A tkletessg csak az sszeomls pontjn rhet el. (C.N. Parkinson)

Tpusok Alaptpusok Logikai tpusok Karakterek Karakterliterlok Egszek Egsz literlok Lebegpontos tpusok Lebegpontos literlok Mretek void Felsorol tpusok Deklarcik Nevek Hatkrk Kezdeti rtkads Objektumok typedef-ek Tancsok Gyakorlatok

Forrs: http://www.doksi.hu

90

Alapok

4.1. Tpusok
Vegyk az
x = y+f(2);

kifejezst. Hogy ez rtelmes legyen valamely C++ programban, az x, y s f neveket megfelelen definilni kell, azaz a programoznak meg kell adnia, hogy ezek az x, y, s f nev egyedek lteznek s olyan tpusak, amelyekre az = (rtkads), a + (sszeads) s a () (fggvnyhvs) rendre rtelmezettek. A C++ programokban minden nvnek (azonostnak) van tpusa. Ez a tpus hatrozza meg, milyen mveleteket lehet vgrehajtani a nven (azaz az egyeden, amelyre a nv hivatkozik) s ezek a mveletek mit jelentenek. Pldul a
float x; int y = 7; float f(int); // x lebegpontos vltoz // y egsz tpus vltoz, kezdrtke 7 // f egsz paramtert vr s lebegpontos szmot visszaad fggvny

deklarcik mr rtelmess teszik a fenti pldt. Mivel y-t int-knt adtuk meg, rtkl lehet adni, hasznlni lehet aritmetikai kifejezsekben stb. Msfell f-et olyan fggvnyknt hatroztuk meg, amelynek egy int paramtere van, gy meg lehet hvni a megfelel paramterrel. Ez a fejezet az alapvet tpusokat (4.1.1) s deklarcikat (4.9) mutatja be. A pldk csak a nyelv tulajdonsgait szemlltetik, nem felttlenl vgeznek hasznos dolgokat. A terjedelmesebb s valsghbb pldk a ksbbi fejezetekben kerlnek sorra, amikor mr tbbet ismertettnk a C++-bl. Ez a fejezet egyszeren csak azokat az alapelemeket rja le, amelyekbl a C++ programok ltrehozhatk. Ismernnk kell ezeket az elemeket, a velk jr elnevezseket s formai kvetelmnyeket, ahhoz is, hogy valdi C++ programot kszthessnk, de fleg azrt, hogy el tudjuk olvasni a msok ltal rt kdot. A tbbi fejezet megrtshez azonban nem szksges teljesen tltni ennek a fejezetnek minden apr rszlett. Kvetkezskpp az olvas jobban teszi, ha csak tnzi, hogy megrtse a fontosabb fogalmakat, s ksbb visszatr, hogy megrtse a rszleteket, amint szksges.

Forrs: http://www.doksi.hu

4. Tpusok s deklarcik

91

4.1.1. Alaptpusok
A C++ rendelkezik azokkal az alaptpusokkal, amelyek megfelelnek a szmtgp leggyakoribb trolsi egysgeinek s adattrolsi mdszereinek: 4.2 4.3 4.4 4.5 Logikai tpus (bool) Karaktertpusok (mint a char) Egsz tpusok (mint az int) Lebegpontos tpusok (mint a double)

Tovbb a felhasznl megadhat: 4.8 s ltezik a 4.7 void tpus is, melyet az informci hinynak jelzsre hasznlunk. felsorol tpusokat adott rtkhalmazok jellsre (enum)

Ezekbl a tpusokbl ms tpusokat is ltrehozhatunk. Ezek a kvetkezk: 5.1 Mutattpusok (mint az int*) 5.2 Tmbtpusok (mint a char[ ]) 5.5 Referencia-tpusok (mint a double&) 5.7 Adatszerkezetek s osztlyok (10. fejezet) A logikai, karakter- s egsz tpusokat egytt integrlis tpusoknak nevezzk, az integrlis s lebegpontos tpusokat pedig kzsen aritmetikai tpusoknak. A felsorol tpusokat s az osztlyokat (10. fejezet) felhasznli adattpusokknt emlegetjk, mert a felhasznlnak kell azokat meghatroznia; elzetes bevezets nlkl nem llnak rendelkezsre, mint az alaptpusok. A tbbi tpust beptett tpusnak nevezzk. Az integrlis s lebegpontos tpusok tbbfajta mrettel adottak, lehetv tve a programoznak, hogy kivlaszthassa a felhasznlt tr nagysgt, a pontossgot, s a szmtsi rtktartomnyt (4.6). Azt felttelezzk, hogy a szmtgp bjtokat biztost a karakterek trolshoz, gpi szt az egszek trolsra s az azokkal val szmolsra, lteznek alkalmas egyedek lebegpontos szmtsokhoz s cmek szmra, hogy hivatkozhassunk ezekre az egyedekre. A C++ alaptpusai a mutatkkal s tmbkkel egytt az adott nyelvi megvalststl fggetlenl biztostjk ezeket a gpszint fogalmakat a programoz szmra.

Forrs: http://www.doksi.hu

92

Alapok

A legtbb programban a logikai rtkekhez egyszeren bool-t hasznlhatunk, karakterekhez char-t, egszekhez int-et, lebegpontos rtkekhez pedig double-t. A tbbi alaptpus hatkonysgi s ms egyedi clokra hasznlatos, gy legjobb azokat elkerlni addig, amg ilyen ignyek fel nem merlnek, de a rgi C s C++ kdok olvasshoz ismernnk kell ket.

4.2. Logikai tpusok


A logikai tpusoknak (bool), kt rtke lehet: true vagy false (igaz, illetve hamis). A logikai tpusokat logikai mveletek eredmnynek kifejezsre hasznljuk:
void f(int a, int b) { bool b1 = a==b; // ... }

// = rtkads, == egyenlsgvizsglat

Ha a s b rtke ugyanaz, akkor b1 igaz lesz, msklnben hamis. A bool gyakran hasznlatos olyan fggvny visszatrsi rtkeknt, amely valamilyen felttelt ellenriz (prediktum):
bool is_open(File*); bool greater(int a, int b) { return a>b; }

Egsz szmra alaktva a true rtke 1 lesz, a false- pedig 0. Ugyangy az egszek is logikai tpusv alakthatk: a nem nulla egszek true, a 0 false logikai rtkk alakulnak:
bool b = 7; int i = true; // bool(7) igaz, gy b igaz // int(true) rtke 1, gy i rtke 1

Aritmetikai s logikai kifejezsekben a logikai tpusok egssz alakulnak; az aritmetikai s logikai egsz-mveletek az talaktott rtkeken hajtdnak vgre. Ha az eredmny visszaalakul logikai tpusv, a 0 false lesz, a nem nulla egszek pedig true rtket kapnak.

Forrs: http://www.doksi.hu

4. Tpusok s deklarcik

93

void g() { bool a = true; bool b = true; bool x = a+b; bool y = a|b; // a+b rtke 2, gy x igaz // a|b rtke 1, gy y igaz

Logikai tpusv mutatkat is alakthatunk (C.6.2.5). A nem nulla mutatk true, a nulla mutatk false rtkek lesznek.

4.3. Karaktertpusok
A char tpus vltozk egy karaktert trolhatnak az adott nyelvi megvalsts karakterkszletbl:
char ch = 'a';

A char tpus ltalban 8 bites, gy 256 klnbz rtket trolhat. A karakterkszlet jellemzen az ISO-646 egy vltozata, pldul ASCII, gy a billentyzeten megjelen karaktereket tartalmazza. Sok problma szrmazik abbl, hogy ezeket a karakterkszleteket csak rszben szabvnyostottk (C.3). Jelentsen eltrnek azok a karakterkszletek, amelyek termszetes nyelveket tmogatnak, s azok is, amelyek msflekppen tmogatjk ugyanazt a termszetes nyelvet. Itt azonban csak az rdekel minket, hogy ezek a klnbsgek hogyan befolysoljk a C++ szablyait. Ennl fontosabb krds, hogyan programozzunk tbbnyelv, tbb karakterkszletes krnyezetben, ez azonban a knyv keretein tlmutat, br szmos helyen emltsre kerl (20.2, 21.7, C3.3, D). Biztosan feltehet, hogy az adott C++-vltozat karakterkszlete tartalmazza a decimlis szmjegyeket, az angol bc 26 betjt s nhny ltalnos rsjelet. Nem biztos, hogy egy 8 bites karakterkszletben nincs 127-nl tbb karakter (nhny karakterkszlet 255 karaktert biztost), hogy nincs tbb alfabetikus karakter, mint az angolban (a legtbb eurpai nyelvben tbb van), hogy az bc beti sszefggek (az EBCDIC lyukat hagy az i s a j kztt), vagy hogy minden karakter rendelkezsre ll, ami a C++ kd rshoz szks-

Forrs: http://www.doksi.hu

94

Alapok

ges (nhny nemzeti karakterkszlet nem biztostja a { } [ ] | \ karaktereket, C.3.1). Amikor csak lehetsges, nem szabad semmit feltteleznnk az objektumok brzolsrl s ez az ltalnos szably a karakterekre is vonatkozik. Minden karakternek van egy egsz rtke, a b- pldul az ASCII karakterkszletben 98. me egy kis program, amely a begpelt karakter egsz rtkt mutatja meg:
#include <iostream> int main() { char c; std::cin >> c; std::cout << "A(z) ' " << c << " ' rtke " << int(c) << '\n'; }

Az int(c) jells a c karakter egsz rtkt adja. Az a lehetsg, hogy karaktert egssz lehet alaktani, felvet egy krdst: a char eljeles vagy eljel nlkli? A 8 bites bjton brzolt 256 rtket gy lehet rtelmezni, mint 0-tl 255-ig vagy -127-tl 127-ig terjed rtkeket. Sajnos az adott fordtprogram dnti el, melyiket vlasztja egy sima char esetben (C.1, C.3.4). A C++ azonban ad kt olyan tpust, amelyekre a krds biztosan megvlaszolhat: a signed char-t (eljeles karakter), amely legalbb a -127 s127 kzti rtkeket kpes trolni s az unsigned char (eljel nlkli karakter) tpust, amely legalbb 0-tl 255-ig tud rtkeket trolni. Szerencsre csak a 0-127 tartomnyon kvli rtkekben lehet klnbsg s a leggyakoribb karakterek a tartomnyon bell vannak. Azok a 0-127 tartomnyon tli rtkek, amelyeket egy sima char trol, nehezen felderthet hordozhatsgi problmkat okozhatnak. Lsd mg a C.3.4-et arra az esetre, ha tbbfle char tpus szksges, vagy ha char tpus vltozkban szeretnnk egszeket trolni. A nagyobb karakterkszletek pldul a Unicode karaktereinek trolsra a wchar_t ll rendelkezsnkre, amely nll tpus. Mrete az adott C++-vltozattl fgg, de elg nagy ahhoz, hogy a szksges legnagyobb karakterkszletet trolhassa (lsd 21.7 s C.3.3). A klns nv mg a C-bl maradt meg. A C-ben a wchar_t egy typedef (4.9.7), vagyis tpus-lnv, nem pedig beptett tpus. Az _t toldalk a szabvnyos typedef-ektl val megklnbztetst segti. Jegyezzk meg, hogy a karaktertpusok integrlis tpusok (4.1.1), gy alkalmazhatak rjuk az aritmetikai s logikai mveletek (6.2) is.

Forrs: http://www.doksi.hu

4. Tpusok s deklarcik

95

4.3.1. Karakterliterlok
A karakterliterl, melyet gyakran karakterkonstansnak is hvnak, egy egyszeres idzjelek kz zrt karakter, pldul 'a' s '0'. A karakterliterlok tpusa char. Valjban szimbolikus konstansok (jelkpes llandk), melyek rtke annak a szmtgpnek a karakterkszletben lv karakter egsz rtke, amin a C++ program fut. Ha pldul ASCII karakterkszlettel rendelkez szmtgpet hasznlunk, a '0' rtke 48 lesz. A program hordozhatsgt javtja, ha decimlis jells helyett karakterliterlokat hasznlunk. Nhny karakternek szintn van szabvnyos neve, ezek a \ fordtott perjelt hasznljk n. escape karakterknt. Pldul a \n az j sort, a \t pedig a vzszintes tabultort (behzst) jelenti. Az escape karakterekrl rszletesebben lsd C.3.2-t. A szles karakterliterlok Lab alakak, ahol az egyszeres idzjelek kztt lv karakterek szmt s jelentst az adott C++-megvalsts a wchar_t tpushoz igaztja, mivel a szles karakterliterlok tpusa wchar_t.

4.4. Egsz tpusok


A char-hoz hasonlan az egsz tpusok is hromflk: sima int, signed int, s unsigned int. Az egszek hrom mretben adottak: short int, sima int , illetve long int. Egy long intre lehet sima long-knt hivatkozni. Hasonlan, a short a short int, az unsigned az unsigned int, a signed pedig a signed int szinonimja. Az eljel nlkli (unsigned) egsz tpusok olyan felhasznlsra idelisak, amely gy kezeli a trat, mint egy bittmbt. Szinte soha nem j tlet az eljel nlkli tpust hasznlni int helyett, hogy egy vagy tbb bitet nyerjnk pozitv egszek trolshoz. Azok a prblkozsok, amelyek gy ksrlik meg biztostani valamilyen rtk nem negatv voltt, hogy a vltozt unsigned-knt adjk meg, ltalban meghisulnak a mgttes konverzis szablyok miatt (C.6.1, C.6.2.1). A sima char-ral ellenttben a sima int-ek mindig eljelesek. A signed int tpusok csak vilgosabban kifejezett szinonimi a nekik megfelel sima int tpusoknak.

Forrs: http://www.doksi.hu

96

Alapok

4.4.1. Egsz literlok


Az egsz literlok ngyfle alakban fordulnak el: decimlis, oktlis, hexadecimlis s karakterliterlknt. A decimlis literlok a leginkbb hasznlatosak s gy nznek ki, ahogy elvrjuk tlk:
7 1234 976 12345678901234567890

A fordtprogramnak figyelmeztetnie kell olyan literlok esetben, amelyek tl hosszak az brzolshoz. A nullval kezdd s x-szel folytatd (0x) literlok hexadecimlis (16-os szmrendszerbeli) szmok. Ha a literl nullval kezddik s szmjeggyel folytatdik, oktlis (8-as szmrendszerbeli) szmrl van sz:
decimlis: oktlis: hexadecimlis: 0 0x0 2 02 0x2 63 077 0x3f 83 0123 0x53

Az a, b, c, d, e s f betk, illetve nagybets megfelelik rendre 10-et, 11-et, 12-t, 13-at, 14et s 15-t jelentenek. Az oktlis s hexadecimlis jells leginkbb bitmintk kifejezsnl hasznos. Meglepetseket okozhat, ha ezekkel a jellsekkel valdi szmokat fejeznk ki. Egy olyan gpen pldul, ahol az int egy kettes komplemens 16 bites egszknt van brzolva, 0xffff a -1 negatv decimlis szm lesz. Ha tbb bitet hasznltunk volna az egsz brzolsra, akkor ez 65 535 lett volna. Az U uttag hasznlatval eljel nlkli (unsigned) literlokat adhatunk meg. Hasonlan, az L uttag hasznlatos a long literlokhoz. Pldul 3 egy int, 3U egy unsigned int s 3L egy long int. Ha nincs megadva uttag, a fordt egy olyan egsz literlt ad, amelynek tpusa megfelel az rtknek s a megvalsts egsz-mreteinek (C.4). J tlet korltozni a nem maguktl rtetd llandk hasznlatt nhny, megjegyzsekkel megfelelen elltott const (5.4) vagy felsorol tpus (4.8) kezdeti rtkadsra.

4.5. Lebegpontos tpusok


A lebegpontos tpusok lebegpontos (vals) szmokat brzolnak. Az egszekhez hasonlan ezek is hromfajta mretek lehetnek: float (egyszeres pontossg), double (ktszeres pontossg), s long double (kiterjesztett pontossg).

Forrs: http://www.doksi.hu

4. Tpusok s deklarcik

97

Az egyszeres, ktszeres s kiterjesztett pontossg pontos jelentse az adott C++-vltozattl fgg. A megfelel pontossg kivlasztsa egy olyan problmnl, ahol fontos a vlaszts, a lebegpontos szmtsok mly megrtst kveteli meg. Ha nem rtnk a lebegpontos aritmetikhoz, krjnk tancsot, sznjunk idt a megtanulsra, vagy hasznljunk double-t s remljk a legjobbakat.

4.5.1. Lebegpontos literlok


Alaprtelmezs szerint a lebegpontos literlok double tpusak. A fordtnak itt is figyelmeztetnie kell, ha a lebegpontos literlok az brzolshoz kpest tl nagyok. me nhny lebegpontos literl:
1.23 .23 0.23 1. 1.0 1.2e10 1.23e-15

Jegyezzk meg, hogy szkz nem fordulhat el egy lebegpontos literl kzepn. A 65.43 e-21 pldul nem lebegpontos literl, hanem ngy klnll nyelvi egysg (ami formai hibt okoz):
65.43 e 21

Ha float tpus lebegpontos literlt akarunk megadni, akkor azt az f vagy F uttag hasznlatval tehetjk meg:
3.14159265f 2.0f 2.997925F 2.9e-3f

Ha long double tpus lebegpontos literlt szeretnnk megadni, hasznljuk az l vagy L uttagot:
3.14159265L 2.0L 2.997925L 2.9e-3L

4.6. Mretek
A C++ alaptpusainak nhny jellemzje, mint pldul az int mrete, a C++ adott megvalststl fgg (C.2). Rmutatok ezekre a fggsgekre s gyakran ajnlom, hogy kerljk ket vagy tegynk lpseket annak rdekben, hogy hatsukat cskkentsk. Mirt kellene ezzel foglalkozni? Azok, akik klnbz rendszereken programoznak vagy tbbfle ford-

Forrs: http://www.doksi.hu

98

Alapok

tt hasznlnak, knytelenek trdni ezzel, mert ha nem tennk, rknyszerlnnek arra, hogy idt pazaroljanak nehezen megfoghat programhibk megtallsra s kijavtsra. Azok, akik azt lltjk, hogy nem rdekli ket a hordozhatsg, ltalban azrt teszik ezt, mert csak egy rendszert hasznlnak s gy rzik, megengedhetik maguknak azt a hozzllst, miszerint a nyelv az, amit a fordtm megvalst. Ez beszklt ltsmd. Ha egy program sikeres, akkor valszn, hogy tviszik ms rendszerre, s valakinek meg kell tallnia s ki kell javtania a megvalsts sajtossgaibl add problmkat. A programokat tovbb gyakran jra kell fordtani ms fordtkkal ugyanarra a rendszerre s mg a kedvenc fordtnk ksbbi vltozata is mskppen csinlhat nhny dolgot, mint a mostani. Sokkal egyszerbb ismerni s korltozni az adott fordt hasznlatnak hatst, amikor egy programot megrnunk, mint megprblni ksbb kibogozni a problmt. A megvalstsbl ered nyelvi sajtossgok hatst viszonylag knny korltozni, a rendszerfgg knyvtrakt azonban sokkal nehezebb. Az egyik mdszer az lehet, hogy lehetleg csak a standard knyvtr elemeit hasznljuk. Annak, hogy tbb egsz, tbb eljel nlkli, s tbb lebegpontos tpus van, az az oka, hogy ez lehetsget ad a programoznak, hogy a hardver jellemzit megfelelen kihasznlhassa. Sok gpen jelents klnbsgek vannak a memriaignyben, a memria hozzfrsi idejben, s a tbbfajta alaptpussal val szmolsi sebessgben. Ha ismerjk a gpet, ltalban knnyen kivlaszthatjuk pldul a megfelel egsz tpust egy adott vltoz szmra, igazn hordozhat kdot rni azonban sokkal nehezebb. A C++ objektumainak mrete mindig a char mretnek tbbszrse, gy a char mrete 1. Egy objektum mrett a sizeof opertorral kaphatjuk meg (6.2). Az alaptpusok mretre vonatkozan a kvetkezk garantltak:
1 sizeof(char) sizeof(short) sizeof(int) sizeof(long) 1 sizeof(bool) sizeof(long) sizeof(char) sizeof(wchar_t) sizeof(long) sizeof(float) sizeof(double) sizeof(long double) sizeof(N) sizeof(signed N) sizeof(unsigned N)

A fentiekben N lehet char, short int, int vagy long int, tovbb biztostott, hogy a char legalbb 8, a short legalbb 16, a long pedig legalbb 32 bites. A char a gp karakterkszletbl egy karaktert trolhat. Az albbi bra az alaptpusok egy lehetsges halmazt s egy minta-karakterlncot mutat:

Forrs: http://www.doksi.hu

4. Tpusok s deklarcik

99

char: bool: short: int: int*: double: char[14]:

'a' 1 756 100000000 &c1 1234567e34 Hello, world!\0

Ugyanilyen mretarnyban (0,5 cm egy bjt) egy megabjt memria krlbell t kilomternyire lgna ki a jobb oldalon. A char tpust az adott nyelvi vltozatnak gy kell megvlasztania, hogy a karakterek trolsra s kezelsre egy adott szmtgpen a legmegfelelbb legyen; ez jellemzen egy 8 bites bjt. Hasonlan, az int tpusnak a legmegfelelbbnek kell lennie az egszek trolsra s kezelsre; ez ltalban egy 4 bjtos (32 bites) gpi sz. Nem blcs dolog tbbet felttelezni. Pldul vannak olyan gpek, ahol a char 32 bites. Ha szksgnk van r, az adott C++vltozat egyedi tulajdonsgait megtallhatjuk a <limits> fejllomnyban (22.2). Pldul:
#include <limits> #include <iostream> int main() { std::cout << "A legnagyobb lebegpontos szm == " << std::numeric_limits<float>::max() << ", a char eljeles == " << std::numeric_limits<char>::is_signed << '\n'; }

Az alaptpusok rtkadsokban s kifejezsekben szabadon prosthatk. Ahol lehetsges, az rtkek gy alaktdnak t, hogy ne legyen adatveszts (C.6). Ha v rtk pontosan brzolhat egy T tpus vltozban, akkor v rtk T tpusv alaktsa megrzi az rtket s nincs problma. Legjobb, ha elkerljk azokat az eseteket, amikor a konverzik nem rtkrzk (C.6.2.6). Nagyobb programok ksztshez az automatikus konverzikat rszletesebben meg kell rtennk, fleg azrt, hogy kpesek legynk rtelmezni a msok ltal rt kdot, a kvetkez fejezetek olvasshoz ugyanakkor ez nem szksges.

Forrs: http://www.doksi.hu

100

Alapok

4.7. Void
A void formja alapjn alaptpus, de csak egy bonyolultabb tpus rszeknt lehet hasznlni; nincsenek void tpus objektumok. Vagy arra hasznljuk, hogy meghatrozzuk, hogy egy fggvny nem ad vissza rtket, vagy akkor, amikor egy mutat ismeretlen tpus objektumra mutat:
void x; void f(); void* pv; // hiba: nincsenek void objektumok // az f fggvny nem ad vissza rtket (7.3) // ismeretlen tpus objektumra hivatkoz mutat (5.6)

Amikor egy fggvnyt bevezetnk, meg kell hatrozni visszatrsi rtknek tpust is. Logikailag elvrhat lenne, hogy a visszatrsi tpus elhagysval jelezzk, a fggvny nem ad vissza rtket. Ez viszont a nyelvtant (A fggelk) kevsb szablyoss tenn s tkzne a C-beli gyakorlattal. Kvetkezskppen a void ltszlagos visszatrsi tpus-knt hasznlatos, annak jellsre, hogy a fggvny nem ad vissza rtket.

4.8. Felsorol tpusok


A felsorol tpus (enumeration) olyan tpus, amely felhasznl ltal meghatrozott rtkeket tartalmaz. Meghatrozsa utn az egsz tpushoz hasonlan hasznlhat. A felsorol tpusok tagjaiknt nvvel rendelkez egsz konstansokat adhatunk meg. Az albbi kd pldul hrom egsz llandt ad meg ezeket felsorol konstansoknak nevezzk s rtkeket rendel hozzjuk:
enum { ASM, AUTO, BREAK };

Alaprtelmezs szerint az llandk 0-tl nvekven kapnak rtkeket, gy ASM==0, AUTO==1, BREAK==2. A felsorol tpusnak lehet neve is:
enum keyword { ASM, AUTO, BREAK };

Minden felsorols nll tpus. A felsorol konstansok tpusa a felsorolsi tpus lesz. Az AUTO pldul keyword tpus.

Forrs: http://www.doksi.hu

4. Tpusok s deklarcik

101

Ha keyword tpus vltozt adunk meg sima int helyett, mind a felhasznlnak, mind a fordtnak utalunk a vltoz tervezett hasznlatra:
void f(keyword key) { switch (key) { case ASM: // valamit csinlunk break; case BREAK: // valamit csinlunk break; } }

A fordt figyelmeztetst adhat, mert a hrom keyword tpus rtkbl csak kettt kezeltnk. A felsorol konstans a kezdeti rtkadskor integrlis tpus (4.1.1) konstans kifejezssel (C.5.) is megadhat. A felsorol tpus rtkhalmaza sszes tagjnak rtkt tartalmazza, felkerektve a 2 legkzelebbi, azoknl nagyobb hatvnynl eggyel kisebb rtkig. Ha a legkisebb felsorol konstans nem negatv, az rtkhalmaz 0-val kezddik, ha negatv, a 2 legkzelebbi, a tagoknl kisebb negatv hatvnyval. Ez a szably azt a legkisebb bitmezt adja meg, amely tartalmazhatja a felsorol konstansok rtkt. Pldul:
enum e1 { dark, light }; enum e2 { a = 3, b = 9 }; enum e3 { min = -10, max = 1000000 }; // tartomny: 0:1 // tartomny: 0:15 // tartomny: -1048576:1048575

Egy integrlis tpus rtkt meghatrozott mdon felsorol tpusv alakthatjuk. Ha az rtk nem esik a felsorol tpus rtkhalmazba, a konverzi eredmnye nem meghatrozott:
enum flag { x=1, y=2, z=4, e=8 }; flag f1 = 5; flag f2 = flag(5); flag f3 = flag(z|e); flag f4 = flag(99); // tartomny: 0:15

// tpushiba: 5 nem flag tpus // rendben: flag(5) flag tpus s annak tartomnyn belli // rendben: flag(12) flag tpus s annak tartomnyn belli // meghatrozatlan: 99 nem esik a flag tartomnyn bellre

Az utols rtkads mutatja, mirt nincs automatikus konverzi egszrl felsorol tpusra; a legtbb egsz rtk ugyanis nem brzolhat egy adott felsorol tpusban.

Forrs: http://www.doksi.hu

102

Alapok

A felsorol tpusok rtkhalmaznak fogalma klnbzik a Pascal nyelvcsaldba tartoz nyelvek felsorol tpusainak fogalmtl. A C-ben s a C++-ban azonban hossz hagyomnya van azoknak a bitkezel mveleteknek, amelyek arra ptenek, hogy a felsorol tpusok tagjain kvli rtkek jl krlhatroltak. A felsorol tpusok sizeof-ja egy olyan integrlis tpus sizeof-ja, amely kpes a felsorol tpus rtkhalmazt trolni s nem nagyobb sizeof(int)-nl, hiszen akkor nem lehetne intknt vagy unsigned int-knt brzolni. Pldul sizeof(e1) lehet 1 vagy taln 4, de nem lehet 8 egy olyan gpen, ahol sizeof(int)==4. Alaprtelmezs szerint a felsorol tpusok aritmetikai mveletek esetben egssz alaktdnak (6.2). A felsorol tpus felhasznli tpus, gy a felhasznlk a felsorolsra sajt mveleteket adhatnak meg, pldul a ++ s << opertorokkal (11.2.3).

4.9. Deklarcik
A C++ programokban a neveket (azonostkat) hasznlat eltt be kell vezetnnk, azaz meg kell hatroznunk tpusukat, hogy megmondjuk a fordtnak, a nv mifle egyedre hivatkozik. A deklarcik sokflesgt a kvetkez pldk szemlltetik:
char ch; string s; int count = 1; const double pi = 3.1415926535897932385; extern int error_number; char* name = "Natasa"; char* season[ ] = { "tavasz", "nyr", "sz", "tl" }; struct Date { int d, m, y; }; int day(Date* p) { return p->d; } double sqrt(double); template<class T> T abs(T a) { return a<0 ? -a : a; } typedef complex<short> Point; struct User; enum Beer { Carlsberg, Tuborg, Thor }; namespace NS { int a; }

Forrs: http://www.doksi.hu

4. Tpusok s deklarcik

103

Amint a pldkbl lthat, a deklarci tbbet is jelenthet annl, mint hogy egyszeren egy nevet kapcsol ssze a nv tpusval. A fenti deklarcik tbbsge definci is, azaz meg is hatrozza azt az egyedet, amelyre a nv hivatkozik. A ch esetben pldul ez az egyed a megfelel memriaterlet, amelyet vltozknt hasznlunk (vagyis ezt a memriaterletet fogjuk lefoglalni), a day-nl a meghatrozott fggvny, a pi llandnl a 3.1415926535897932385 rtk, a Date-nl egy j tpus. A Point esetben az egyed a complex<short> tpus, gy a Point a complex<short> szinonimja lesz. A fenti deklarcik kzl csak a
double sqrt(double); extern int error_number; struct User;

deklarcik nem defincik is egyben: azaz mshol kell definilni (meghatrozni) azokat az egyedeket, amelyekre hivatkoznak. Az sqrt fggvny kdjt (trzst) ms deklarcikkal kell meghatrozni, az int tpus error_number vltoz szmra az error_number egy msik deklarcijnak kell lefoglalnia a memrit, s a User tpus egy msik deklarcijnak kell megadnia, hogy a tpus hogy nzzen ki. Pldul:
double sqrt(double d) { /* ... */ } int error_number = 1; struct User { /* ... */ };

A C++ programokban minden nv szmra mindig pontosan egy definci (meghatrozs) ltezhet (az #include hatsait lsd 9.2.3-ban). Ugyanakkor a nevet tbbszr is deklarlhatunk (bevezethetjk). Egy egyed minden deklarcija meg kell, hogy egyezzen a hivatkozott egyed tpusban. gy a kvetkez rszletben kt hiba van:
int count; int count; extern int error_number; extern short error_number; // hiba: jbli definci // hiba: nem megfelel tpus

A kvetkezben viszont egy sincs (az extern hasznlatrl lsd 9.2):


extern int error_number; extern int error_number;

Forrs: http://www.doksi.hu

104

Alapok

Nhny definci valamilyen rtket is meghatroz a megadott egyedeknek:


struct Date { int d, m, y; }; typedef complex<short> Point; int day(Date* p) { return p->d; } const double pi = 3.1415926535897932385;

Tpusok, sablonok, fggvnyek s llandk esetben ez az rtk nem vltozik. Nem konstans adattpusok esetben a kezdeti rtket ksbb mdosthatjuk:
void f() { int count = 1; char* name = "Bjarne"; // ... count = 2; name = "Marian"; }

A defincik kzl csak az albbi nem hatroz meg rtket:


char ch; string s;

(Arrl, hogy hogyan s mikor kap egy vltoz alaprtelmezett rtket, lsd 4.9.5-t s 10.4.2-t.) Minden deklarci, amely rtket hatroz meg, egyben defincinak is minsl.

4.9.1. A deklarcik szerkezete


A deklarcik ngy rszbl llnak: egy nem ktelez minstbl, egy alaptpusbl, egy deklartorbl, s egy szintn nem ktelez kezdrtk-ad kifejezsbl. A fggvny- s nvtr-meghatrozsokat kivve a deklarci pontosvesszre vgzdik:
char* kings[ ] = { "Antignusz", "Szeleukusz", "Ptolemaiosz" };

Itt az alaptpus char, a deklartor a *kings[ ], a kezdrtk-ad rsz pedig az ={}. A minst (specifier) egy kulcssz, mint a virtual (2.5.5, 12.2.6) s az extern (9.2), s a bevezetett elem nhny, nem a tpusra jellemz tulajdonsgt hatrozza meg. A deklartor (declarator) egy nvbl s nhny nem ktelez opertorbl ll. A leggyakoribb deklartor-opertorok a kvetkezk (A.7.1):

Forrs: http://www.doksi.hu

4. Tpusok s deklarcik

105

* *const & [] ()

mutat konstans mutat referencia tmb fggvny

eltag eltag eltag uttag uttag

Hasznlatuk egyszer lenne, ha mindegyikk eltagknt (prefix) vagy mindegyikk uttagknt (postfix) hasznlt opertor volna. A *, a [ ] s a () opertorokat azonban arra terveztk, hogy kifejezsekben is hasznlhatk legyenek (6.2), gy a * eltag-, a [ ] s a () pedig uttag opertorok. Az uttagknt hasznlt opertorok tbb megktssel jrnak, mint az eltagknt hasznltak. Kvetkezskppen a *kings[ ] egy valamire hivatkoz mutatkbl ll vektor, s zrjeleket kell hasznlnunk, ha olyasmit akarunk kifejezni, mint fggvnyre hivatkoz mutat (lsd az 5.1 pldit). Teljes rszletessggel lsd a nyelvtant az A fggelkben. Jegyezzk meg, hogy a tpus nem hagyhat el a deklarcibl:
const c = 7; gt(int a, int b) { return (a>b) ? a : b; } unsigned ui; long li; // hiba: nincs tpus // hiba: nincs visszatrsi tpus

// rendben: 'unsigned' jelentse 'unsigned int' // rendben: 'long' jelentse 'long int'

A szabvnyos C++ ebben eltr a C s a C++ rgebbi vltozataitl, amelyek megengedtk az els kt pldt, azt felttelezve, hogy a tpus int, ha nincs tpus megadva (B.2). Ez az implicit int szably sok flrerts s nehezen megfoghat hiba forrsa volt.

4.9.2. Tbb nv bevezetse


Egyetlen deklarciban tbb nevet is megadhatunk. A deklarci ekkor vesszvel elvlasztott deklarcik listjt tartalmazza. Kt egszet pldul gy vezethetnk be:
int x, y; // int x s int y;

Jegyezzk meg, hogy az opertorok csak egyes nevekre vonatkoznak, az ugyanabban a deklarciban szerepl tovbbi nevekre nem:
int* p, y; int x, *q; int v[10], *pv; // int* p s int y, NEM int* y // int x s int* q // int v[10] s int* pv

A fentihez hasonl szerkezetek rontjk a program olvashatsgt, ezrt kerlendk.

Forrs: http://www.doksi.hu

106

Alapok

4.9.3. Nevek
A nv (azonost) betk s szmok sorozatbl ll. Az els karakternek betnek kell lennie. Az _ (alhzs) karaktert betnek tekintjk. A C++ nem korltozza a nvben hasznlhat karakterek szmt. A fordtprogram rja azonban a megvalsts egyes rszeire nincs befolyssal (konkrtan a szerkesztprogramra, a linker-re), ez pedig hatrokat szab. Nhny futsi idej krnyezet ugyancsak szksgess teszi, hogy kibvtsk vagy megszortsuk az azonostban elfogadhat karakterkszletet. A bvtsek (pldul a $ engedlyezse egy nvben) nem hordozhat programokat eredmnyeznek. A C++ kulcsszavai (A fggelk), mint a new s az int, nem hasznlhatk felhasznli egyedek neveknt. Pldk a nevekre:
hello DEFINED var0 ez_egy_szokatlanul_hossz_nv foO bAr u_name LoPatko var1 CLASS _class ___

s nhny plda olyan karaktersorozatokra, amelyek nem hasznlhatk azonostknt:


012 fizetes.esedekes egy bolond foo~bar $sys .name class if 3var

Az alhzssal kezdd nevek a nyelvi megvalsts s a futsi idej krnyezet egyedi eszkzei szmra vannak fenntartva, gy ezeket nem szabadna hasznlni alkalmazi programokban. Amikor a fordt olvassa a programot, mindig a leghosszabb olyan sorozatot keresi, amely kiadhat egy nevet. gy a var10 s nem a var nv (amit a 10-es szm kvet). Hasonlan, az elseif is egy nv, nem pedig az else, amit az if kulcssz kvet. A kis- s nagybetket a nyelv megklnbzteti, gy a Count s a count klnbz nevek, de nem blcs dolog olyan neveket vlasztani, amelyek csak a kezdbetben trnek el. A legjobb elkerlni azokat a neveket, amelyek csak kicsit klnbznek. Pldul a nagybets o (O) s a nulla (0) nehezen megklnbztethet, a kis L (l) s az egyes (1) szintn. Kvetkezskppen azonostnak a l0, lO, l1 s ll nem szerencss vlaszts. A nagy hatkr nevek lehetleg hosszak s rthetek legyenek, mint vector, Window_with_border, s Department_number. A kd viszont rthetbb lesz, ha a kis hatkrben hasznlt neveknek rvid, hagyomnyos stlus nevk van, mint x, i s p. Az osztlyok (10. fejezet) s a nvterek (8.2) hasznlhatk arra, hogy a hatkrk kicsik maradjanak. Hasznos dolog viszonylag rvidnek hagyni a gyakran hasznlt neveket, az igazn hosszakat

Forrs: http://www.doksi.hu

4. Tpusok s deklarcik

107

pedig megtartani a kevsb gyakran hasznlatos egyedeknek. Vlasszuk meg gy a neveket, hogy az egyed jelentsre s ne annak megvalstsra utaljanak. A phone_book (telefonknyv) pldul jobb, mint a number_list (szmok listja), mg akkor is, ha a telefonszmokat listban (3.7) troljuk. A j nevek megvlasztsa is egyfajta mvszet. Prbljunk kvetkezetes elnevezsi stlust fenntartani. Pldul rjuk nagy kezdbetvel a nem standard knyvtrbeli felhasznli tpusokat s kisbetvel azokat a neveket, amelyek nem tpusnevek (pldul Shape s current_token). Tovbb hasznljunk csupa nagybett makrk esetben (ha makrkat kell hasznlnunk, pldul HACK) s hasznljunk alhzst, ha az azonostban szt akarjuk vlasztani a szavakat. Akrhogy is, nehz elrni a kvetkezetessget, mivel a programokat ltalban klnbz forrsokbl vett rszletek alkotjk s szmos klnbz sszer stlus hasznlatos bennk. Legynk kvetkezetesek a rvidtsek s betszavak hasznlatban is.

4.9.4. Hatkrk
A deklarci a megadott nevet egy hatkrbe (scope) vezeti be, azaz a nevet csak a programszveg meghatrozott rszben lehet hasznlni. A fggvnyeken bell megadott nevek esetben (ezeket gyakran loklis vagy helyi nvnek hvjuk) ez a hatkr a deklarci helytl annak a blokknak a vgig tart, amelyben a deklarci szerepel. A blokk olyan kdrsz, amelyet a { } kapcsos zrjelek hatrolnak. Egy nevet globlisnak neveznk, ha fggvnyen, osztlyon (10. fejezet) vagy nvtren (8.2) kvl bevezetett. A globlis nevek hatkre a bevezets pontjtl annak a fjlnak a vgig terjed, amelyben a deklarci szerepel. A blokkokban szerepl nvdeklarcik a krlvev blokkban lv deklarcikat s a globlis neveket elfedhetik, azaz egy nevet jra meg lehet adni gy, hogy egy msik egyedre hivatkozzon egy blokkon bell. A blokkbl val kilps utn a nv visszanyeri elz jelentst:
int x; void f() { int x; x = 1; { } int x; x = 2; // globlis x

// a loklis x elfedi a globlis x-et // rtkads a loklis x-nek // elfedi az els loklis x-et // rtkads a msodik loklis x-nek

Forrs: http://www.doksi.hu

108

Alapok

x = 3;

// rtkads az els loklis x-nek // a globlis x cmnek felhasznlsa

int* p = &x;

A nevek elfedse elkerlhetetlen nagy programok rsakor. A kdot olvasnak azonban knnyen elkerli a figyelmt, hogy egy nv tbbszr szerepel. Mivel az ilyen hibk viszonylag ritkn fordulnak el, nagyon nehezen lehet azokat megtallni. Kvetkezskppen a nvelfedsek szmt a lehet legkisebbre kell cskkenteni. Ha valaki olyan neveket hasznl globlis vltozknt vagy egy hosszabb fggvny loklis vltozjaknt, mint i s x, akkor maga keresi a bajt. Az elfedett globlis nevekre a :: hatkrjelz hasznlatval hivatkozhatunk:
int x; void f2() { int x = 1; ::x = 2; x = 2; // ... }

// a globlis x elfedse // rtkads a globlis x-nek // rtkads a loklis x-nek

Elfedett loklis nv hasznlatra nincs md. A nv hatkre a nv deklarcijnak pontjtl kezddik; azaz a teljes deklartor utn s a kezdeti rtk(ek)et megad rsz eltt. Ez azt jelenti, hogy egy nevet sajt kezdrtknek meghatrozsra is hasznlhatunk:
int x; void f3() { int x = x; }

// perverz: kezdeti rtkads x-nek sajt maga (nem meghatrozott) rtkvel

Ez nem tiltott, csak butasg. Egy j fordtprogram figyelmeztetst ad, ha egy vltozt azeltt hasznlunk, mieltt rtkt belltottuk volna (lsd 5.9[9]).

Forrs: http://www.doksi.hu

4. Tpusok s deklarcik

109

Egy nvvel egy blokkban kt klnbz objektumra a :: opertor hasznlata nlkl is hivatkozhatunk:
int x = 11; void f4() { int y = x; int x = 22; y = x; } // perverz: // a globlis x felhasznlsa, y = 11 // a loklis x felhasznlsa, y = 22

A fggvnyparamterek neveit gy tekintjk, mint ha a fggvny legkls blokkjban lennnek megadva, gy az albbi hibs, mert x-et ugyanabban a hatkrben ktszer adtuk meg:
void f5(int x) { int x; }

// hiba

Ilyen hiba gyakran elfordul, rdemes figyelnnk r.

4.9.5. Kezdeti rtkads


Ha egy objektumhoz kezdrtk-ad kifejezst adunk meg, akkor ez hatrozza meg az objektum kezdeti rtkt. Ha nincs megadva ilyen, a globlis (4.9.4), nvtr (8.2), vagy helyi statikus objektumok (7.1.2, 10.2.4) (melyeket egyttesen statikus objektumoknak neveznk) a megfelel tpus 0 rtkt kapjk kezdrtkl:
int a; double d; // jelentse "int a = 0;'' // jelentse "double d = 0.0;''

A loklis vltozknak (ezeket nha automatikus objektumoknak nevezzk) s a szabad trban ltrehozott objektumoknak (dinamikus vagy heap objektumok) alaprtelmezs szerint nincs kezdrtkk:
void f() { int x; // ... }

// x rtke nem meghatrozott

Forrs: http://www.doksi.hu

110

Alapok

A tmbk s struktrk tagjai alaprtelmezs szerint kapnak kezdrtket, fggetlenl attl, hogy az adott szerkezet statikus-e vagy sem. A felhasznli tpusokhoz magunk is megadhatunk alaprtelmezett kezdrtket (10.4.2). A bonyolultabb objektumoknak egynl tbb rtkre van szksgk a kezdeti rtkadshoz. A tmbk s struktrk C tpus feltltsekor (5.2.1, 5.7) ezt egy { } zrjelek ltal hatrolt listval rhetjk el. A konstruktorral rendelkez felhasznli tpusoknl a fggvny stlus paramterlistk hasznlatosak (2.5.2, 10.2.3). Jegyezzk meg, hogy a deklarcikban szerepl () res zrjelek jelentse mindig fggvny:
int a[ ] = { 1, 2 }; Point z(1,2); int f(); // tmb kezdeti rtkadsa // fggvny stlus kezdeti rtkads (konstruktorral) // fggvny-deklarci

4.9.6. Objektumok s balrtkek


Nvvel nem rendelkez vltozkat is lefoglalhatunk s hasznlhatunk, s rtket is adhatunk nekik furcsa kifejezsekkel (pl. *p[a+10]=7). Kvetkezskpp el kellene neveznnk azt, hogy valami a memriban. Ez az objektum legegyszerbb s legalapvetbb fogalma. Azaz, az objektum egy folytonos trterlet; a bal oldali rtk (balrtk) pedig egy olyan kifejezs, amely egy objektumra hivatkozik. A balrtk (lvalue) szt eredetileg arra alkottk, hogy a kvetkezt jelentse: valami, ami egy rtkads bal oldaln szerepelhet. Nem minden balrtk lehet azonban az rtkads bal oldaln, egy balrtk hivatkozhat llandra (const) is (5.5). A nem const-knt megadott balrtket szoks mdosthat balrtknek (modifiable lvalue) is nevezni. Az objektumnak ezt az egyszer s alacsony szint fogalmt nem szabad sszetveszteni az osztlyobjektumok s tbbalak (polimorf tpus) objektumok (15.4.3) fogalmval. Hacsak a programoz mskpp nem rendelkezik (7.1.2, 10.4.8), egy fggvnyben bevezetett vltoz akkor jn ltre, amikor defincijhoz rkeznk, s akkor sznik meg, amikor a neve a hatkrn kvlre kerl (10.4.4). Az ilyen objektumokat automatikus objektumoknak nevezzk. A globlis s nvtr-hatkrben bevezetett objektumok s a fggvnyekben vagy osztlyokban megadott static objektumok (csak) egyszer jnnek ltre s kapnak kezdeti rtket, s a program befejeztig lnek(10.4.9). Az ilyen objektumokat statikus objektumoknak nevezzk. A tmbelemeknek s a nem statikus struktrk vagy osztlyok tagjainak az lettartamt az az objektum hatrozza meg, amelynek rszei. A new s delete opertorokkal olyan objektumok hozhatk ltre, amelyek lettartama kzvetlenl szablyozhat (6.2.6).

Forrs: http://www.doksi.hu

4. Tpusok s deklarcik

111

4.9.7. Typedef
Az a deklarci, amit a typedef kulcssz elz meg, a tpus szmra j nevet hoz ltre, nem egy adott tpus vltozt:
typedef char* Pchar; Pchar p1, p2; char* p3 = p1; // p1 s p2 tpusa char*

Az gy megadott, gyakran typedef-nek (ltpus) nevezett nv knyelmes rvidts lehet egy nehezen hasznlhat nv helyett. Pldul az unsigned char tlsgosan hossz az igazn gyakori hasznlatra, ezrt megadhatjuk a szinonimjt, az uchar-t:
typedef unsigned char uchar;

A typedef msik hasznlata az, hogy egy tpushoz val kzvetlen hozzfrst egy helyre korltozunk:
typedef int int32; typedef short int16;

Ha most az int32-t hasznljuk, amikor egy viszonylag nagy egszre van szksgnk, programunkat tvihetjk egy olyan gpre, ahol a sizeof(int) 2-vel egyenl, gy, hogy a kdban egyszer szerepl int32-t most mskpp hatrozzuk meg:
typedef long int32;

Vgezetl, a typedef-ek inkbb ms tpusok szinonimi, mint nll tpusok. Kvetkezskppen a typedef-ek szabadon felcserlhetk azokkal a tpusokkal, melyeknek szinonimi. Azok, akik ugyanolyan jelentssel vagy brzolssal rendelkez nll tpusokat szeretnnek, hasznljk a felsorol tpusokat (4.8) vagy az osztlyokat (10. fejezet).

Forrs: http://www.doksi.hu

112

Alapok

4.10. Tancsok
[1] A hatkrk legyenek kicsik. 4.9.4. [2] Ne hasznljuk ugyanazt a nevet egy hatkrben s az azt krlvev hatkrben is. 4.9.2. [3] Deklarcinknt (csak) egy nevet adjunk meg. 4.9.3. [4] A gyakori s helyi nevek legyenek rvidek, a nem helyi s ritkn hasznlt nevek hosszabbak. 4.9.3. [5] Kerljk a hasonlnak ltsz neveket. 4.9.3. [6] Elnevezsi stlusunk legyen kvetkezetes. 4.9.3. [7] Figyeljnk arra, hogy a nvvlaszts inkbb a jelentsre, mintsem a megvalstsra utaljon. 4.9.3. [8] Ha a beptett tpus, amelyet egy rtk brzolsra hasznlunk, megvltozhat, hasznljunk typedef-et, gy a tpus szmra beszdes nevet adhatunk. 4.9.7 [9] A typedef-ekkel tpusok szinonimit adjuk meg; j tpusok definilsra hasznljunk felsorol tpusokat s osztlyokat. 4.9.7. [10] Emlkezznk arra, hogy minden deklarciban szksges a tpus megadsa (nincs implicit int). 4.9.1. [11] Kerljk a karakterek szmrtkvel kapcsolatos szksgtelen felttelezseket. 4.3.1, C.6.2.1. [12] Kerljk az egszek mretvel kapcsolatos szksgtelen felttelezseket. 4.6. [13] Kerljk a szksgtelen felttelezseket a lebegpontos tpusok rtkkszletvel kapcsolatban is. 4.6. [14] Rszestsk elnyben a sima int-et a short int-tel vagy a long int-tel szemben. 4.6. [15] Rszestsk elnyben a double-t a float-tal vagy a long double-lal szemben. 4.5. [16] Rszestsk elnyben a sima char-t a signed char-ral s az unsigned char-ral szemben. C.3.4. [17] Kerljk az objektumok mretvel kapcsolatos szksgtelen felttelezseket. 4.6. [18] Kerljk az eljel nlkli aritmetikt. 4.4. [19] Legynk vatosak az eljelesrl eljel nlklire s unsigned-rl signed-ra val talaktssal. C.6.2.6. [20] Legynk vatosak a lebegpontos tpusrl egszre val talaktssal. C.6.2.6. [21] Legynk vatosak a kisebb tpusokra val talaktsokkal (pldul int-rl char-ra). C.6.2.6.

Forrs: http://www.doksi.hu

4. Tpusok s deklarcik

113

4.11. Gyakorlatok
1. (*2) Futtassuk le a Hell, vilg! programot (3.2). Ha a program fordtsa nem gy sikerl, ahogy kellene, olvassuk el B.3.1-et. 2. (*1) 4.9 minden deklarcijra vgezzk el a kvetkezket: ha a deklarci nem definci, rjunk hozz defincit. Ha a deklarci definci is, rjunk olyan deklarcit, ami nem az. 3. (*1.5) rjunk programot, amely kirja az alaptpusok, nhny szabadon vlasztott mutattpus s nhny szabadon vlasztott felsorol tpus mrett. Hasznljuk a sizeof opertort. 4. (*1.5) rjunk programot, amely kirja az 'a''z' betket s a '0''9' szmjegyeket, valamint a hozzjuk tartoz egsz rtkeket. Vgezzk el ugyanezt a tbbi kirhat karakterre is. Csinljuk meg ugyanezt hexadecimlis jellssel. 5. (*2) Mi a rendszernkn a legnagyobb s legkisebb rtke a kvetkez tpusoknak: char, short, int, long, float, double, long double s unsigned? 6. (*1) Mi a leghosszabb loklis nv, amit a C++ programokban hasznlhatunk a rendszernkben? Mi a leghosszabb kls nv, amit a C++ programokban hasznlhatunk a rendszernkben? Van-e megszorts a nevekben hasznlhat karakterekre? 7. (*2) Rajzoljunk brt az egsz s alaptpusokrl, ahol egy tpus egy msik tpusra mutat, ha az els minden rtke minden szabvnyos megvalstsban brzolhat a msik tpus rtkeknt. Rajzoljuk meg az brt kedvenc C++vltozatunk tpusaira is.

Forrs: http://www.doksi.hu

5
Mutatk, tmbk s struktrk
A fensges s a nevetsges gyakran annyira sszefggnek, hogy nehz ket sztvlasztani. (Tom Paine) Mutatk Nulla Tmbk Karakterliterlok Tmbre hivatkoz mutatk Konstansok Mutatk s konstansok Referencik void* Struktrk Tancsok Gyakorlatok

5.1. Mutatk
Ha T egy tpus, T* a T-re hivatkoz mutat tpus lesz, azaz egy T* tpus vltoz egy T tpus objektum cmt tartalmazhatja. Pldul:
char c = 'a'; char* p = &c; // a p a c cmt trolja

Forrs: http://www.doksi.hu

116

Alapok

brval:

p:

&c c: 'a'

Sajnos a tmbkre s fggvnyekre hivatkoz mutatk esetben bonyolultabb jells szksges:


int* pi; char** ppc; int* ap[15]; int (*fp)(char*); int* f(char*); // mutat egszre // mutat char-ra hivatkoz mutatra // egszre hivatkoz mutatk 15 elem tmbje // char* paramter fggvnyre hivatkoz mutat; egszet ad vissza // char* paramter fggvny; egszre hivatkoz mutatt ad vissza

Lsd 4.9.1-et a deklarcik formai kvetelmnyeire vonatkozan, s az A fggelket a teljes nyelvtannal kapcsolatban. A mutatn vgezhet alapvet mvelet a dereferencia, azaz a mutat ltal mutatott objektumra val hivatkozs. E mveletet indirekcinak (kzvetett hasznlatnak, hivatkozsnak) is hvjk. Az indirekci jele az eltagknt hasznlt egyoperandus * :
char c = 'a'; char* p = &c; char c2 = *p; // a p a c cmt trolja // c2 == 'a'

A p ltal mutatott vltoz c, a c-ben trolt rtk 'a', gy c2 rtke 'a' lesz.A tmbelemekre hivatkoz mutatkon aritmetikai mveleteket is vgezhetnk (5.3), a fggvnyekre hivatkoz mutatk pedig vgtelenl hasznosak, ezeket a 7.7 pontban trgyaljuk. A mutatk clja, hogy kzvetlen kapcsolatot teremtsenek annak a gpnek a cmzsi eljrsaival, amin a program fut. A legtbb gp bjtokat cmez meg. Azok, amelyek erre nem kpesek, olyan hardverrel rendelkeznek, amellyel a bjtokat gpi szavakbl nyerik ki. Msrszrl kevs gp tud kzvetlenl egy bitet megcmezni, kvetkezskpp a legkisebb objektum, amely szmra nllan memrit foglalhatunk s amelyre beptett tpus mutatval hivatkozhatunk, a char. Jegyezzk meg, hogy a bool legalbb annyi helyet foglal, mint a char (4.6). Ahhoz, hogy a kisebb rtkeket tmrebben lehessen trolni, logikai opertorokat (6.2.4) vagy struktrkban lev bitmezket (C.8.1) hasznlhatunk.

Forrs: http://www.doksi.hu

5. Mutatk, tmbk s struktrk

117

5.1.1. Nulla
A nulla (0) az int tpusba tartozik. A szabvnyos konverziknak (C.6.2.3) ksznheten a 0 integrlis (4.1.1), lebegpontos, mutat, vagy tagra hivatkoz mutat tpus konstansknt is hasznlhat. A tpust a krnyezet dnti el. A nullt ltalban (de nem szksgszeren) a megfelel mret csupa nulla bitminta jelli.Nincs olyan objektum, amely szmra a 0 cmmel foglalnnk helyet. Kvetkezskppen a 0 mutat-literlknt viselkedik, azt jellve, hogy a mutat nem hivatkozik objektumra.A C-ben a nulla mutatt (nullpointer) szoks a NULL makrval jellni. A C++ szigorbb tpusellenrzse miatt az ilyen NULL makrk helyett hasznljuk a sima 0-t, ez kevesebb problmhoz vezet. Ha gy rezzk, muszj a NULL-t megadnunk, tegyk azt az albbi mdon:
const int NULL = 0 ;

A const minst megakadlyozza, hogy a NULL-t vletlenl jra definiljuk s biztostja, hogy a NULLt ott is hasznlni lehessen, ahol llandra van szksg.

5.2. Tmbk
Ha T egy tpus, a T[size] a size darab T tpus elembl ll tmb tpus lesz. Az elemek sorszmozsa 0-tl size-1-ig terjed:
float v[3]; char* a[32]; // hrom lebegpontos szmbl ll tmb: v[0], v[1], v[2] // karakterre hivatkoz mutatk 32 elem tmbje: a[0] .. a[31]

A tmb elemeinek szma, mrete vagy dimenzija, konstans kifejezs kell, hogy legyen (C.5). Ha vltoz mretre van szksgnk, hasznljunk vektort (3.7.1, 16.3):
void f(int i) { int v1[i]; vector<int> v2(i); }

// hiba: a tmb mrete nem konstans kifejezs // rendben

A tbbdimenzis tmbk gy brzoldnak, mint tmbkbl ll tmbk:


int d2[10][20]; // d2 olyan tmb, amely 10 darab, 20 egszbl ll tmbt tartalmaz

Forrs: http://www.doksi.hu

118

Alapok

A ms nyelvekben tmbk mretnek meghatrozsra hasznlt vessz jells fordtsi hibkat eredmnyez, mert a , (vessz) mveletsorozatot jelz opertor (6.2.2) s nem megengedett konstans kifejezsekben (C.5). Pldul prbljuk ki ezt:
int bad[5,2]; // hiba: konstans kifejezsben nem lehet vessz

A tbbdimenzis tmbket a C.7 pontban trgyaljuk. Alacsonyszint kdon kvl a legjobb, ha kerljk ket.

5.2.1. Tmbk feltltse


A tmbknek rtkekbl ll listkkal adhatunk kezdrtket:
int v1[ ] = { 1, 2, 3, 4 }; char v2[ ] = { 'a', 'b', 'c', 0 };

Amikor egy tmbt gy adunk meg, hogy a mrett nem hatrozzuk meg, de kezdrtkeket biztostunk, a fordtprogram a tmb mrett a kezdrtk-lista elemeinek megszmllsval szmtja ki. Kvetkezskpp v1 s v2 tpusa rendre int[4] s char[4] lesz. Ha a mretet megadjuk, a kezdrtk-listban nem szerepelhet annl tbb elem, mert ez hibnak szmt:
char v3[2] = { 'a', 'b', 0 }; char v4[3] = { 'a', 'b', 0 }; // hiba: tl sok kezdrtk // rendben

Ha a kezdrtk tl kevs elemet ad meg, a tmb maradk elemeire 0 lesz felttelezve:


int v5[8] = { 1, 2, 3, 4 };

Az elz kd egyenrtk a kvetkezvel:


int v5[ ] = { 1, 2, 3, 4 , 0, 0, 0, 0 };

Jegyezzk meg, hogy a kezdrtk-lista nem helyettesthet s nem brlhat fell tmbrtkadssal:
void f() { v4 = { 'c', 'd', 0 }; // hiba: tmbt nem lehet rtkl adni }

Forrs: http://www.doksi.hu

5. Mutatk, tmbk s struktrk

119

Ha ilyen rtkadsra van szksgnk, tmb helyett hasznljunk vector-t (16.3) vagy valarray-t (22.4). A karaktertmbket knyelmi okokbl karakterliterlokkal (5.2.2) is feltlthetjk.

5.2.2. Karakterliterlok
A karakterliterl egy macskakrmkkel hatrolt karaktersorozat:
"Ez egy karakterlnc"

Egy karakterliterl a ltszlagosnl eggyel tbb karaktert tartalmaz; a '\0' null karakterre vgzdik, melynek rtke 0:
sizeof("Bohr")==5

A karakterliterlok tpusa megfelel szm const (lland) karakterbl ll tmb, gy a "Bohr" tpusa const char[5] lesz. A karakterliterlokat egy char*-nak is rtkl adhatjuk. Ez azrt megengedett, mert a karakterliterl tpusa a C s a C++ korbbi vltozataiban char* volt, gy ez szksges ahhoz, hogy milli sornyi C s C++ kd rvnyes maradjon. Az ilyen karakterliterlokat azonban hiba ilyen mutatn keresztl mdostani.
void f() { char* p = "Platn"; p[4] = 'e'; // hiba: rtkads konstansnak; az eredmny nem meghatrozott }

Az effajta hibt ltalban nem lehet a futsi idig kiderteni, s a nyelv egyes megvalstsai is klnbznek abban, hogy mennyire szereznek rvnyt ennek a szablynak. (Lsd mg B.2.3-at.) Az, hogy a karakterliterlok llandk, nemcsak magtl rtetd, hanem azt is lehetv teszi, hogy a nyelv adott vltozata jelentsen optimalizlhassa a karakterliterlok trolsnak s hozzfrsnek mdjt.

Forrs: http://www.doksi.hu

120

Alapok

Ha olyan karakterlncot szeretnnk, amit biztosan mdosthatunk, karaktereit egy tmbbe kell msolnunk:
void f() { char p[ ] = "Znn"; p[0] = 'R'; }

// p egy 6 karakterbl ll tmb // rendben

A karakterliterl trolsi helye nem vltozik (statikus), gy egy fggvny visszatrsi rtkeknt biztonsgosan megadhat:
const char* error_message(int i) { // ... return "tartomnyhiba"; }

A range_error-t tartalmaz memriaterlet tartalma nem trldik az error_message() meghvsa utn. Az, hogy kt egyforma karakterliterl egyetlen memriaterleten troldik-e, az adott nyelvi vltozattl fgg (C.1):
const char* p = "Herakleitosz"; const char* q = "Herakleitosz"; void g() { if (p == q) cout << "Egyezik!\n"; // ... }

// az eredmny az adott C++-vltozattl fgg

Jegyezzk meg, hogy a mutatkra alkalmazott == a cmeket (a mutat rtkeket) hasonltja ssze, nem azokat az rtkeket, melyekre a mutatk hivatkoznak. res karakterlncot a "" szomszdos macskakrm-prral rhatunk le (tpusa const char[1]). A nem grafikus karakterek jellsre hasznlt fordtott perjel (C.3.2) szintn hasznlhat egy karakterlnc belsejben. Ez lehetv teszi az idzjel (" ) s a fordtott perjel escape karakter (\) karakterlncon belli brzolst is. Az '\n' (j sor) karakter ezek kzl messze a leggyakrabban hasznlt:
cout<<"cseng az zenet vgn\a\n";

Forrs: http://www.doksi.hu

5. Mutatk, tmbk s struktrk

121

Az '\a' karakter az ASCII BEL (cseng), amely alert-knt is ismert, kirsa pedig valamilyen hangjelzst eredmnyez. A karakterlncokban igazi sortrs nem szerepelhet:
"Ez nem karakterlnc hanem formai hiba"

A hossz lncok reshely (whitespace) karakterekkel szttrdelhetk, hogy a programszveg szebb legyen:
char alpha[ ] = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

A fordtprogram sszefzi a szomszdos lncokat, gy az alpha egyetlen karakterlnccal is megadhat lett volna:
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

A null karaktert elvileg a karakterlncok belsejben is hasznlhatnnk, de a legtbb program nem felttelezi, hogy utna is vannak karakterek. A "Jens\000Munk" karakterlncot pldul az olyan standard knyvtrbeli fggvnyek, mint a strcpy() s a strlen(), "Jens"-knt fogjk kezelni (20.4.1). Az L eltag karakterlncok mint amilyen az L"angst" szles karakterekbl (wide char) llnak (4.3, C3.3), tpusuk const wchar_t[ ].

5.3. Tmbkre hivatkoz mutatk


A C++-ban a tmbk s mutatk kztt szoros kapcsolat ll fenn. Egy tmb nevt gy is hasznlhatjuk, mint egy mutatt, amely a tmb els elemre mutat:
int v[ ] = { 1, 2, 3, 4 }; int* p1 = v; // mutat a kezdelemre (automatikus konverzi) int* p2 = &v[0]; // mutat a kezdelemre int* p3 = &v[4]; // mutat az "utols utni" elemre

Forrs: http://www.doksi.hu

122

Alapok

brval:

p1

p2

p3

v:

Az biztosan mkdik, ha a mutatt eggyel a tmb vge utni elemre lltjuk. Ez sok algoritmus szmra fontos (2.7.2, 18.3). Mivel azonban egy ilyen mutat tnylegesen mr nem mutat egy tmb elemre, nem szabad rsra vagy olvassra hasznlni. Nem meghatrozott, hogy mi trtnik, amikor egy tmb kezdeleme eltt lev elem cmt vesszk, ezrt az ilyesmi kerlend. Egyes szmtgpeken a tmbk gyakran a gp cmzsi hatrain kerlnek lefoglalsra, gy az eggyel a kezdelem eltti elem egyszeren rtelmetlen lesz. A tmbnevek automatikus (implicit) talaktsa mutatv szleskren hasznlatos a C stlus kdokban szerepl fggvnyhvsoknl:
extern "C" int strlen(const char*); // a <string.h> fejllomnybl

void f() { char v[ ] = "Annemarie"; char* p = v; // char[ ] automatikus talaktsa char*-g strlen(p); strlen(v); // char[ ] automatikus talaktsa char*-g v = p; // hiba: a tmbnek nem adhat rtk }

A standard knyvtr strlen() fggvnynek mindkt hvskor ugyanaz az rtk addik t. Az a bkken, hogy az automatikus konverzit lehetetlen elkerlni, vagyis nincs md olyan fggvny bevezetsre, amelynek meghvsakor a v tmb tmsoldik. Szerencsre mutatrl tmbre val talakts nem vgezhet sem automatikusan, sem definilt mdon. A tmbparamter automatikus mutatv alaktsa azt jelenti, hogy a tmb mrete elvsz a fggvny szmra. A fggvnynek azonban valahogy meg kell hatroznia a tmb mrett, hogy rtelmes mveleteket hajthasson vgre rajta. A C standard knyvtrban lev ms fggvnyekhez hasonlan amelyek karakterre hivatkoz mutatkat kapnak paramterknt az strlen() is arra szmt, hogy a null karakter jelzi a karakterlnc vgt, gy a strlen(p)

Forrs: http://www.doksi.hu

5. Mutatk, tmbk s struktrk

123

a p karaktereinek a 0 null karakter vgzdsig szmolt mennyisgt jelenti, nem belertve a null karakter vgzdst . Ez meglehetsen alacsony szint megolds. A standard knyvtrban lv vector (16.3.) s string (20. fejezet) esetben nincs ilyen problma.

5.3.1. Tmbk bejrsa


Sok algoritmus lnyege a tmbkhz s ms hasonl adattpusokhoz val hatkony s elegns hozzfrs (lsd 3.8, 18. fejezet). A hozzfrs egy tmbre hivatkoz mutatval, illetve egy indexszel vagy egy elemre hivatkoz mutatval valsthat meg. me egy plda egy karakterlnc bejrsra index hasznlatval:
void fi(char v[ ]) { for (int i = 0; v[i]!=0; i++) use(v[i]); }

Ez egyenrtk a mutatval trtn bejrssal:


void fp(char v[ ]) { for (char* p = v; *p!=0; p++) use(*p); }

Az eltagknt hasznlt * indirekci opertor egy mutat-hivatkozst old fel, gy *p a p ltal mutatott karakter lesz, a ++ pedig gy nveli a mutatt, hogy az a tmb kvetkez elemre hivatkozzon. Nincs olyan eredend ok, amirt az egyik vltozat gyorsabb lenne a msiknl. A modern fordtprogramoknak ugyanazt a kdot kell ltrehozniuk mindkt plda esetben (lsd 5.9[8]-at). A programozk logikai s eszttikai alapon vlaszthatnak a vltozatok kztt. Ha a +, -, ++ vagy -- aritmetikai mveleti jeleket mutatkra alkalmazzuk, az eredmny a mutatk ltal hivatkozott objektumok tpustl fgg. Amikor egy T* tpus p mutatra alkalmazunk egy aritmetikai opertort, akkor p-rl felttelezzk, hogy egy T tpus objektumokbl ll tmb elemre mutat, gy p+1 a tmb kvetkez elemt jelzi, p-1 pedig az elz elemre mutat. Ez arra utal, hogy p+1 egsz rtke sizeof(T)-vel nagyobb lesz, mint p egsz rtke. Hajtsuk vgre a kvetkezt:
#include <iostream> int main () { int vi[10]; short vs[10];

Forrs: http://www.doksi.hu

124

Alapok

std::cout << &vi[0] << ' ' << &vi[1] << '\n'; std::cout << &vs[0] << ' ' << &vs[1] << '\n';

Ekkor a kvetkezt kapjuk (a mutatk rtknek alaprtelmezs szerinti, hexadecimlis jellst hasznlva):
0x7fffaef0 0x7fffaef4 0x7fffaedc 0x7fffaede

Ez azt mutatja, hogy sizeof(short) az adott megvalstsban 2, sizeof(int) pedig 4. Mutatkat csak akkor vonhatunk ki egymsbl definilt mdon, ha mindkt mutat ugyanannak a tmbnek az elemeire mutat (br a nyelvben nincs gyors md annak ellenrzsre, hogy valban arra mutatnak). Amikor kivonunk egy mutatt egy msikbl, az eredmny a kt mutat kztt lv tmbelemek szma (egy egsz tpus rtk) lesz. A mutatkhoz egsz rtket is adhatunk s ki is vonhatunk belle egszet, az eredmny mindkt esetben egy mutat rtk lesz. Ha ez az rtk nem ugyanannak a tmbnek egy elemre mutat, amelyre az eredeti mutat, vagy nem eggyel a tmb mg, az eredmnyl kapott mutat rtk felhasznlsa kiszmthatatlan eredmnyhez vezethet:
void f() { int v1[10]; int v2[10]; int i1 = &v1[5]-&v1[3]; int i2 = &v1[5]-&v2[3]; int* p1 = v2+2; int* p2 = v2-2; // i1 = 2 // meghatrozhatatlan eredmny // p1 = &v2[2] // *p2 nem meghatrozott

A bonyolult mutataritmetika rendszerint szksgtelen, ezrt legjobb elkerlni. Nincs rtelme mutatkat sszeadni, s ez nem is megengedett. A tmbk nem nlerk, mert nem biztos, hogy a tmb elemeinek szma is troldik a tmbbel egytt. Ez azt jelenti, hogy ahhoz, hogy bejrjunk egy tmbt, amely nem tartalmaz a karakterlncokhoz hasonl vgzdst, valahogy meg kell adnunk a tmb elemeinek szmt:

Forrs: http://www.doksi.hu

5. Mutatk, tmbk s struktrk

125

void fp(char v[ ], unsigned int size) { for (int i=0; i<size; i++) use(v[i]); const int N = 7; char v2[N]; for (int i=0; i<N; i++) use(v2[i]); }

Jegyezzk meg, hogy a legtbb C++-vltozat a tmbk esetben nem vgez indexhatrellenrzst. A tmb ezen fogalma eredenden alacsony szint. Fejlettebb tmbfogalmat osztlyok hasznlatval valsthatunk meg (3.7.1).

5.4. Konstansok
A C++ felknlja a const, azaz a felhasznli lland fogalmt, hogy lehetsgnk legyen annak kifejezsre, hogy egy rtk nem vltozik meg kzvetlenl. Ez szmos esetben hasznos lehet. Sok objektumnak a ltrehozs utn mr nem vltozik meg az rtke. A szimbolikus konstansok (jelkpes llandk) knnyebben mdosthat kdhoz vezetnek, mint a kdban kzvetlenl elhelyezett literlok. Gyakori, hogy egy rtket mutatn keresztl rnk el, de az rtket nem vltoztatjuk meg. A legtbb fggvnyparamtert csak olvassuk, nem rjuk. A const kulcssz hozzadhat egy objektum deklarcijhoz, jelezve, hogy az objektumot llandknt hatrozzuk meg. Mivel egy llandnak ksbb nem lehet rtket adni, kezdeti rtkadst kell vgeznnk:
const int model = 90; const int v[ ] = { 1, 2, 3, 4 }; const int x; // a model lland // a v[i] lland // hiba: nincs kezdeti rtkads

Ha valamit const-knt hatrozunk meg, az biztostk arra, hogy hatkrn bell rtke nem fog megvltozni:
void f() { model = 200; v[2]++; }

// hiba // hiba

Forrs: http://www.doksi.hu

126

Alapok

Jegyezzk meg, hogy a const kulcssz mdostja a tpust s megszortst ad arra, hogyan hasznlhatunk egy objektumot, de nem hatrozza meg, hogyan kell az lland szmra helyet foglalni:
void g(const X* p) { // itt *p nem mdosthat } void h() { X val; // a val mdosthat g(&val); // ... }

Attl fggen, hogy a fordtprogram mennyire okos, szmos mdon kihasznlhatja egy objektum lland mivoltt. Az llandk kezdeti rtke pldul gyakran (de nem mindig) egy konstans kifejezs (C.5), ami fordtsi idben kirtkelhet. Tovbb, ha a fordtprogram tud az lland minden hasznlatrl, nem kell trhelyet sem lefoglalnia szmra:
const int c1 = 1; const int c2 = 2; const int c3 = my_f(3); extern const int c4; const int* p = &c2;

// c3 rtke fordtskor nem ismert // c4 rtke fordtskor nem ismert // c2 szmra trterletet kell foglalni

Ekkor a fordtprogram ismeri c1 s c2 rtkt, gy azokat konstans kifejezsekben felhasznlhatjuk. Mivel a c3 s c4 rtkek fordtsi idben nem ismertek (ha csak ebben a fordtsi egysgben lev informcikat hasznljuk fel, lsd 9.1), c3-nak s c4-nek trhelyet kell foglalni. Mivel c2 cmt hasznljuk, c2-nek is helyet kell foglalni. A c1 konstans plda arra az egyszer s gyakori esetre, amikor az lland rtke fordtsi idben ismert s szmra nem szksges trat foglalni. Az extern kulcssz azt jelli, hogy a c4-et mshol definiltuk (9.2). A konstansokbl ll tmbknek ltalban szksges helyet foglalni, mert a fordtprogram nem tudja eldnteni, mely tmbelemekre hivatkoznak a kifejezsek. Sok gpen azonban mg ebben az esetben is nvelhetjk a hatkonysgot, gy, hogy a konstansokbl ll tmbt csak olvashat memriba tesszk.

Forrs: http://www.doksi.hu

5. Mutatk, tmbk s struktrk

127

A const-okat gyakran hasznljuk tmbk indexhatraknt s case cmkknl is:


const int a = 42; const int b = 99; const int max = 128; int v[max]; void f(int i) { switch (i) { case a: // ... case b: } } // ...

Ilyen esetekben gyakori, hogy const helyett felsorol konstansokat (4.8) hasznlunk. Azt, hogy a const milyen mdon hasznlhat osztlyok tagfggvnyeivel, a 10.2.6 s 10.2.7 pontokban trgyaljuk. A szimbolikus konstansokat rendszeresen hasznlnunk kellene arra, hogy elkerljk a kdban a mgikus szmokat. Ha egy numerikus lland, pldul egy tmb mrete, a kdban ismtldik, a programot nehz lesz tnzni, hogy a megfelel mdostskor az lland minden egyes elfordulst kicserljk. A szimbolikus konstansok hasznlata viszont lokliss teszi az informcit. A numerikus konstansok rendszerint valamilyen, a programmal kapcsolatos felttelezst jellnek. A 4 pldul egy egszben lv bjtok szmt, a 128 a bemenet tmeneti trba helyezshez (pufferelshez) szksges karakterek szmt, a 6.24 pedig a dn korona s az amerikai dollr kztti keresztrfolyamot jellheti. Ha ezeket az rtkeket numerikus llandknt hagyjuk a kdban, akkor az, aki a programot karbantartja, nagyon nehezen tudja megtallni s megrteni azokat. Ezeket az llandkat gyakran nem veszik szre, s rvnytelenn vlnak, amikor a programot tviszik ms rendszerre vagy ha ms vltozsok alssk az ltaluk kifejezett felttelezseket. Ha a feltevseket megjegyzsekkel megfelelen elltott szimbolikus konstansokknt valstjuk meg, minimlisra cskkenthetjk az ilyen jelleg karbantartsi problmkat.

Forrs: http://www.doksi.hu

128

Alapok

5.4.1. Mutatk s konstansok


A mutatk hasznlatakor kt objektummal kapcsolatos dologrl van sz: magrl a mutatrl s az ltala mutatott objektumrl. Ha a mutat deklarcijt a const sz elzi meg, akkor az az objektumot, s nem a mutatt hatrozza meg llandknt. Ahhoz, hogy llandknt egy mutatt, s ne az ltala mutatott objektumot vezessk be, a *const deklartort kell hasznlnunk a sima * helyett:
void f1(char* p) { char s[ ] = "Gorm"; const char* pc = s; pc[3] = 'g'; pc = p; char *const cp = s; cp[3] = 'a'; cp = p; const char *const cpc = s; cpc[3] = 'a'; cpc = p; // mutat llandra // hiba: pc llandra mutat // rendben // konstans mutat // rendben // hiba: cp konstans // konstans mutat llandra // hiba: cpc llandra mutat // hiba: cpc konstans

A *const deklartorjelz teszi llandv a mutatt. Nincs azonban const* deklartor-opertor, gy a * eltt szerepl const kulcsszt az alaptpus rsznek tekintjk:
char *const cp; char const* pc; const char* pc2; // konstans mutat karakterre // mutat kostans karakterre // mutat kostans karakterre

ltalban segtsget jelent, ha az ilyen deklarcikat jobbrl balra olvassuk ki. Pldul: cp egy konstans (const) mutat, amely egy karakterre (char) mutat s pc2 egy mutat, amely egy karakter-konstansra (char const) mutat. Egy objektum, amely lland akkor, amikor mutatn keresztl frnk hozz, lehet, hogy mdosthat lesz akkor, ha ms mdon frnk hozz. Ez klnsen hasznos a fggvnyparamterek esetben. Azzal, hogy egy mutat-paramtert const-knt adunk meg, a fggvnynek megtiltjuk, hogy mdostsa a mutat ltal mutatott objektumot:
char* strcpy(char* p, const char* q); // *q nem mdosthat

Forrs: http://www.doksi.hu

5. Mutatk, tmbk s struktrk

129

Egy vltoz cmt rtkl adhatjuk egy konstansra hivatkoz mutatnak, mert ebbl mg semmi rossz nem kvetkezik. Konstans cmt azonban nem lehet rtkl adni egy nem konstans mutatnak, mert ezzel megengednnk, hogy az objektum rtke megvltozzon:
void f4() { int a = 1; const int c = 2; const int* p1 = &c; const int* p2 = &a; int* p3 = &c; *p3 = 7; }

// rendben // rendben // hiba: kezdeti rtkads int*-nak const int*-gal // ksrlet c rtknek mdostsra

A const-ra hivatkoz mutatkkal kapcsolatos megszortsokat meghatrozott (explicit) tpuskonverzival kszblhetjk ki (10.2.7.1 s 15.4.2.1).

5.5. Referencik
A referencia (hivatkozs) egy objektum lneve (alias). Az ilyen hivatkozsokat ltalban fggvnyek s klnsen tlterhelt opertorok (11. fejezet) paramtereinek s visszatrsi rtkeinek megadsra hasznljuk. Az X& jells jelentse referencia X-re. Lssunk egy pldt:
void f() { int i = 1; int& r = i; int x = r; } r = 2;

// r s i itt ugyanarra az int-re hivatkoznak // x = 1 // i = 2

Azt biztostand, hogy a referencia valaminek a neve legyen (azaz tartozzon hozz objektum), a hivatkozs clpontjt mr ltrehozskor meg kell hatroznunk:
int i = 1; int& r1 = i; int& r2; extern int& r3; // rendben: r1 kapott kezdrtket // hiba: kezdeti rtkads hinyzik // rendben: r3 mshol kap kezdrtket

Forrs: http://www.doksi.hu

130

Alapok

A referencia kezdeti rtkadsa nagyban klnbzik a ksbbi rtkadstl. A ltszat ellenre a referencin nem hajtdik vgre egyetlen mvelet sem. Pldul:
void g() { int ii = 0; int& rr = ii; rr++; int* pp = &rr; }

// ii nvelse eggyel // pp az ii-re mutat

Ez nem helytelen, de rr++ nem az rr rtkt nveli; a ++ egy int-re hajtdik vgre (ami itt ii). Kvetkezskppen a referencik rtke mr nem mdosthat a kezdeti rtkads utn; mindig arra az objektumra fognak hivatkozni, amelyre kezdetben belltottuk azokat. Az rr ltal jellt objektumra hivatkoz mutatt &rr-rel kaphatjuk meg. A referencia magtl rtetd mdon megvalsthat (konstans) mutatknt is, amely minden egyes hasznlatakor automatikusan feloldja a mutat-hivatkozst. Nem szrmazhat baj abbl, ha gy gondolunk a referencikra, mindaddig, mg el nem felejtjk, hogy nem olyan objektumok, amelyet mutatknt kezelhetnnk:

pp:

&ii rr: ii: 1

Egyes fordtprogramok olyan optimalizcit alkalmazhatnak, amely a referencia szmra futsi idben szksgtelenn teszi trterlet lefoglalst. A referencia kezdeti rtkadsa magtl rtetd, ha a kezdrtk egy balrtk (vagyis egy olyan objektum, amelynek cmre hivatkozhatunk, lsd 4.9.6). Egy sima T& kezdrtke T tpus balrtk kell, hogy legyen. Egy const T& esetben ez nem szksges (sem balrtknek, sem T tpusnak nem kell lennie), helyette az albbiak trtnnek:

Forrs: http://www.doksi.hu

5. Mutatk, tmbk s struktrk

131

1. Elszr T-re trtn automatikus tpuskonverzi megy vgbe, ha szksges (lsd C.6-ot), 2. aztn a kapott rtk egy T tpus ideiglenes vltozba kerl, 3. vgl ez az ideiglenes vltoz lesz a kezdrtk. Vegyk a kvetkez pldt:
double& dr = 1; const double& cdr = 1; // hiba: balrtkre van szksg // rendben

A msodik a kvetkezkppen rtelmezhet:


double temp = double(1); const double& cdr = temp; // elszr ltrehozunk egy ideiglenes vltozt a jobb oldali // rtkkel // majd ezt hasznljuk a cdr kezdeti rtkadsra

A referencia kezdrtkt trol ideiglenes vltoz a referencia hatkrnek vgig marad fenn. A konstansok s vltozk hivatkozsait azrt klnbztetjk meg, mert a vltozk esetben nagy hibalehetsgeket rejt magban egy ideiglenes vltoz bevezetse, a vltoznak val rtkads ugyanis a nemsokra megszn ideiglenes trterletnek adna rtket. A konstansok hivatkozsaival nincs ilyen problma, ami szerencss, mert ezek gyakran fggvnyparamterknt jtszanak fontos szerepet (11.6). A referencikat olyan fggvnyparamterek megadsra is hasznlhatjuk, melyeken keresztl a fggvny mdosthatja a neki tadott objektum rtkt:
void increment(int& aa) { aa++; } void f() { int x = 1; increment(x); }

// x = 2

A paramtertads a kezdeti rtkadshoz hasonl, gy az increment meghvsakor az aa paramter az x msik neve lesz. Ha azt szeretnnk, hogy a program olvashat maradjon, legjobb, ha elkerljk az olyan fggvnyeket, amelyek mdostjk paramtereiket. Ehelyett meghatrozhatjuk a fggvny ltal visszaadand rtket vagy mutat paramtert adhatunk neki:
int next(int p) { return p+1; } void incr(int* p) { (*p)++; }

Forrs: http://www.doksi.hu

132

Alapok

void g() { int x = 1; increment(x); x = next(x); incr(&x); }

// x = 2 // x = 3 // x = 4

Az increment(x) jells az olvasnak semmit sem rul el arrl, hogy az x rtke mdosul, ellenttben az x=next(x) s incr(&x) jellsekkel. Kvetkezskppen a sima referenciaparamtereket csak olyan esetekben hasznljuk, amikor a fggvny neve hatrozottan utal arra, hogy ezek mdosulnak. A referencikat olyan fggvnyek megadsra is hasznlhatjuk, amelyek egy rtkads bal s jobb oldaln egyarnt szerepelhetnek. Ez a bonyolultabb felhasznli tpusok tervezsekor lehet igazn hasznos. Adjunk meg pldul egy egyszer asszociatv tmbt. Elszr hatrozzuk meg a Pair adatszerkezetet:
struct Pair { string name; double val; };

Az alaptlet az, hogy a string-hez tartozik egy lebegpontos rtk. Knny elkszteni a value() fggvnyt, amely egy Pair-bl ll adatszerkezetet vet ssze klnbz karakterlncokkal. Rvidtsk le a pldt s hasznljunk egy nagyon egyszer (persze nem tl hatkony) megvalstst:
vector<Pair> pairs; double& value(const string& s) /* Vesszk Pair-ek egy halmazt, megkeressk s-t, ha megtalltuk, visszaadjuk az rtkt; ha nem, j Pair-t ksztnk s visszaadjuk az alaprtelmezett 0-t. */ { for (int i = 0; i < pairs.size(); i++) if (s == pairs[i].name) return pairs[i].val; Pair p = { s, 0 }; pairs.push_back(p); } // Pair hozzadsa a vghez (3.7.3)

return pairs[pairs.size()-1].val;

Forrs: http://www.doksi.hu

5. Mutatk, tmbk s struktrk

133

Ezt a fggvnyt gy foghatjuk fel, mint egy lebegpontos rtkekbl ll tmbt, amit karakterlncok indexelnek. Adott karakterlnccal sszevetve, a value() a megfelel lebegpontos objektumot (nem pedig annak rtkt) tallja meg, s az erre vonatkoz referencit adja vissza:
int main() { string buf; // az egyes szavak elfordulsnak megszmllsa a bemeneten

while (cin>>buf) value(buf)++; for (vector<Pair>::const_iterator p = pairs.begin(); p!=pairs.end(); ++p) cout << p->name << ": " << p->val << '\n';

A while ciklus minden esetben beolvas egy szt a cin szabvnyos bemenetrl s a buf karakterlncba helyezi (3.6.), aztn nveli a hozz tartoz szmllt. Vgl kirja az eredmnyl kapott tblzatot, amelyben a bemenetrl kapott karakterlncok s azok elfordulsnak szma szerepel. Ha a bemenet pldul a kvetkez:
aa bb bb aa aa bb aa aa

akkor a program eredmnye az albbi:


aa: 5 bb: 3

Ezt mr knny gy tovbb finomtani, hogy valdi asszociatv tmbt kapjunk; ehhez egy sablon osztlyt kell hasznlnunk a tlterhelt (11.8.) [ ] indexel opertorral. Mg knnyebb a dolgunk, ha a standard knyvtr map (17.4.1.) tpust hasznljuk.

5.6. Void-ra hivatkoz mutatk


Brmilyen tpus objektumra hivatkoz mutatt rtkl lehet adni egy void* tpus vltoznak, egy void* tpus vltozt rtkl lehet adni egy msik void* tpusnak, a void* tpus vltozkat ssze lehet hasonltani, hogy egyenlek-e vagy sem, egy void* tpus vltozt pedig meghatrozott mdon ms tpusv lehet alaktani. A tbbi mvelet nem lenne

Forrs: http://www.doksi.hu

134

Alapok

biztonsgos, mert a fordtprogram nem tudja, hogy valjban mifle objektumra hivatkozik egy ilyen mutat, ezrt a tbbi mvelet fordtsi idej hibt eredmnyez. Ahhoz, hogy egy void* tpus vltozt hasznlhassunk, t kell konvertlnunk azt adott tpus mutatv:
void f(int* pi) { void* pv = pi; *pv; pv++;

// rendben: int* automatikus konvertlsa void*-g // hiba: nem lehet void*-ra hivatkozni // hiba: void* nem nvelhet (a mutatott objektum mrete ismeretlen) // explicit visszaalakts int*-ra // hiba // hiba // nem biztonsgos

int* pi2 = static_cast<int*>(pv); double* pd1 = pv; double* pd2 = pi; double* pd3 = static_cast<double*>(pv);

ltalban nem biztonsgos olyan mutatt hasznlni, amely olyan tpusra konvertldik (cast), amely klnbzik a mutat ltal elzleg hivatkozott tpustl. A gp pldul felttelezheti, hogy minden double 8 bjtos memriahatron jn ltre. Ha gy van, akkor furcsa mkds szrmazhat abbl, ha a pi egy olyan int-re mutatott, amely nem gy helyezkedett el a memriban. Az ilyen jelleg explicit tpusknyszerts eredenden csnya s nem biztonsgos, kvetkezskpp a hasznlt static_cast jellst is szndkosan csnynak terveztk. A void* elsdlegesen arra hasznlatos, hogy mutatkat adjunk t olyan fggvnyeknek, amelyek nem feltteleznek semmit az objektumok tpusrl, valamint arra, hogy fggvnyek nem tpusos objektumokat adjanak vissza. Ahhoz, hogy ilyen objektumokat hasznljunk, explicit tpuskonverzit kell alkalmaznunk. Azok a fggvnyek, amelyek void* tpus mutatkat hasznlnak, jellemzen a rendszer legals szintjn helyezkednek el, ahol az igazi hardver-erforrsokat kezelik. Pldul:
void* my_alloc(size_t n); // n bjt lefoglalsa sajt trterleten

A rendszer magasabb szintjein lv void* tpus mutatkat gyanakvssal kell figyelnnk, mert tervezsi hibt jelezhetnek. Ha a void*-ot optimalizlsra hasznljuk, rejtsk tpusbiztos fellet mg (13.5, 24.4.2). A fggvnyekre hivatkoz mutatkat (7.7.) s a tagokra hivatkoz mutatkat (15.5) nem adhatjuk rtkl void* tpus vltoznak.

Forrs: http://www.doksi.hu

5. Mutatk, tmbk s struktrk

135

5.7. Struktrk
A tmbk azonos tpus elemekbl llnak, a struct-ok (adatszerkezetek, struktrk) majdnem tetszleges tpusakbl:
struct address { char* name; long int number; char* street; char* town; char state[2]; long zip; }; // "Jim Dandy" // 61 // "South St" // "New Providence" // 'N' 'J' // 7974

A fenti kd egy address (cm) nev j tpust hoz ltre, amely levelek kldshez szksges cmzsi adatokat tartalmaz. Vegyk szre a pontosvesszt a definci vgn. Ez egyike azon kevs helyeknek a C++-ban, ahol pontosvesszt kell tenni a kapcsos zrjel utn, ezrt sokan hajlamosak elfelejteni. Az address tpus vltozkat pontosan gy adhatjuk meg, mint ms vltozkat, s az egyes tagokra a . (pont, tagkivlaszt) opertorral hivatkozhatunk:
void f() { address jd; jd.name = "Jim Dandy"; jd.number = 61; }

A tmbk kezdeti rtkadsra hasznlt jells a struktra-tpus vltozk feltltsre is hasznlhat:


address jd = { "Jim Dandy", 61, "South St", "New Providence", {'N','J'}, 7974 };

Ennl azonban rendszerint jobb megolds konstruktorokat (10.2.3) hasznlni. Vegyk szre, hogy a jd.state-et nem lehetett volna az "NJ" karakterlnccal feltlteni. Mivel a karakterlncok a '\0' karakterre vgzdnek, az "NJ" hrom karakterbl ll, ami eggyel tbb, mint ami a jd.state-be belefr.

Forrs: http://www.doksi.hu

136

Alapok

A struktrk objektumaira gyakran hivatkozunk mutatkon keresztl a -> (struktra-mutat) opertorral:


void print_addr(address* p) { cout << p->name << '\n' << p->number << ' ' << p->street << '\n' << p->town << '\n' << p->state[0] << p->state[1] << ' ' << p->zip << '\n'; }

Ha p egy mutat, akkor p->m egyenrtk (*p).m-mel. A struktra-tpus objektumokat rtkl adhatjuk, tadhatjuk fggvnyparamterknt, s visszaadhatjuk fggvnyek visszatrsi rtkeknt is:
address current; address set_current(address next) { address prev = current; current = next; return prev; }

Ms lehetsges mveletek, mint az sszehasonlts (== s !=), nem meghatrozottak, de a felhasznl megadhat ilyeneket (11. fejezet). A struktra-tpus objektumok mrete nem felttlenl a tagok mretnek sszege. Ennek az az oka, hogy sok gp ignyli bizonyos tpus objektumok elhelyezst a felptstl fgg memriahatrokra, vagy eleve hatkonyabban kezeli az gy ltrehozott objektumokat. Az egszek pldul gyakran gpi szhatrokon jnnek ltre. Ezt gy mondjuk, hogy az ilyen gpeken az objektumok jl illesztettek. Ez a struktrkon bell lyukakat eredmnyez. Szmos gpen a sizeof(address) pldul 24, nem pedig 22, ahogy az elvrhat lenne. Az elpazarolt helyet a lehet legkevesebbre cskkenthetjk, ha egyszeren mret szerint rendezzk a struktra tagjait (a legnagyobb tag lesz az els). A legjobb azonban az, ha olvashatsg szerint rendezzk sorba a tagokat, s csak akkor mret szerint, ha bizonytottan szksg van optimalizlsra.

Forrs: http://www.doksi.hu

5. Mutatk, tmbk s struktrk

137

Egy tpus neve rgtn felhasznlhat attl a ponttl, ahol elszr megjelenik, nem csak a teljes deklarci utn:
struct Link { Link* previous; Link* successor; };

A struktra teljes deklarcijnak vgig viszont nem adhatunk meg jabb ilyen tpus objektumokat:
struct No_good { No_good member; }; // hiba: rekurzv definci

Ez azrt hibs, mert a fordtprogram nem kpes eldnteni a No_good mrett. Kt (vagy tbb) struktra-tpus klcsns hivatkozshoz adjunk meg pldul egy nevet, amely a tpus neve:
struct List; struct Link { Link* pre; Link* suc; Link* member_of; }; struct List { Link* head; }; // ksbb meghatrozand

A List els deklarcija nlkl a List hasznlata a Link deklarcijban formai hibt okozott volna. A struktra-tpus neve a tpus meghatrozsa eltt is felhasznlhat, feltve, hogy ez a hasznlat nem ignyli egy tag nevnek vagy a struktra mretnek ismerett:
class S; extern S a; S f(); void g(S); S* h(S*); // 'S' valamilyen tpus neve

Forrs: http://www.doksi.hu

138

Alapok

A fenti deklarcik kzl azonban sok nem hasznlhat, hacsak meg nem adjuk az S tpust:
void k(S* p) { S a; f(); g(a); p->m = 7; S* q = h(p); q->m = 7;

// hiba: S nem definilt; a helyfoglalshoz mret kell // hiba: S nem definilt; rtk visszaadshoz mret kell // hiba: S nem definilt; paramter tadshoz mret kell // hiba: S nem definilt; a tag neve nem ismert // rendben: a mutatk szmra foglalhat hely s t is adhatk // hiba: S nem definilt; a tag neve nem ismert

A struct az osztly (10. fejezet) egyszer formja. A C trtnetre visszanyl okok miatt ugyanazzal a nvvel s ugyanabban a hatkrben megadhatunk egy struct-ot s egy nem struktra jelleg tpust is:
struct stat { /* ... */ }; int stat(char* name, struct stat* buf);

Ebben az esetben a sima stat nv a nem-struktra neve, az adatszerkezetre pedig a struct eltaggal kell hivatkoznunk. Eltagknt a class, union (C.8.2) s enum (4.8) kulcsszavak is hasznlhatk, ezekkel elkerlhetjk a ktrtelmsget. A legjobb azonban, ha nem terheljk tl a neveket.

5.7.1. Egyenrtk tpusok


Kt struktra mindig klnbz tpus, akkor is, ha tagjaik ugyanazok:
struct S1 { int a; }; struct S2 { int a; };

A fenti kt tpus klnbz, gy


S1 x; S2 y = x; // hiba: nem megfelel tpus

Forrs: http://www.doksi.hu

5. Mutatk, tmbk s struktrk

139

A struktra-tpusok az alaptpusoktl is klnbznek, ezrt


S1 x; int i = x; // hiba: nem megfelel tpus

Minden struct-nak egyrtelm meghatrozsa kell, hogy legyen a programban (9.2.3.).

5.8. Tancsok
[1] [2] [3] [4] [5] [6] [7] [8] Kerljk a nem magtl rtetd mutat-aritmetikt. 5.3. gyeljnk arra, hogy ne rjunk egy tmb indexhatrn tlra. 5.3.1. Hasznljunk 0-t NULL helyett. 5.1.1. Hasznljuk a vector-t s a valarray-t a beptett (C stlus) tmbk helyett. 5.3.1. Hasznljunk string-et nulla vgzds karaktertmbk helyett. 5.3. Hasznljunk a lehet legkevesebb egyszer referencia-paramtert. 5.5. Az alacsonyszint kdot kivve kerljk a void*-ot. 5.6. Kerljk a kdban a nem magtl rtetd literlokat (mgikus szmokat). Hasznljunk helyettk jelkpes llandkat. 4.8, 5.4.

5.9. Gyakorlatok
1. (*1) Vezessk be a kvetkezket: karakterre hivatkoz mutat, 10 egszbl ll tmb, 10 egszbl ll tmb referencija, karakterlncokbl ll tmbre hivatkoz mutat, karakterre hivatkoz mutatra hivatkoz mutat, konstans egsz, konstans egszre hivatkoz mutat, egszre hivatkoz konstans mutat. Mindegyiknek adjunk kezdeti rtket. 2. (*1,5) Mik a char*, int*, s void* mutattpusokra vonatkoz megszortsok a mi rendszernkn? Lehetne-e pldul egy int*-nak furcsa rtke? Segtsg: illeszts.

Forrs: http://www.doksi.hu

140

Alapok

3. (*1) Hasznljunk typedef-et a kvetkezk meghatrozsra: unsigned char, const unsigned char, egszre hivatkoz mutat, karakterre hivatkoz mutatra hivatkoz mutat, karaktertmbkre hivatkoz mutat; 7 elem, egszre hivatkoz mutatkbl ll tmb; 7 elem, egszre hivatkoz mutatkbl ll tmbre hivatkoz mutat; egszre hivatkoz mutatkat tartalmaz 7 elem tmbkbl ll 8 elem tmb. 4. (*1) rjunk egy swap nev fggvnyt, amely kt egszt cserl fel. Hasznljunk int* tpust a paramterek tpusaknt. rjunk egy msik swap-et is, melynek paramterei int& tpusak. 5. (*1,5) Mi az str tmb mrete a kvetkez pldban?
char str[ ] = "rvid karakterlnc";

Mi a "rvid karakterlnc" hossza? 6. (*1) Ksztsk el az f(char), g(char&) s h(const char&) fggvnyeket. Hvjuk meg ket az 'a', 49, 3300, c, uc s sc paramterekkel, ahol c char, uc unsigned char s sc signed char tpus. Mely hvsok megengedettek? Mely hvsoknl vezet be a fordtprogram ideiglenes vltozt? 7. (*1,5) Ksztsnk egy tblzatot, amely a hnapok neveibl s napjaik szmbl ll. rjuk ki a tblzatot. Csinljuk meg mindezt ktszer: egyszer hasznljunk karaktertmbt a nevek s egy tmbt a napok szmra, msodszor hasznljunk struktrkbl ll tmbt, ahol az egyes adatszerkezetek a hnap nevt s a benne lev napok szmt troljk. 8. (*2) Futtassunk le nhny tesztet, hogy megnzzk, a fordtprogram tnyleg egyenrtk kdot hoz-e ltre a mutatk hasznlatval s az indexelssel val tmbbejrshoz (5.3.1). Ha klnbz mrtk optimalizlst lehet hasznlni, nzzk meg, hat-e s hogyan hat ez a ltrehozott kd minsgre. 9. (*1,5) Talljunk pldt, hol lenne rtelme egy nevet a sajt kezdrtkben hasznlni. 10. (*1) Adjunk meg egy karakterlncokbl ll tmbt, ahol a karakterlncok a hnapok neveit tartalmazzk. rjuk ki ezeket. Adjuk t a tmbt egy fggvnynek, amely kirja a karakterlncokat. 11. (*2) Olvassuk be a bemenetrl szavak egy sorozatt. A bemenetet lezr szknt hasznljuk a Quit-et. rjuk ki a beolvasott szavakat. Ne rjuk ki ktszer ugyanazt a szt. Mdostsuk a programot, hogy rendezze a szavakat, mieltt kirn azokat. 12. (*2) rjunk olyan fggvnyt, amely megszmolja egy betpr elfordulsait egy karakterlncban, s egy msikat, ami ugyanezt csinlja egy nulla vg karaktertmbben (vagyis egy C stlus karakterlncban). Az "ab" pr pldul ktszer szerepel az "xabaacbaxabb"-ben. 13. (*1,5) Adjunk meg egy Date struktrt dtumok brzolshoz. rjunk olyan fggvnyt, ami Date-eket olvas be a bemenetrl, olyat, ami Date-eket r a kimenetre, s olyat, ami egy dtummal ad kezdrtket a Date-nek.

Forrs: http://www.doksi.hu

6
Kifejezsek s utastsok
Az id eltti optimalizls minden rossz gykere. (D. Knuth) Msrszrl, nem hagyhatjuk figyelmen kvl a hatkonysgot. (John Bentley)

Asztali szmolgp plda Bemenet Parancssori paramterek Kifejezsek (ttekints) Logikai s sszehasonlt opertorok Nvels s cskkents Szabad tr Meghatrozott tpuskonverzik Utastsok (ttekints) Deklarcik Elgaz utastsok Deklarcik a felttelekben Ciklusutastsok A hrhedt goto Megjegyzsek s behzs Tancsok Gyakorlatok

Forrs: http://www.doksi.hu

142

Alapok

6.1. Egy asztali szmolgp


A kifejezseket s utastsokat egy asztali szmolgp pldjn keresztl mutatjuk be. A szmolgp a ngy aritmetikai alapmveletet hajtja vgre lebegpontos rtkeken. A mveleti jeleket a szmok kztt (infix opertorknt) kell megadni. A felhasznl vltozkat is megadhat. A bemenet legyen a kvetkez:
r = 2.5 area = pi * r * r

A szmolgp program az albbiakat fogja kirni (pi elre meghatrozott):


2.5 19.635

A 2.5 a bemenet els sornak, a 19.635 a bemenet msodik sornak eredmnye. A szmolgp ngy f rszbl ll: egy elemzbl (parser), egy adatbeviteli fggvnybl, egy szimblumtblbl s egy vezrlbl. Valjban ez egy miniatr fordtprogram, amelyben az elemz vgzi a szintaktikai elemzst (vagyis a nyelvi utastsok formai elemzst), az adatbeviteli fggvny kezeli a bemenetet s vgzi a lexikai elemzst (vagyis a nyelvi elemek rtelmezst), a szimblumtbla tartalmazza a nem vltoz adatokat s a vezrl kezeli a kezdeti rtkadst, a kimenetet s a hibkat. A szmolgpet szmos szolgltatssal bvthetjk, hogy mg hasznosabb tegyk (6.6[20]), de a kd gy is elg hossz lesz, s a legtbb szolgltats csak a kdot nveln, anlkl, hogy tovbbi betekintst nyjtana a C++ hasznlatba.

6.1.1. Az elemz
me a szmolgp ltal elfogadott nyelvtan:
program: END expr_list END expression PRINT expression PRINT expr_list // END a bevitel vge

expr_list:

// PRINT a pontosvessz

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

143

expression: expression + term expression - term term term: term / primary term * primary primary NUMBER NAME NAME = expression - primary ( expression )

primary:

Ms szval, a program kifejezsek sorozata, amelyeket pontosvesszk vlasztanak el egymstl. A kifejezsek alapelemei a szmok, nevek s a *, /, +, - (akr egy, akr kt operandus) opertorok, s az = . A neveket nem kell hasznlat eltt definilni. Az ltalunk hasznlt szintaktikai elemzs mdszert rendszerint rekurzv leszllsnak (recursive descent) nevezik; npszer s lnyegretr, fellrl lefel halad eljrs. Egy olyan nyelvben, mint a C++, amelyben a fggvnyhvsok viszonylag kis kltsgek", a mdszer hatkony is. A nyelvtan minden szablyra adunk egy fggvnyt, amely ms fggvnyeket hv meg. A lezr szimblumokat (pldul az END, NUMBER, + s -) a get_token() lexikai elemz, a nem lezr szimblumokat pedig az expr(), term() s prim() szintaktikai elemz fggvnyek ismerik fel. Ha egy (rsz)kifejezs minkt operandusa ismert, a kifejezs kirtkeldik egy igazi fordtprogram esetben ezen a ponton trtnhetne a kd ltrehozsa. Az elemz a get_token() fggvnyt hasznlja arra, hogy bemenetet kapjon. Az utols get_token() hvs eredmnye a curr_tok globlis vltozban tallhat. A curr_tok vltoz tpusa Token_value felsorol tpus:
enum Token_value NAME, PLUS='+', PRINT=';', }; { NUMBER, MINUS='-', ASSIGN='=', END, MUL='*', LP='(',

DIV='/', RP=')'

Token_value curr_tok = PRINT;

Forrs: http://www.doksi.hu

144

Alapok

Az, hogy minden szimblumot (token) a karakternek megfelel egsz rtkkel jellnk, knyelmes s hatkony megolds, s segtheti azokat, akik hibakerest (debugger) hasznlnak. Ez a mdszer addig mkdik, amg bemenetknt olyan karaktert nem adunk meg, melynek rtkt felsorol konstansknt mr hasznljuk. n pedig nem tudok olyan karakterkszletrl, amelyben van olyan kirhat karakter, melynek egsz rtke egy szmjegy. Azrt vlasztottam a curr_tok kezdeti rtkeknt a PRINT-et, mert a curr_tok ezt az rtket fogja felvenni, miutn a szmolgp kirtkelt egy kifejezst s kirta annak rtkt. gy alapllapotban indtjuk el a rendszert, a lehet legkisebbre cskkentjk annak az eslyt, hogy hibk forduljanak el s egyedi indtkdra sincs szksgnk. Minden elemz fggvnynek van egy logikai (bool) (4.2) paramtere, amely jelzi, hogy meg kell-e hvnia a get_token()-t a kvetkez szimblum beolvasshoz. A fggvny kirtkeli a sajt kifejezst s visszaadja az rtkt. Az expr() fggvny kezeli az sszeadst s kivonst. A fggvny egyetlen ciklusbl ll, amely elemeket (term) keres az sszeadshoz vagy kivonshoz:
double expr(bool get) { double left = term(get); for (;;) switch (curr_tok) { case PLUS: left += term(true); break; case MINUS: left -= term(true); break; default: return left; } // sszeads s kivons

// "rkk" (vgtelen ciklus)

Ez a fggvny nmagban nem csinl tl sokat. Egy nagyobb program magasabb szint fggvnyeihez hasonl mdon ms fggvnyeket hv meg a feladat elvgzshez. A switch utasts azt vizsglja meg, hogy a switch kulcssz utn zrjelben megadott felttel rtke megegyezik-e a konstansok valamelyikvel. A break-kel a switch utastsbl lphetnk ki. A case cmkket kvet konstansoknak klnbznik kell egymstl. Ha a vizsglt rtk nem egyezik egyik case cmkvel sem, a default cmke vlasztdik ki. A programoznak nem ktelez megadnia a default rszt.

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

145

Figyeljk meg, hogy a 2-3+4-hez hasonl kifejezsek (2-3)+4-knt rtkeldnek ki, ahogy azt a nyelvtanban meghatroztuk. A furcsa for( ; ; ) jells megszokott mdja annak, hogy vgtelen ciklust rjunk; gy mondhatjuk ki, hogy rkk (forever). Ez a for utasts (6.3.3) vgletes formja; helyette hasznlhatjuk a while(true) szerkezetet is. A switch utasts vgrehajtsa addig ismtldik, amg nem tall a +-tl s - -tl klnbz jelet, amikor is a default cmke utni return utasts hajtdik vgre. Az sszeads s kivons kezelsre a += s -= opertorokat hasznljuk. Hasznlhatnnk a left=left+term(true) s left=left-term(true) formt is, a program jelentse nem vltozna. A left+=term(true) s a left-=term(true) azonban nemcsak rvidebbek, hanem kzvetlenebbl is fejezik ki a kvnt mveletet. Minden rtkad opertor nll nyelvi egysg, gy a + = 1 nyelvtanilag hibs a + s az = kztti szkz miatt. A kvetkez ktoperandus mveletekhez lteznek rtkad opertorok:
+ * / % & | ^ << >>

gy a kvetkez rtkad opertorok lehetsgesek:


= += -= *= /= %= &= |= ^= <<= >>=

A % a modul vagy maradkkpz opertor; &, |, s ^ a bitenknti S, VAGY, illetve kizr VAGY opertorok; << s >> pedig a balra s jobbra lptet opertorok. A mveleti jeleket s jelentsket 6.2 foglalja ssze. Ha @ egy binris (ktoperandus) opertor, akkor x@=y jelentse x=x@y, azzal a klnbsggel, hogy x csak egyszer rtkeldik ki. A 8. s a 9. fejezet trgyalja, hogyan ptsnk fel programot modulokbl. A szmolgp plda deklarciit egy kivtellel gy rendezhetjk sorba, hogy mindent csak egyszer s hasznlat eltt adunk meg. A kivtel az expr(), ami meghvja a term()-et, ami meghvja a prim()-et, ami pedig ismt meghvja az expr()-et. Ezt a krt valahol meg kell szaktanunk. A prim() meghatrozsa eltti deklarci erre val.
double expr(bool);

A term() fggvny ugyanolyan mdon kezeli a szorzst s osztst, mint ahogy az expr() kezeli az sszeadst s kivonst:

Forrs: http://www.doksi.hu

146

Alapok

double term(bool get) { double left = prim(get); for (;;) switch (curr_tok) { case MUL: left *= prim(true); break;

// szorzs s oszts

case DIV: if (double d = prim(true)) { left /= d; break; } return error("Nullval nem lehet osztani"); default: } } return left;

A nullval val oszts nem meghatrozott s rendszerint vgzetes hibt okoz. Ezrt oszts eltt megnzzk, hogy a nevez 0 -e, s ha igen, meghvjuk az error()-t. Az error() fggvnyt a 6.1.4-ben ismertetjk. A d vltozt pontosan azon a ponton vezetjk be a programba, ahol az szksges, s rgtn kezdeti rtket is adunk neki. Egy felttelben bevezetett nv hatkre a felttel ltal vezrelt utasts, az eredmnyezett rtk pedig a felttel rtke (6.3.2.1). Kvetkezskppen a left/=d oszts s rtkads csak akkor megy vgbe, ha d nem nulla. A prim() fggvny, amely az elemi szimblumokat kezeli, nagyban hasonlt az expr()-re s a term()-re, kivve azt, hogy mivel mr lejjebb rtnk a hvsi hierarchiban, nmi valdi munkt kell vgezni s nincs szksg ciklusra:
double number_value; string string_value; double prim(bool get) { if (get) get_token(); // elemi szimblumok kezelse

switch (curr_tok) { case NUMBER: // lebegpontos konstans { double v = number_value; get_token(); return v; }

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

147

case NAME: { double& v = table[string_value]; if (get_token() == ASSIGN) v = expr(true); return v; } case MINUS: // egyoperandus mnusz return -prim(true); case LP: { double e = expr(true); if (curr_tok != RP) return error(") szksges"); get_token(); // ')' lenyelse return e; } default: return error("elemi szimblum szksges"); }

Amikor egy NUMBER-t (azaz egy egsz vagy lebegpontos literlt) tallunk, visszaadjuk az rtkt. A get_token() bemeneti eljrs elhelyezi az rtket a number_value globlis vltozban. Globlis vltoz hasznlata a kdban gyakran jelenti, hogy a program szerkezete nem kristlytiszta valamifle optimalizcit alkalmaztak r. Itt is ez trtnt. Idelis esetben egy nyelvi egysg (lexikai szimblum) kt rszbl ll: egy rtkbl, amely meghatrozza a szimblum fajtjt (ebben a programban ez a Token_value) s (ha szksges) a token rtkbl. Itt csak egy egyszer curr_tok vltoz szerepel, gy a number_value globlis vltoz szksges ahhoz, hogy az utols beolvasott NUMBER rtkt trolja. E ktes szerep globlis vltoz kikszblst is a feladatok kz tzzk ki (6.6[21]). A number_value rtkt nem felttlenl szksges a v loklis vltozba menteni a get_token() meghvsa eltt. A szmolgp a szmtshoz minden helyes bemenetnl hasznlatba veszi az els szmot, mieltt egy msikat olvasna be, hiba esetn viszont segtheti a felhasznlt, ha mentjk az rtket s helyesen kirjuk. Hasonlan ahhoz, ahogy az utols beolvasott NUMBER-t a number_value trolja, az utols beolvasott NAME karakterlncot a string_value tartalmazza. Mieltt a szmolgp brmit kezdene egy nvvel, meg kell nznie, hogy a nevet rtkl kell-e adnia vagy csak egyszeren be kell olvasnia. Mindkt esetben a szimblumtblhoz fordul. A szimblumtbla egy map (3.7.4, 17.4.1):
map<string,double> table;

Azaz, amikor a table-t egy karakterlnccal indexeljk, az eredmnyl kapott rtk az a double lesz, ami a karakterlnchoz tartozik. Tegyk fel, hogy a felhasznl a kvetkezket rja be:
radius = 6378.388;

Forrs: http://www.doksi.hu

148

Alapok

Ekkor a szmolgp az albbiakat hajtja vgre:


double& v = table["radius"]; // ... expr() kiszmolja az tadand rtket v = 6378.388;

A v referencit hasznljuk arra, hogy a radius-hoz tartoz double rtkre hivatkozzunk, amg az expr() a bemeneti karakterekbl kiszmtja a 6378.388 rtket.

6.1.2. A bemeneti fggvny


A bemenet beolvassa gyakran a program legrendezetlenebb rsze. Ez azrt van gy, mert a programnak egy emberrel kell trsalognia s meg kell birkznia annak szeszlyeivel, szoksaival s viszonylag vletlenszer hibival. Kellemetlen dolog (jogosan), ha megprbljuk a felhasznlt rknyszerteni, hogy gy viselkedjen, hogy az a gp szmra megfelelbb legyen. Egy alacsonyszint beolvas eljrs feladata az, hogy karaktereket olvasson be s magasabb szint szimblumokat hozzon ltre bellk. Ezek a szimblumok ksbb a magasabb szint eljrsok bemeneti egysgei lesznek. Itt az alacsonyszint beolvasst a get_token() vgzi. Nem felttlenl mindennapi feladat alacsonyszint bemeneti eljrsokat rni. Sok rendszer erre a clra szabvnyos fggvnyeket nyjt. Kt lpsben ptem fel a get_token()-t. Elszr egy megtveszten egyszer vltozatot ksztek, amely komoly terhet r a felhasznlra. Ezutn ezt mdostom egy kevsb elegns, de jval hasznlhatbb vltozatra. Az tlet az, hogy beolvasunk egy karaktert, felhasznljuk arra, hogy eldntsk, milyen szimblumot kell ltrehozni, majd visszaadjuk a beolvasott token-t brzol Token_value rtket. A kezdeti utastsok beolvassk az els nem reshely (whitespace, azaz szkz, tabultor, j sor stb.) karaktert ch-ba, s ellenrzik, hogy az olvassi mvelet sikerlt-e:
Token_value get_token() { char ch = 0; cin>>ch; switch (ch) { case 0: return curr_tok=END;

// rtkads s visszatrs

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

149

Alaprtelmezs szerint a >> opertor tugorja az reshely karaktereket s ch rtkt vltozatlanul hagyja, ha a bemeneti mvelet nem sikerl. Kvetkezskppen ch==0 a bemenet vgt jelzi. Az rtkads egy opertor, az rtkads eredmnye pedig annak a vltoznak az rtke, melynek rtket adunk. Ez megengedi, hogy a curr_tok vltoznak az END-et adjam rtkl, majd a vltozt ugyanabban az utastsban adjam vissza. Az, hogy egy utastst hasznlunk kett helyett, megknnyti a kd ksbbi mdostst. Ha az rtkadst s a visszaadott rtket klnvlasztannk a kdban, lehet, hogy a programoz megvltoztatn az egyiket, de elfelejten mdostani a msikat. Nzznk meg nhny esetet kln-kln, mieltt a teljes fggvnnyel foglalkoznnk. A kifejezsek ; vgzdst, a zrjeleket s az opertorokat gy kezeljk, hogy egyszeren visszaadjuk az rtkket:
case case case case case case case case ';': '*': '/': '+': '-': '(': ')': '=':

return curr_tok=Token_value(ch);

A szmokat gy kezeljk:
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': cin.putback(ch); cin >> number_value; return curr_tok=NUMBER;

A case cmkket fggleges helyett vzszintesen egy kupacba tenni ltalban nem j tlet, mert ez az elrendezs nehezebben olvashat. Fraszt lenne azonban minden szmjegyet kln sorba rni. Mivel a >> mveleti jel a lebegpontos konstansokat szablyszeren egy double tpus vltozba olvassa, a kd magtl rtetd. Elszr a kezd karaktert (szmjegyet vagy pontot) visszatesszk a cin-be, majd a konstanst a number_value vltozba helyezzk.

Forrs: http://www.doksi.hu

150

Alapok

A neveket hasonlan kezeljk:


default: // NAME, NAME =, vagy hiba if (isalpha(ch)) { cin.putback(ch); cin>>string_value; return curr_tok=NAME; } error("rossz szimblum"); return curr_tok=PRINT;

A standard knyvtrban lev isalpha() fggvnyt (20.4.2) hasznljuk arra, hogy ne kelljen minden karaktert felsorolnunk, mint klnbz case cmkket. A karakterlncok (ebben az esetben a string_value) >> mvelete addig olvassa a lncot, amg reshelyet nem tall. Kvetkezskppen a felhasznlnak szkzzel kell befejeznie az adott nevet azon opertorok eltt, melyek a nevet operandusknt hasznljk. Ez nem idelis megolds, ezrt erre a problmra mg visszatrnk 6.1.3-ban. me a teljes bemeneti fggvny:
Token_value get_token() { char ch = 0; cin>>ch; switch (ch) { case 0: return curr_tok=END; case case case case case case case case ';': '*': '/': '+': '-': '(': ')': '=':

return curr_tok=Token_value(ch);

case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': cin.putback(ch); cin >> number_value; return curr_tok=NUMBER;

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

151

default:

if (isalpha(ch)) { cin.putback(ch); cin>>string_value; return curr_tok=NAME; } error("rossz szimblum"); return curr_tok=PRINT;

// NAME, NAME =, vagy hiba

Egy opertor talaktsa az opertornak megfelel szimblumra magtl rtetd, mivel az opertorok token_value rtkt az opertor egsz rtkeknt hatroztuk meg (4.8).

6.1.3. Alacsonyszint bemenet


Ha a szmolgpet gy hasznljuk, ahogy az eddigiekben lertuk, fny derl nhny knyelmetlen dologra. Fraszt emlkezni arra, hogy pontosvesszt kell tennnk egy kifejezs utn, ha ki akarjuk ratni az rtkt, s nagyon bosszant tud lenni, hogy csak reshellyel lehet egy nevet befejezni. Pldul x=7 egy azonost, s nem x, amit az = opertor s a 7-es szm kvet. Mindkt problmt gy oldjuk meg, hogy a get_token()-ben a tpussal kapcsolatos alaprtelmezett bemeneti mveleteket olyan kdra cserljk, amely egyenknt olvassa be a karaktereket. Elszr is, az j sor karaktert azonosknt kezeljk a kifejezs vgt jelz pontosvesszvel:
Token_value get_token() { char ch; do { // reshelyek tugrsa az '\n' kivtelvel if(!cin.get(ch)) return curr_tok = END; } while (ch!='\n' && isspace(ch)); switch (ch) { case ';': case '\n': return curr_tok=PRINT;

A do utastst hasznljuk, amely egyenrtk a while utastssal, kivve, hogy a ciklusmag mindig legalbb egyszer vgrehajtdik. A cin.get(ch) beolvas egy karaktert a szabvnyos bemeneti adatfolyambl ch-ba. Alaprtelmezs szerint a get() nem ugorja t az reshelyeket

Forrs: http://www.doksi.hu

152

Alapok

gy, ahogy a >> mvelet teszi. Az if(!cin.get(ch)) ellenrzs sikertelen, ha nem olvashat be karakter a cin-bl; ebben az esetben END-et adunk vissza, hogy befejezzk a szmolgp mkdst. A ! (NEM) opertort azrt hasznljuk, mert a get() igazat ad vissza, ha sikeres. A standard knyvtr isspace() fggvnye vgzi az reshelyek (20.4.2) szabvnyos vizsglatt. Ha c reshely, az isspace(c) nem nulla rtket ad vissza, ms esetben nullt. A vizsglatot tblzatban val keressknt valstjuk meg, gy az isspace() hasznlata sokkal gyorsabb, mint az egyes reshely karakterek vizsglata. Hasonl fggvnyekkel nzhetjk meg, hogy egy karakter szmjegy (isdigit()), bet (isalpha()), esetleg bet vagy szm-e (isalnum()). Miutn tugrottuk az reshelyeket, a kvetkez karaktert arra hasznljuk, hogy eldntsk, mifle nyelvi egysg jn. A problmt, amit az okoz, hogy a >> addig olvassa a karakterlncot, amg reshelyeket nem tall, gy oldjuk meg, hogy egyszerre egy karaktert olvasunk be, amg olyan karaktert nem tallunk, ami nem szm s nem bet:
default: // NAME, NAME=, vagy hiba if (isalpha(ch)) { string_value = ch; while (cin.get(ch) && isalnum(ch)) string_value.push_back(ch); cin.putback(ch); return curr_tok=NAME; } error("rossz szimblum"); return curr_tok=PRINT;

Szerencsre mindkt javts elvgezhet gy, hogy a kdnak csak egyes helyi rvnyessg rszeit mdostjuk. Fontos tervezsi cl, hogy olyan programokat hozzunk ltre, melyek javtst, fejlesztst helyi mdostsokkal intzhetjk.

6.1.4. Hibakezels
Mivel a program ennyire egyszer, a hibakezelssel nem kell komolyabban trdnnk. Az error fggvny egyszeren megszmolja a hibkat, kir egy hibazenetet, s visszatr:
int no_of_errors; double error(const string& s) { no_of_errors++; cerr << "hiba: " << s << '\n'; return 1; }

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

153

A cerr egy tmeneti trba nem helyezett (nem pufferelt) kimeneti adatfolyam, amely rendszerint hibajelzsre hasznlatos (21.2.1). Azrt adunk vissza rtket, mert a hibk jellemzen valamilyen kifejezs kirtkelse kzben trtnnek, gy vagy teljesen abba kellene hagynunk a kirtkelst, vagy olyan rtket kellene visszaadnunk, amely nem valszn, hogy tovbbi hibkat okozna. Ezen egyszer szmolgp esetben az utbbi megolds megfelel. Ha a get_token() nyomon kvette volna a sorok szmt, az error() tjkoztathatta volna a felhasznlt a hiba pontos helyrl, ami akkor lenne hasznos, ha a szmolgpet nem interaktvan hasznlnnk (6.6.[19]). A program futsnak gyakran be kell fejezdne, miutn hiba trtnt, mert nincs megadva, milyen sszer mdon folytathatn mkdst. Ezt tehetjk meg az exit() meghvsval, amely elszr rendbe rakja az adatfolyamokat s hasonl dolgokat, majd befejezi a programot, melynek visszatrsi rtke az exit() paramtere lesz (9.4.1.1). Kivtelek hasznlatval elegnsabb hibakezel eljrsok kszthetk (lsd 8.3 s 14. fejezet), de amit most csinltunk, egy 150 soros szmolgpnek ppen megfelel.

6.1.5. A vezrl
Miutn a program minden rszlete a helyre kerlt, mr csak a vezrl kdra van szksgnk ahhoz, hogy elindtsuk a mkdst. Ebben az egyszer pldban ezt a main() vgzi el:
int main() { table["pi"] = 3.1415926535897932385; table["e"] = 2.7182818284590452354; while (cin) { get_token(); if (curr_tok == END) break; if (curr_tok == PRINT) continue; cout << expr(false) << '\n'; } } return no_of_errors; // elre megadott nevek beillesztse

Hagyomny szerint a main() 0-t kell, hogy visszaadjon, ha a program hiba nlkl r vget, ms esetben nem nullt (3.2). A hibk szmnak visszaadsval ezt szpen megold-

Forrs: http://www.doksi.hu

154

Alapok

juk. Ebben az esetben az egyetlen szksges elkszts az, hogy a szimblumtblba bele kell tennnk az elre megadott neveket. A f ciklus feladata, hogy beolvassa a kifejezseket s kirja a vlaszt. Ezt a kvetkez sor oldja meg:
cout << expr(false) << '\n';

A false paramter mondja meg az expr()-nek, hogy nem kell meghvnia a get_token()-t ahhoz, hogy egy jabb szimblumot kapjon, amellyel dolgozhat. A cin ciklusonknt egyszeri ellenrzse biztostja, hogy a program befejezdik, ha hiba trtnik a bemeneti adatfolyammal, az END vizsglata pedig arrl gondoskodik, hogy a ciklusbl megfelelen lpjnk ki, ha a get_token() a fjl vghez r. A break utasts a legkzelebbi krlvev switch utastsbl vagy ciklusbl (azaz for, while vagy do utastsbl) lp ki. A PRINT (azaz '\n' s ';') vizsglata megknnyti az expr() dolgt az res kifejezsek kezelsben. A continue utasts egyenrtk azzal, hogy a ciklus legvgre ugrunk, gy ebben az esetben
while (cin) { // ... if (curr_tok == PRINT) continue; cout << expr(false) << '\n'; }

megegyezik a kvetkezvel:
while (cin) { // ... if (curr_tok != PRINT) cout << expr(false) << '\n'; }

6.1.6. Fejllomnyok
A szmolgp a standard knyvtr eszkzeit hasznlja. Ezrt a megfelel fejllomnyokat (header) be kell ptennk (#include), hogy befejezzk a programot:
#include<iostream> #include<string> #include<map> #include<cctype> // bemenet/kimenet // karakterlncok // asszociatv tmb // isalpha(), stb.

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

155

Ezen fejllomnyok mindegyike az std nvtrben nyjt szolgltatsokat, gy ahhoz, hogy az ltaluk nyjtott neveket felhasznlhassuk, vagy az std:: minstt kell hasznlnunk, vagy a globlis nvtrbe kell helyeznnk a neveket a kvetkezkppen:
using namespace std;

n az utbbit vlasztottam, hogy ne keverjem ssze a kifejezsek trgyalst a modularits krdskrvel. A 8. s a 9. fejezet trgyalja, milyen mdon lehet ezt a szmolgpet a nvterek hasznlatval modulokba szervezni s hogyan lehet forrsfjlokra bontani. A szabvnyos fejllomnyoknak szmos rendszeren .h kiterjeszts fjl megfeleljk van, melyek lerjk az osztlyokat, fggvnyeket stb. s a globlis nvtrbe is behelyezik azokat (9.2.1, 9.2.4, B.3.1).

6.1.7. Parancssori paramterek


Miutn a programot megrtam s kiprbltam, knyelmetlennek talltam, hogy elszr el kell indtani a programot, aztn be kell gpelni a kifejezseket, vgl ki kell lpni. A leggyakrabban egyetlen kifejezs kirtkelsre hasznltam a programot. Ha egy kifejezst meg lehetne adni parancssori paramterknt, jnhny billentyletst megtakarthatnnk. A program a main() (3.2., 9.4.) meghvsval kezddik, amely kt paramtert kap: az egyik, melyet ltalban argc-nek neveznek, a paramterek (argumentumok) szmt adja meg, a msik a paramterekbl ll tmb, ezt rendszerint argv-nek hvjk. A paramterek karakterlncok, ezrt argv tpusa char*[argc+1] lesz. A program neve (ahogy az a parancssorban elfordul) argc[0]-knt addik t, gy argc rtke mindig legalbb 1. A paramterek listjt a null karakter zrja le, gy argv[argc]==0. Vegyk az albbi parancsot:
dc 150/1.1934

Ekkor a paramterek rtke a kvetkez: argc: argv:

2 0
"dc" "150/1.1934"

Forrs: http://www.doksi.hu

156

Alapok

Mivel a main() meghvsra vonatkoz szablyok a C nyelv kvetelmnyeivel azonosak, a hvskor C tpus tmbk s karakterlncok hasznlatosak. A parancssori paramterek beolvassa egyszer, a problma csak az, hogyan hasznljuk azokat gy, hogy minl kevesebbet kelljen programoznunk. Az tlet a kvetkez: olvassunk ugyangy a parancssori karakterlncbl, mint a bemeneti adatfolyamokbl. A karakterlncbl olvas adatfolyam neve micsoda meglepets istringstream. Sajnos nincs elegns mdja annak, hogy cin-knt az istringstream-re hivatkozhassunk, ezrt ki kell tallnunk, hogy a szmolgp bemeneti fggvnyei hogyan hivatkozzanak vagy az istringstream-re vagy a cin-re, attl fggen, milyen parancssori paramtereket adunk meg. Egyszer megolds, ha bevezetnk egy input nev globlis mutatt, amely a hasznland bemeneti adatfolyamra mutat; minden bemeneti eljrsban ezt fogjuk felhasznlni:
istream* input; // mutat bemeneti adatfolyamra

int main(int argc, char* argv[ ]) { switch (argc) { case 1: // olvass a szabvnyos bemenetrl input = &cin; break; case 2: // a karakterlnc paramter beolvassa input = new istringstream(argv[1]); break; default: error("tl sok paramter"); return 1; } table["pi"] = 3.1415926535897932385; table["e"] = 2.7182818284590452354; while (*input) { get_token(); if (curr_tok == END) break; if (curr_tok == PRINT) continue; cout << expr(false) << '\n'; } if (input != &cin) delete input; return no_of_errors; // elre megadott nevek beillesztse

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

157

Az istringstream olyan istream, amely karakterlnc paramterbl olvas (21.5.3). Amikor elri a lnc vgt, pontosan ugyangy jelzi azt, mint a tbbi adatfolyam a bemenet vgt (3.6, 21.3.3). Az istringstream hasznlathoz be kell pteni az <sstream> fejllomnyt. Knny lenne gy mdostani a main()-t, hogy tbb parancssori paramtert is elfogadjon, de erre nincs szksg, mert egyetlen paramterknt tbb kifejezst is tadhatunk:
dc "rate=1.1934;150/rate;19.75/rate;217/rate"

Azrt hasznlok idzjeleket, mert a ; a UNIX rendszerekben parancs-elvlasztknt hasznlatos. Ms rendszerek szablyai a program indtsakor paramterek megadsra vonatkozan eltrek. Nem volt elegns dolog gy mdostani a bemeneti eljrsokat, hogy cin helyett *input-ot hasznljanak, hogy ezzel rugalmasabbak legyenek s klnbz bemeneti forrsokkal mkdhessenek. A vltoztats elkerlhet lett volna, ha kell elreltssal mr a kezdetektl bevezetnk valamilyen, az input-hoz hasonl dolgot. ltalnosabb s hasznosabb megoldst kszthetnk, ha szrevesszk, hogy a bemenet forrsa valjban a szmolgp modul paramtere kell, hogy legyen. Az alapvet problma, amit ezzel a szmolgppel rzkeltetni akartam, az, hogy a szmolgp csak fggvnyek s adatok gyjtemnye. Nincs olyan modul (2.4) vagy objektum (2.5.2), amely kifejezett s egyrtelm mdon brzolja a szmolgpet. Ha egy szmolgp modul vagy szmolgp tpus tervezse lett volna a clom, akkor termszetesen meggondoltam volna, milyen paramterei lehetnek a modulnak/tpusnak (8.5[3], 10.6[16]).

6.1.8. Megjegyzs a stlussal kapcsolatban


A standard knyvtrbeli map szimblumtblaknt val hasznlata majdnem csalsnak tnhet azoknak a programozknak a szemben, akik nem ismerik az asszociatv tmbket. De nem az. A standard knyvtr s ms knyvtrak arra valk, hogy hasznljk azokat. A knyvtrak tervezskor s megvalstskor ltalban nagyobb figyelmet kapnak, mint amennyit egy programoz megengedhet magnak, amikor sajt kezleg olyan kdot r, amit csak egyetlen program hasznl fel. Ha megnzzk a szmolgp kdjt (klnsen az els vltozatot), lthatjuk, hogy nem sok hagyomnyos C stlus, alacsonyszint kd tallhat benne. Szmos hagyomnyos trkkt helyettestettnk azzal, hogy olyan standard knyvtrbeli osztlyokat hasznltunk, mint az ostream, string, s map (3.4, 3.5, 3.7.4, 17.fejezet).

Forrs: http://www.doksi.hu

158

Alapok

Vegyk szre, hogy az aritmetika, a ciklusok, st az rtkadsok is viszonylag ritkn fordulnak el. ltalban ilyennek kellene lennie egy olyan kdnak, amely nem kezeli a hardvert kzvetlenl s nem l alacsonyszint elvont adatbrzolsokkal.

6.2. Opertorok ttekints


Ez a rsz sszefoglalja a kifejezseket s bemutat nhny pldt. Minden opertort egy vagy tbb nv kvet, amely pldaknt szolgl az ltalnosan hasznlt megnevezsekre s a szoksos hasznlatra. A tblzatokban az osztlynv egy osztly neve, a tag egy tag neve, az objektum egy olyan kifejezs, amelynek az eredmnye osztlyobjektum, a mutat egy mutat eredmny kifejezs, a kif egy kifejezs, s a balrtk egy olyan kifejezs, amely nem konstans objektumot jell. A tpus csak akkor lehet egy teljesen ltalnos tpusnv (*-gal, ()-lel stb.), ha zrjelek kz van zrva; mshol megszortsok vonatkoznak r (A.5). A kifejezsek formja fggetlen az operandusok tpustl. Az itt bemutatott jelentsek arra az esetre vonatkoznak, amikor az operandusok beptett tpusak (4.1.1). A felhasznli tpus operandusokra alkalmazott opertorok jelentst magunk hatrozhatjuk meg (2.5.2, 11. fejezet). A tblzat minden celljban azonos erssg (precedencij) opertorok tallhatk. A felsbb cellkban lev opertorok az als cellkban levkkel szemben elnyt lveznek. Pldul a+b*c jelentse a+(b*c), nem pedig (a+b)*c, mert a * magasabb precedencij, mint a +. Az egyoperandus (unris) s az rtkad opertorok jobbrl balra, az sszes tbbi balrl jobbra rtelmezend. Pldul a=b=c jelentse a=(b=c), a+b+c jelentse (a+b)+c, *p++ jelentse pedig *(p++), nem (*p)++. Nhny nyelvtani szablyt nem lehet kifejezni a precedencival s az asszociativitssal (ktssel). Pldul a=b<c?d=e:f=g jelentse a=((b<c)?(d=e):(f=g)), de ahhoz, hogy ezt eldnthessk, meg kell nznnk a nyelvtant (A.5).

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

159

Opertor ttekints hatkr-felolds hatkr-felolds globlis hatkr globlis hatkr tagkivlaszts tagkivlaszts indexels fggvnyhvs rtk ltrehozsa nvels uttaggal cskkents uttaggal tpusazonosts futsi idej tpusazonosts futsi idben ellenrztt tpusknyszerts fordtsi idben ellenrztt tpusknyszerts nem ellenrztt tpusknyszerts konstans tpusknyszerts objektum mrete tpus mrete nvels eltaggal cskkents eltaggal komplemenskpzs (logikai) nem mnusz eljel plusz eljel cm opertor indirekci ltrehozs (memriafoglals) ltrehozs (memriafoglals s kezdeti rtkads) ltrehozs (elhelyezs) ltrehozs (elhelyezs s kezdeti rtkads) felszmols (felszabadts) tmb felszmolsa tpuskonverzi osztlynv :: tag nvtr_nv :: tag :: nv :: minstett_nv objektum . tag mutat -> tag mutat [kif] kif (kif_lista) tpus (kif_lista) balrtk ++ balrtk -typeid (tpus) typeid (kif) dynamic_cast <tpus> (kif) static_cast <tpus> (kif) reinterpret_cast <tpus> (kif) const_cast <tpus> (kif) sizeof kif sizeof (tpus) ++ balrtk -- balrtk ~ kif ! kif - kif + kif & balrtk * kif new tpus new (kif_lista) new (kif_lista) tpus new (kif_lista) tpus (kif_lista) delete mutat delete [ ] mutat (tpus) kif

Forrs: http://www.doksi.hu

160

Alapok

Opertor ttekints (folytats) tagkivlaszts tagkivlaszts szorzs oszts modul (maradkkpzs) sszeads (plusz) kivons (mnusz) balra lptets jobbra lptets kisebb kisebb vagy egyenl nagyobb nagyobb vagy egyenl egyenl nem egyenl bitenknti S bitenknti kizr VAGY bitenknti megenged VAGY logikai S logikai megenged VAGY feltteles kifejezs objektum .*tagra_hivatkoz_mutat mutat -> *tagra_hivatkoz_mutat kif * kif kif / kif kif % kif kif + kif kif - kif kif << kif kif >> kif kif < kif kif <= kif kif > kif kif >= kif kif == kif kif != kif kif & kif kif ^ kif kif | kif kif && kif kif || kif kif ? kif : kif

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

161

Opertor ttekints (folytats) egyszer rtkads szorzs s rtkads oszts s rtkads maradkkpzs s rtkads sszeads s rtkads kivons s rtkads balra lptets s rtkads jobbra lptets s rtkads S s rtkads megenged VAGY s rtkads kizr VAGY s rtkads kivtel kivltsa vessz (mveletsor) balrtk = kif balrtk *= kif balrtk /= kif balrtk %= kif balrtk += kif balrtk -= kif balrtk <<= kif balrtk >>= kif balrtk &= kif balrtk |= kif balrtk ^= kif throw kif kif , kif

6.2.1. Eredmnyek
Az aritmetikai mveletek eredmnynek tpust az a szablyhalmaz dnti el, amelyet ltalnos aritmetikai talaktsok-nak neveznk (C.6.3). A f cl az, hogy a legtgabb operandustpussal megegyez eredmny jjjn ltre. Ha egy binris opertor operandusa pldul lebegpontos, a szmtst lebegpontos aritmetikval vgezzk s az eredmny egy lebegpontos rtk lesz. Ha long tpus operandusa van, a szmts hossz egsz (long) aritmetikval trtnik, az eredmny pedig long rtk lesz. Az int-nl kisebb operandusok (mint a bool s a char) int-t alakulnak, mieltt az opertort alkalmazzuk rjuk. Az ==, <= stb. relcis (sszehasonlt) opertorok logikai rtkeket adnak vissza. A felhasznl ltal megadott opertorok jelentst s eredmnyt deklarcijuk hatrozza meg (11.2). Ha egy opertornak balrtk operandusa van, akkor ha ez logikailag lehetsges az opertor eredmnye egy olyan balrtk lesz, amely a balrtk operandust jelli:

Forrs: http://www.doksi.hu

162

Alapok

void f(int x, int y) { int j = x = y; int* p = &++x; int* q = &(x++); int* pp = &(x>y?x:y); }

// x=y rtke az x rtkads utni rtke // p x-re mutat // hiba: x++ nem balrtk // a nagyobb rtk int cme

Ha a ? : msodik s harmadik operandusa is balrtk s ugyanolyan tpusak, az eredmny a megfelel tpus balrtk lesz. Az, hogy ilyen mdon megrizzk a balrtkeket, nagy rugalmassgot ad az opertorok hasznlatban. Ez klnsen akkor fontos, ha olyan kdot runk, amelynek egyformn s hatkonyan kell mkdnie beptett s felhasznli tpusok esetben is (pldul ha olyan sablonokat vagy programokat runk, amelyek C++ kdot hoznak ltre). A sizeof eredmnye a size_t nev eljel nlkli integrlis tpus, melynek meghatrozsa a <cstddef> fejllomnyban szerepel, a mutat-kivons pedig egy eljeles integrlis tpus, amit ptrdiff_t-nek hvnak s szintn a <cstddef> fejllomny rja le. A fordtnak nem kell ellenriznie az aritmetikai tlcsordulst s ltalban nem is teszi meg. Pldul:
void f() { int i = 1; while (0 < i) i++; cout << "Az i negatv lett!" << i << '\n'; }

A ciklus elbb-utbb az i rtkt a legnagyobb egsz rtken tl nveli. Ami ekkor trtnik, nem meghatrozott; az rtk jellemzen egy negatv szmig r krbe (az n gpemen ez -2147483648). Hasonlan, a nullval oszts eredmnye sem meghatrozott, ez viszont rendszerint a program hirtelen befejezdst eredmnyezi. Az alulcsorduls, a tlcsorduls s a nullval val oszts nem vlt ki szabvnyos kivteleket (14.10).

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

163

6.2.2. Kirtkelsi sorrend


A kifejezseken belli rszkifejezsek kirtkelsi sorrendje nem meghatrozott, gy nem ttelezhetjk fel pldul azt sem, hogy a kifejezs kirtkelse balrl jobbra trtnik:
int x = f(2)+g(3); // nem meghatrozott, hogy f() vagy g() hvdik meg elszr

Jobb kdot kszthetnk, ha a kifejezsek kirtkelsi sorrendje nem kttt, de a kirtkelsi sorrendre vonatkoz megszortsok hinya elre nem meghatrozott eredmnyekhez vezethet:
int i = 1; v[i] = i++; // nem meghatrozott eredmny

A fenti kifejezs vagy v[1]=1-knt, vagy v[2]=1-knt rtkeldik ki, esetleg mg furcsbban viselkedik. A fordtprogramok figyelmeztethetnek az ilyen ktrtelmsgekre, sajnos, a legtbb ezt nem teszi meg. A , (vessz), a && (logikai S), s a || (logikai VAGY) opertorok esetben biztostott, hogy a bal oldali operandus a jobb oldali eltt rtkeldik ki. A b=(a=2,a+1) pldul a bnek 3-at ad rtkl. A || s a && hasznlatra vonatkoz pldk a 6.2.3-ban tallhatk. Beptett tpusoknl a && msodik operandusa csak akkor rtkeldik ki, ha az els operandus true, a || msodik operandusa pedig csak akkor, ha az els operandus rtke false; ezt nha rvid vagy rvidzras kirtkelsnek (short-circuit evaluation) nevezik. Jegyezzk meg, hogy a , (vessz) mveletsor-jelz logikailag klnbzik attl a vessztl, amit arra hasznlunk, hogy a fggvnyhvsoknl elvlasszuk a paramtereket. Nzzk az albbi pldt:
f1(v[i],i++); f2( (v[i],i++) ); // kt paramter // egy paramter

Az f1 meghvsnak kt paramtere van, v[i] s i++, a paramter-kifejezsek kirtkelsi sorrendje pedig nem meghatrozott. Az olyan megolds, amely fgg a paramter-kifejezsek sorrendjtl, nagyon rossz stlusrl rulkodik s eredmnye nem meghatrozott. Az f2 meghvshoz egy paramtert adtunk meg; a (v[i], i++) vesszs kifejezs, amely i++-szal egyenrtk.

Forrs: http://www.doksi.hu

164

Alapok

A csoportosts kiknyszertsre zrjeleket hasznlhatunk. Pldul a*b/c jelentse (a*b)/c, ezrt zrjeleket kell hasznlnunk, ha a*(b/c)-t akarunk kapni. Az a*(b/c) kifejezs csak akkor rtkeldhet ki (a*b)/c-knt, ha a felhasznl nem tud klnbsget tenni kztk. Az a*(b/c) s az (a*b)/c szmos lebegpontos szmtsnl jelentsen klnbzik, gy a fordtprogram pontosan gy fogja az ilyen kifejezseket kirtkelni, ahogy azokat lertuk.

6.2.3. Az opertorok sorrendje


A precedencia s a ktsi (asszociativitsi) szablyok a leggyakoribb hasznlatot tkrzik. Pldul
if (i<=0 || max<i) // ...

azt jelenti, hogy ha i kisebb vagy egyenl 0-nl VAGY max kisebb i-nl. Ez egyenrtk az albbival:
if ( (i<=0) || (max<i) ) // ...

Az albbi rtelmetlen, de szablyos kifejezssel viszont nem:


if (i <= (0||max) < i) // ...

Zrjeleket hasznlni azonban mindig hasznos, ha a programoznak ktsgei vannak ezekkel a szablyokkal kapcsolatban. A zrjelek hasznlata mg gyakoribb, ha a rszkifejezsek bonyolultabbak. A bonyolult rszkifejezsek mindig hiba forrsai lehetnek, ezrt ha gy rezzk, hogy szksgnk van zrjelekre fontoljuk meg, hogy nem kellene-e egy kln vltoz hasznlatval sztbontanunk a kifejezst. Vannak olyan esetek, amikor az opertorok sorrendje nem a magtl rtetd rtelmezst eredmnyezi:
if (i&mask == 0) // hopp! == kifejezs & operandusaknt

Ekkor nem az trtnik, hogy alkalmazzuk a mask-ot az i-re, majd megnzzk, hogy az eredmny 0-e. Mivel az == elnyt lvez az & (ktoperandus) mvelettel szemben, a kifejezs i&(mask==0)-knt lesz rtelmezve. Szerencsre a fordtprogram knnyen figyelmeztethet az ilyen hibkra. Ebben az esetben a zrjelek fontosak:
if ((i&mask) == 0) // ...

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

165

rdemes megjegyezni, hogy a kvetkez nem gy mkdik, ahogy egy matematikus elvrn:
if (0 <= x <= 99) // ...

Ez megengedett, de rtelmezse (0<=x)<=99, ahol az els sszehasonlts eredmnye vagy true vagy false. A logikai rtket a fordtprogram aztn automatikusan 1-re vagy 0-ra alaktja, amit aztn sszehasonltva 99-cel true-t kapunk. A kvetkezkppen vizsglhatjuk meg, hogy x a 0..99 tartomnyban van-e:
if (0<=x && x<=99) // ...

Gyakori hiba kezdknl, hogy a felttelekben =-t (rtkadst) hasznlnak == (egyenl) helyett:
if (a = 7) // hopp! konstans rtkads a felttelben

Ez termszetes, mert az = jelentse sok nyelvben egyenl. A fordtprogramok ltalban figyelmeztetnek is erre.

6.2.4. Bitenknti logikai mveletek


Az &, |, ^, -, >> s << bitenknti logikai opertorokat integrlis (egsz tpus) objektumokra s felsorolsokra alkalmazzuk azaz a bool, char, short, int, long tpusokra, ezek eljel nlkli (unsigned) megfelelire s az enum tpusokra. Az eredmny tpust a szoksos aritmetikai talaktsok (C.6.3.) dntik el. A bitenknti logikai opertorok jellemz felhasznlsa a kis halmazok (bitvektorok) fogalmnak megvalstsa. Ebben az esetben egy eljel nlkli egsz minden bitje a halmaz egy elemt jelli, s a bitek szma korltozza a halmaz elemeinek szmt. Az & binris opertort metszetknt, a | opertort uniknt, a ^-ot szimmetrikus differenciaknt, a ~-t pedig komplemensknt rtelmezzk. Felsorol tpust arra hasznlhatunk, hogy megnevezzk egy ilyen halmaz elemeit. me egy rvid plda, melyet az ostream megvalstsbl vettnk klcsn:
enum ios_base::iostate { goodbit=0, eofbit=1, failbit=2, badbit=4 };

Forrs: http://www.doksi.hu

166

Alapok

Az adatfolyam az llapotot gy llthatja be s ellenrizheti:


state = goodbit; // ... if (state&(badbit|failbit))

// nem megfelel adatfolyam

A kln zrjelek azrt szksgesek, mert az & elnyt lvez a | mveleti jellel szemben. Egy fggvny gy jelezheti, hogy elrte a bemenet vgt:
state |= eofbit;

A |= opertort arra hasznljuk, hogy az llapothoz hozzadjunk valamilyen j informcit. Az egyszer state=eofbit rtkads kitrlt volna minden ms bitet. Ezek az adatfolyam-llapotjelzk megfigyelhetk a folyam megvalstsn kvl is. Pldul gy nzhetjk meg, hogyan klnbzik kt adatfolyam llapota:
int diff = cin.rdstate()^cout.rdstate(); // rdstate() az llapotot adja vissza

Az adatfolyam-llapotok klnbsgeinek kiszmtsa nem tl gyakori, ms hasonl tpusoknl viszont alapvet mvelet. Vegyk pldul azt az esetet, amikor ssze kell hasonltanunk azt a bitvektort, amely a kezelt megszaktsok halmazt jelli, egy msik bitvektorral, amely olyan megszaktsok halmazt brzolja, melyek arra vrnak, hogy kezeljk ket. Jegyezzk meg, hogy ezt a zsonglrkdst a bitekkel az iostream megvalstsbl vettk s nem a felhasznli felletbl. A knyelmes bitkezels nagyon fontos lehet, de a megbzhatsg, a mdosthatsg, vagy a hordozhatsg rdekben a rendszer alacsonyabb szintjein kell tartanunk. ltalnosabb halmazfogalomra nzzk meg a standard knyvtrbeli set-et (17.4.3), bitset-et (17.5.3), s a vector<bool>-t (16.3.11). A mezk (C.8.1) hasznlata igazn knyelmes mdon rvidti le azt a mveletet, amikor lptetssel s maszkolssal vesznk ki bitmezket egy szbl. Ezt termszetesen megtehetjk a bitenknti logikai opertorokkal is. Egy 32 bites long kzps 16 bitjt pldul gy vehetjk ki:
unsigned short middle(long a) { return (a>>8)&0xffff; }

A bitenknti logikai opertorokat ne keverjk ssze az &&, || s ! logikai opertorokkal. Az utbbiak vagy true-t, vagy false-t adnak vissza, s elsdlegesen akkor hasznosak, amikor egy

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

167

if, while, vagy for utastsban (6.3.2, 6.3.3) felttelt runk. Pldul az !0 (nem nulla) true rtk, mg a ~0 (a nulla komplemense) egy csupa egyesbl ll bitminta, amely a -1 rtk kettes komplemensbeli brzolsa.

6.2.5. Nvels s cskkents


A ++ opertort arra hasznljuk, hogy egy rtk nvelst kzvetlenl, s nem az sszeads s rtkads prostsval fejezzk ki. Definci szerint a ++lvalue jelentse lvalue+=1, ez pedig lvalue=lvalue+1-et jelent, feltve, hogy a balrtknek nincs mellkhatsa". A nvelend objektumot jell kifejezs (csak) egyszer rtkeldik ki. A cskkentst ugyangy a -- opertor fejezi ki. A ++ s -- opertorokat hasznlhatjuk eltagknt s uttagknt is. A ++x rtke az x j (megnvelt) rtke lesz, pldul az y=++x egyenrtk az y=(x+=1)gyel. Az x++ rtke azonban az x rgi rtke: az y=x++ egyenrtk az y=(t=x,x+=1,t)-vel, ahol t egy x-szel azonos tpus vltoz. A mutatk sszeadshoz s kivonshoz hasonlan a mutatkra alkalmazott ++ s -- mkdst azok a tmbelemek hatrozzk meg, amelyekre a mutat hivatkozik; p++ a p-t a kvetkez tmbelemre lltja (5.3.1). A nvel opertorok klnsen a ciklusokban hasznlatosak, vltozk nvelsre vagy cskkentsre. Egy nulla vgzds karakterlncot pldul a kvetkezkppen msolhatunk t:
void cpy(char* p, const char* q) { while (*p++ = *q++) ; }

A C-hez hasonlan a C++-t is szeretik s gyllik azrt, mert megengedi az ilyen tmr, kifejezskzpont kdolst. Mivel a
while (*p++ = *q++) ;

kifejezs meglehetsen zavaros a nem C programozk szmra, ez a kdolsi stlus viszont nem ritka a C-ben s a C++-ban, megri kzelebbrl megvizsglnunk. Vegyk elszr a karaktertmbk msolsnak egy hagyomnyosabb mdjt:
int length = strlen(q); for (int i = 0; i<=length; i++) p[i] = q[i];

Forrs: http://www.doksi.hu

168

Alapok

Ez pazarls. A nulla vgzds karakterlnc hosszt gy hatrozzuk meg, hogy a nulla vgzdst keresve vgigolvassuk azt. gy ktszer olvassuk vgig a teljes lncot: egyszer azrt, hogy meghatrozzuk a hosszt, egyszer pedig azrt, hogy tmsoljuk. Ezrt inkbb prbljuk ezt:
int i; for (i = 0; q[i]!=0 ; i++) p[i] = q[i]; p[i] = 0; // lezr nulla

Az i vltozt indexelsre hasznljuk, de ki lehet kszblni, mert p s q mutatk:


while (*q != 0) { *p = *q; p++; q++; } *p = 0;

// lptets a kvetkez karakterre // lptets a kvetkez karakterre // lezr nulla

Mivel az uttagknt hasznlt nvel opertor megengedi, hogy elszr felhasznljuk az rtket, s csak azutn nveljk meg, a kvetkezkppen rhatjuk jra a ciklust:
while (*q != 0) { *p++ = *q++; } *p = 0;

// lezr nulla

A *p++ = *q++ rtke *q, ezrt a pldt gy mdosthatjuk:


while ((*p++ = *q++) != 0) { }

Ebben az esetben addig nem vesszk szre, hogy *q nulla, amg be nem msoljuk *p-be s meg nem nveljk p-t. Kvetkezskppen elhagyhatjuk az utols rtkadst, amiben a nulla vgzdst adjuk rtkl. Vgl tovbb rvidthetjk a pldt azzal, hogy szrevesszk, nincs szksgnk az res blokkra s hogy felesleges a !=0 vizsglat, mert egy mutat vagy integrlis felttel mindig sszehasonltdik 0-val. gy megkapjuk azt a vltozatot, amelyet clul tztnk ki.
while (*p++ = *q++) ;

Ez a vltozat vajon kevsb olvashat, mint az elz? Egy tapasztalt C vagy C++ programoz szmra nem. Hatkonyabb idben s trterletben, mint az elz? Az els vltozatot ki-

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

169

vve, ami meghvta az strlen()-t, nem igazn. Az, hogy melyik vltozat a leghatkonyabb, a gp felptstl s a fordtprogramtl fgg, a nulla vgzds karakterlncok msolsnak leghatkonyabb mdja viszont ltalban a standard knyvtrbeli msol fggvny.
char* strcpy(char*, const char*); // a <string.h> fejllomnybl

ltalnosabb msolsra a szabvnyos copy algoritmust (2.7.2, 18.6.1) hasznlhatjuk. Ahol lehetsges, rszestsk elnyben a standard knyvtr lehetsgeit a mutatkkal s bjtokkal val gyeskedssel szemben. A standard knyvtr fggvnyei lehetnek helyben kifejtett fggvnyek (7.1.1) vagy egyedi gpi utastsokkal megvalstottak. Ezrt gondosan fontoljuk meg, mieltt elhinnnk, hogy valamilyen kzzel rt kdrszlet fellmlja a knyvtri fggvnyek teljestmnyt.

6.2.6. Szabad tr
A nvvel rendelkez objektumok lettartamt (lifetime) hatkrk (4.9.4) dnti el, gyakran azonban hasznos, ha olyan objektumot hozunk ltre, amely fggetlenl ltezik attl a hatkrtl, ahol ltrehoztuk. Nevezetesen gyakori, hogy olyan objektumokat hozunk ltre, amelyek akkor is felhasznlhatk, miutn visszatrtnk abbl a fggvnybl, ahol ltrehoztuk azokat. Az ilyen objektumokat a new opertor hozza ltre s a delete opertort hasznlhatjuk felszmolsukra. A new ltal ltrehozott objektumokra azt mondjuk, hogy a szabad trban vannak (free store), vagy azt, hogy kupac-objektumok (heap), vagyis a dinamikus memriban vannak. Nzzk meg, hogyan rnnk meg egy fordtprogramot olyan stlusban, ahogy az asztali szmolgpnl tettk (6.1). A szintaktikai elemz fggvnyek felpthetnek egy kifejezsft a kdkszt szmra:
struct Enode { Token_value oper; Enode* left; Enode* right; // ... }; Enode* expr(bool get) { Enode* left = term(get);

Forrs: http://www.doksi.hu

170

Alapok

for (;;)

switch(curr_tok) { case PLUS: case MINUS: { Enode* n = new Enode; n->oper = curr_tok; n->left = left; n->right = term(true); left = n; break; } default: return left; }

// Enode ltrehozsa a szabad trban

// csompont visszaadsa

A kdkszt aztn felhasznln az eredmnyl kapott csompontokat (node) s trln azokat:


void generate(Enode* n) { switch (n->oper) { case PLUS: // ... delete n; } }

// Enode trlse a szabad trbl

A new ltal ltrehozott objektum addig ltezik, amg kifejezetten meg nem semmistjk a delete-tel. Ezutn a new jra felhasznlhatja az objektum ltal lefoglalt trhelyet. A C++vltozatok nem garantljk, hogy van szemtgyjt (garbage collector), amely megkeresi azokat az objektumokat, amelyekre nincs mr hivatkozs s jra felhasznlhatv teszi azok helyt. Kvetkezskppen felttelezzk, hogy a new ltal ltrehozott objektumokat magunknak kell megsemmistennk, a delete-et hasznlva. Ha van szemtgyjt, a deleteek a legtbb esetben elhagyhatk (C.9.1). A delete opertort csak a new ltal visszaadott mutatra vagy nullra lehet alkalmazni. Ha a delete-et nullra alkalmazzuk, nem lesz hatsa. A new opertornak egyedi vltozatait is meghatrozhatjuk (15.6).

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

171

6.2.6.1. Tmbk A new hasznlatval ltrehozhatunk objektumokbl ll tmbt is:


char* save_string(const char* p) { char* s = new char[strlen(p)+1]; strcpy(s,p); // msols p-bl s-be return s; } int main(int argc, char* argv[ ]) { if (argc < 2) exit(1); char* p = save_string(argv[1]); // ... delete[ ] p; }

A sima delete opertort arra hasznlhatjuk, hogy egyes objektumokat felszmoljuk, a delete[ ] tmbk felszmolsra hasznlatos. Ha vissza akarjuk nyerni a new ltal lefoglalt trhelyet, a delete-nek vagy a delete[ ]-nek meg kell tudni llaptani, mekkora a lefoglalt objektum mrete. Ebbl az kvetkezik, hogy a szabvnyos new opertorral ltrehozott objektumok valamivel tbb helyet foglalnak, mint a statikus objektumok. Az objektum mrett ltalban egy gpi sz trolja. Jegyezzk meg, hogy a vector (3.7.1, 16.3) valdi objektum, ezrt ltrehozsra s felszmolsra a sima new-t s delete-et hasznlhatjuk:
void f(int n) { vector<int>* p = new vector<int>(n); int* q = new int[n]; // ... delete p; delete[ ] q; }

// nll objektum // tmb

A delete[ ] opertort csak a new ltal visszaadott mutatra vagy nullra alkalmazhatjuk. Ha a delete[ ]-et nullra alkalmazzuk, nem lesz hatsa.

Forrs: http://www.doksi.hu

172

Alapok

6.2.6.2. Memria-kimerls A szabad tr new, delete, new[ ] s delete[ ] opertorai fggvnyekknt vannak megvalstva:
void* operator new(size_t); void operator delete(void*); void* operator new[ ](size_t); void operator delete[ ](void*); // hely az nll objektum szmra // hely a tmb szmra

Ha a new opertornak egy objektum szmra kell helyet foglalnia, az operator new()-t hvja meg, hogy az megfelel szm bjtot foglaljon le. Ha tmb szmra foglal helyet, az operator new[ ]() meghvsra kerl sor. Az operator new() s az operator new [ ]() szabvnyos megvalstsa a visszaadott memrit nem tlti fel kezdrtkkel. Mi trtnik, ha a new nem tall lefoglalhat helyet? Alaprtelmezs szerint a lefoglal bad_alloc kivtelt vlt ki (a msik lehetsget illeten lsd 19.4.5-t):
void f() { try {

for(;;) new char[10000]; } catch(bad_alloc) { cerr << "Elfogyott a memria!\n"; }

Akrmennyi memria ll a rendelkezsnkre, a kd vgl meg fogja hvni a bad_alloc esemnykezeljt. Magunk is meghatrozhatjuk, mit csinljon a new, amikor kifogy a memria. Ha a new nem jr sikerrel, elszr azt a fggvnyt hvja meg, amelyet a <new> fejllomnyban bevezetett set_new_handler() fggvnnyel elzleg belltottunk (amennyiben ezt megtettk):
void out_of_store() { cerr << "Az operator new nem jrt sikerrel: nincs trhely\n"; throw bad_alloc(); }

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

173

int main() { set_new_handler(out_of_store); for (;;) new char[10000]; cout << "ksz\n"; }

// out_of_store lesz a new_handler

Ez azonban soha nem fog elrni addig, hogy kirja a ksz-t. Ehelyett a kvetkezt fogja kirni:
Az operator new nem jrt sikerrel: nincs trhely

Lsd 14.4.5-t az operator new() egy olyan lehetsges megvalstsrl, amely megvizsglja, ltezik-e meghvhat kezelfggvny, s ha nem tall ilyet, bad_alloc-ot vlt ki. Egy new_handler azonban valami okosabbat is tehet, mint hogy egyszeren befejezi a programot. Ha tudjuk, hogyan mkdik a new s a delete pldul azrt, mert sajt operator new()-t s operator delete()-et rtunk ,a kezelfggvny megprblhat valamennyi memrit keresni, hogy a new visszatrhessen, vagyis a felhasznl gondoskodhat szemtgyjtrl, gy elhagyhatv teheti a delete-et (br ez ktsgtelenl nem kezdknek val feladat). Majdnem mindenkinek, akinek automatikus szemtgyjtre van szksge, a leghasznosabb, ha szerez egy mr megrt s ellenrztt termket (C.9.1). Azzal, hogy j new_handler-t lltunk be, felvllaljuk, hogy neknk kell trdnnk a memria kimerlsvel kapcsolatos problmkkal a new minden hasznlatakor. A memriafoglalsnak ktfle tja ltezik: vagy gondoskodunk nem szabvnyos lefoglal s felszabadt fggvnyekrl (15.6) a new szablyos hasznlata szmra, vagy a felhasznl ltal adott tovbbi foglalsi adatokra tmaszkodunk (10.4.11, 19.4.5.).

6.2.7. Meghatrozott tpuskonverzik


Nha nyers memrival kell dolgoznunk, azaz a tr olyan objektumot tartalmaz vagy fog tartalmazni, melynek tpusa ismeretlen a fordtprogram szmra. Ilyen eset, amikor a memriafoglal (alloktor) egy jonnan lefoglalt memriaterletre hivatkoz void* tpus mutatt ad vissza, vagy ha azt akarjuk kifejezni, hogy egy adott egsz rtket gy kell kezelni, mint egy I/O eszkz cmt:
void* malloc(size_t); void f() { int* p = static_cast<int*>(malloc(100)); // a new ltal lefoglalt helyet int-knt hasznljuk IO_device* d1 = reinterpret_cast<IO_device*>(0Xff00); // eszkz a 0Xff00 cmen // ... }

Forrs: http://www.doksi.hu

174

Alapok

A fordtprogram nem ismeri a void* ltal mutatott objektum tpust. Azt sem tudja, vajon a 0Xff00 rvnyes cm-e. Kvetkezskppen az talaktsok helyessge teljes mrtkben a programoz kezben van. Az explicit (pontosan meghatrozott) tpusknyszertsek (casting) nha szksgesek, de hagyomnyosan tl sokszor hasznljk azokat, s jelents hibaforrsok. A static_cast opertor egymssal kapcsolatban lev tpusok kztti konverzit vgez, pldul kt, ugyanazon osztlyhierarchiban lv mutattpus, integrlis tpus s felsorol tpus, vagy lebegpontos tpus s integrlis tpus kzttit. A reinterpret_class olyan tpusok talaktst hajtja vgre, amelyek nincsenek kapcsolatban, pldul egszrl mutatra vagy mutattpusrl egy msik, nem rokon mutatra konvertl. Ez a megklnbztets lehetv teszi, hogy a fordtprogram elvgezzen bizonyos minimlis tpusellenrzst a static_cast esetben, s megknnyti, hogy a programoz megtallja a veszlyesebb talaktsokat, melyeket a reinterpret_cast jell. Nhny static_cast hordozhat, a reinterpret_cast-ok kzl viszont csak kevs. A reinterpret_cast esetben nem sok dolog biztos; ltalban j tpust hoz ltre, amelynek ugyanaz a bitmintja, mint a paramter. Ha a cl legalbb annyi bites, mint az eredeti rtk, az eredmnyt a reinterpret_cast-tal az eredeti tpusra alakthatjuk s hasznlhatjuk azt. A reinterpret_cast eredmnyt csak akkor lehet biztosan felhasznlni, ha annak tpusa pontosan az a tpus, amelyet az rtk meghatrozsra hasznltunk. Ha ksrtst rznk, hogy pontosan meghatrozott tpusknyszertst alkalmazzunk, sznjunk idt arra, hogy meggondoljuk, vajon tnyleg szksges-e. A C++-ban az explicit tpusknyszerts a legtbb esetben szksgtelen olyankor, amikor a C-ben szksg lenne r (1.6), s sok olyan esetben is, ahol a C++ korai vltozataiban szksges volt (1.6.2, B.2.3). Szmos programban az ilyen tpuskonverzi teljesen elkerlhet; mshol nhny eljrsra korltozhatjuk a hasznlatt. Ebben a knyvben explicit tpusknyszertst valsgh helyzetekben csak a 6.2.7, 7.7, 13.5, 15.4, s 25.4.1 pontokban hasznlunk. A futsi idben ellenrztt konverzik egyik formja a dynamic_cast (15.4.1). A const minstt eltvolt konstanstalant const_cast (15.4.2.1) opertort szintn hasznlhatjuk. A C++ a C-bl rklte a (T)e jellst, amely brmilyen talaktst elvgez, amit ki lehet fejezni a static_cast, reinterpret_cast s a const_cast kombincijaknt. Eredmnyl T tpus rtk jn ltre (B.2.3). Ez a C stlus konverzi sokkal veszlyesebb, mint a fent emltettek, mert a jellst nehezebben lehet szrevenni egy nagy programban s a programoz szndka szerinti talakts fajtja nem nyilvnval. Azaz a (T)e lehet, hogy hordozhat talaktst vgez egymssal kapcsolatban lev tpusok kztt, de nem hordozhatt a nem rokon tpusok kztt, esetleg egy mutattpusrl eltvoltja a const minstt. Ha nem tudjuk T s e pontos tpust, ezt nem tudjuk eldnteni.

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

175

6.2.8. Konstruktorok
Egy T tpus rtk ltrehozsa egy e rtkbl a T(e) fggvnyjellssel fejezhet ki:
void f(double d) { int i = int(d); complex z = complex(d); // ... }

// d csonkolsa // complex ltrehozsa d-bl

A T(e) szerkezetet nha fggvny stlus konverzinak nevezik. Sajnos, beptett T tpusokra T(e) egyenrtk (T)e-vel, ami azt vonja maga utn, hogy a T(e) hasznlata nem mindig biztonsgos. Aritmetikai tpusok esetben az rtkek csonkulhatnak, s mg egy hosszabb egsz tpusrl egy rvidebbre (pldul long-rl char-ra) val talakts is nem meghatrozott viselkedst eredmnyezhet. A jellst megprblom kizrlag ott hasznlni, ahol az rtk ltrehozsa pontosan meghatrozott, azaz a szkt aritmetikai talaktsoknl (C.6), az egszekrl felsorol tpusra val talaktsoknl (4.8), s a felhasznli tpusok objektumainak ltrehozsnl (2.5.2, 10.2.3). A mutat-konverzikat a T(e) jellst hasznlva nem fejezhetjk ki kzvetlenl. A char*(2) pldul formai hibnak szmt. Sajnos az a vdelem, amit a konstruktor jells nyjt az ilyen veszlyes talaktsok ellen, kikerlhet ha a mutattpusokra typedef neveket (4.9.7) hasznlunk. A T alaprtelmezett rtknek kifejezsre a T() konstruktor jells hasznlatos:
void f(double d) { int j = int(); complex z = complex(); // ... }

// alaprtelmezett int rtk // alaprtelmezett complex rtk

A beptett tpusok konstruktornak rtke a 0, amit a fordt az adott tpusra konvertl (4.9.5). Ezrt az int() egy msfajta mdja a 0 rsnak. A T felhasznli tpusra T()-t az alaprtelmezett konstruktor (10.4.2) hatrozza meg, ha ltezik ilyen. A konstruktor jells hasznlata beptett tpusokra sablonok rsakor klnsen fontos. Ekkor a programoz nem tudhatja, hogy a sablon (template) paramtere beptett vagy felhasznli tpusra vonatkozik-e majd (16.3.4, 17.4.1.2).

Forrs: http://www.doksi.hu

176

Alapok

6.3. Utastsok ttekints


me a C++ utastsok sszefoglalsa, nhny pldval:

Az utastsok formai szablyai utasts: deklarci { utasts_listanem ktelez } try { utasts_listanem ktelez } kezel_lista kifnem ktelez ; if (felttel) utasts if (felttel) utasts else utasts switch (felttel) utasts while (felttel) utasts do utasts while (kif); for (kezdrtk_meghatroz felttelnem ktelez ;kifnem ktelez ) utasts case konstans_kif : utasts default : utasts break ; continue ; return kifnem ktelez ; goto azonost; azonost : utasts utasts_lista: utasts utasts_listanem ktelez felttel: kif tpusazonost deklartor = kif kezel_lista: catch (kif_deklarci) { utasts_listanem ktelez } kezel_lista kezel_listanem ktelez

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

177

Jegyezzk meg, hogy a deklarci egy utasts, rtkad s eljrshv utastsok pedig nincsenek: az rtkadsok s a fggvnyhvsok kifejezsek. A kivtelek kezelsre vonatkoz utastsokat a try blokkokat a 8.3.1 pontban trgyaljuk.

6.3.1. Deklarcik mint utastsok


A deklarci utasts. Hacsak egy vltozt static-knt nem adunk meg, minden esetben kezdrtket fog kapni, amikor a vezrls thalad a deklarcijn (lsd mg 10.4.8). A deklarcikat azrt engedjk meg minden olyan helyen, ahol utastst hasznlhatunk (s mg pr tovbbi helyen, 6.3.2.1, 6.3.3.1), hogy lehetv tegyk a programoznak a kezdrtk nlkli vltozkbl szrmaz hibk cskkentst s a vltozk hatkrnek lehet legnagyobb szktst a kdban. Ritkn van ok j vltoz bevezetsre, mieltt lenne egy olyan rtk, amit a vltoznak tartalmaznia kell:
void f(vector<string>& v, int i, const char* p) { if (p==0) return; if (i<0 || v.size()<=i) error("rossz index"); string s = v[i]; if (s == p) { // ... } // ... }

A lehetsg, hogy a deklarcikat vgrehajthat kd utn is elhelyezhetjk, alapvet fontossg sok konstans esetben, illetve az olyan egyszeri rtkadsos programozsi stlusnl, ahol egy objektum rtke nem vltozik meg annak ltrehozsa s kezdeti rtkadsa utn. Felhasznli tpusoknl a vltoz meghatrozsnak elhalasztsa addig, amg egy megfelel kezdeti rtk rendelkezsre nem ll jobb teljestmnyhez is vezethet:
string s; /* ... */ s = "A legjobb a j ellensge.";

A fenti knnyen elfordulhat, hogy sokkal lassabb, mint a kvetkez:


string s = "Voltaire";

Kezdeti rtk nlkl ltalban akkor adunk meg egy vltozt, ha a vltoznak utastsra van szksge a kezdeti rtkadshoz. Ilyenek pldul a bemeneti vltozk s a tmbk.

Forrs: http://www.doksi.hu

178

Alapok

6.3.2. Kivlaszt utastsok


Egy rtket az if vagy a switch utastssal vizsglhatunk meg:
if (felttel) utasts if (felttel) utasts else utasts switch (felttel) utasts

Az albbi sszehasonlt opertorok a logikai (bool) tpus true rtket adjk vissza, ha az sszehasonlts igaz, s false-t, ha hamis:
== != < <= > >=

Az if utastsban az els (s egyetlen) utasts akkor hajtdik vgre, ha a kifejezs nem nulla. Ha nulla, a msodik utastsra ugrunk (ha megadtunk ilyet). Ebbl az kvetkezik, hogy brmilyen aritmetikai vagy mutat kifejezst lehet felttelknt hasznlni. Pldul ha x egy egsz, akkor
if (x) // ...

azt jelenti, hogy


if (x != 0) // ...

A p mutat esetben az albbi egy kzvetlen utasts, ami azt a vizsglatot fejezi ki, hogy p egy rvnyes objektumra mutat:
if (p) // ...

A kvetkez kzvetett mdon ugyanezt a krdst fogalmazza, gy, hogy sszehasonltja egy olyan rtkkel, amelyrl tudjuk, hogy nem mutat objektumra:
if (p != 0) // ...

Jegyezzk meg, hogy a 0 mutatt nem minden gp brzolja csupa nullval (5.1.1). Minden fordtprogram, amivel tallkoztam, ugyanazt a kdot ksztette mindkt vizsglatra.

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

179

A
&& || !

logikai opertorok leggyakrabban felttelekben hasznlatosak. Az && s a || mveletek nem rtkelik ki a msodik paramtert, csak ha szksg van r:
if (p && 1<p->count) // ...

A fenti utasts pldul elszr megvizsglja, hogy p nem nulla-e, s csak akkor nzi meg, hogy l<p->count teljesl-e, ha p nem nulla. Nhny if utastst knyelmesen feltteles kifejezsekre cserlhetnk. Pldul az
if (a <= b) max = b; else max = a;

jobban kifejezhet gy:


max = (a<=b) ? b : a;

A felttel krl lv zrjelek nem szksgesek, n azonban gy gondolom, a kd knnyebben olvashat lesz tlk. A switch utasts if utastsok sorozataknt is lerhat. Pldul a
switch (val) { case 1: f(); break; case 2: g(); break; default: h(); break; }

Forrs: http://www.doksi.hu

180

Alapok

gy is kifejezhet:
if (val == 1) f(); else if (val == 2) g(); else h();

A jelents ugyanaz, de az els (switch) vltozatot rszestjk elnyben, mert a mvelet termszete (egy rtket llandk halmazval hasonltunk ssze) gy vilgosabb. A switch utasts olvashatbb olyan pldknl, amelyek nem maguktl rtetdek, s jobb kdot is hozhatunk ltre vele. Vigyzzunk arra, hogy a switch case-t mindig fejezzk be valahogy, hacsak nem akarjuk a vgrehajtst a kvetkez case-nl folytatni. Vegyk a kvetkezt:
switch (val) { // vigyzat! case 1: cout << "1. eset\n"; case 2: cout << "2.eset\n"; default: cout << "Alaprtelmezs: nincs ilyen eset\n"; }

Ha val==1-gyel hvjuk meg, a kvetkezket rja ki:


1. eset 2. eset Alaprtelmezs: nincs ilyen eset

Ez az avatatlanokat nagy meglepetsknt rheti. J tlet, ha megjegyzsekkel ltjuk el azon (ritka) eseteket, amikor a case-ek kztti tovbblps szndkos, gy egy nem magyarzott tovbblpsrl felttelezhetjk, hogy programhiba. A case befejezsnek leggyakoribb mdja a break hasznlata, de a return is hasznos lehet (6.1.1).

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

181

6.3.2.1. Deklarcik felttelekben A vletlen hibs mkds elkerlsre ltalban j tlet a vltozkat a legkisebb lehetsges hatkrben bevezetni. Nevezetesen, rendszerint legjobb elhalasztani egy idelis vltoz bevezetst addig, amg kezdeti rtket nem tudunk adni neki. gy nem kerlhetnk olyan helyzetbe, hogy a vltozt mg azeltt hasznljuk, mieltt kezdeti rtkt belltottuk volna. Az emltett kt elv egyik legelegnsabb felhasznlsa, ha a vltozt egy felttelben adjuk meg. Vegyk a kvetkez pldt:
if (double d = prim(true)) { left /= d; break; }

Itt d deklarlt s kezdrtket is kap, amit a felttel rtkvel hasonltunk ssze. A d hatkre a deklarci pontjtl annak az utastsnak a vgig terjed, amit a felttel vezrel. Ha volna egy else g az if utastsban, a d hatkre mindkt gra kiterjedne. A msik hagyomnyos s kzenfekv megolds, ha a d-t a felttel eltt vezetjk be. gy viszont nagyobb lesz a d hasznlatnak hatkre; kiterjedhet a kezdeti rtkads el vagy a d szndkolt hasznos lettartama utn is:
double d; // ... d2 = d; // hopp! // ... if (d = prim(true)) { left /= d; break; } // ... d = 2.0; // d kt, egymstl fggetlen hasznlata

A vltozk felttelekben trtn megadsnak nemcsak logikai haszna van, tmrebb forrskdot is eredmnyez. A felttelben lv deklarcinak egyetlen vltozt vagy konstanst kell megadnia s feltltenie kezdrtkkel.

Forrs: http://www.doksi.hu

182

Alapok

6.3.3. Ciklusutastsok
A ciklusokat for, while vagy do utastssal fejezhetjk ki:
while ( felttel ) utasts do utasts while ( kifejezs ) ; for ( kezdrtk_meghatroz felttelnem ktelez ; kifejezsnem ktelez ) utasts

Ezen utastsok mindegyike ismtelten vgrehajt egy utastst (amit vezrelt (controlled) utastsnak vagy ciklusmagnak neveznk), amg a felttel hamiss nem vlik vagy a programoz ms mdon ki nem lp a ciklusbl. A for utasts szablyos ciklusok kifejezsre val. A ciklusvltozt, a ciklusfelttelt, s a ciklusvltozt mdost kifejezst egyetlen sorban rhatjuk le, ami nagyon megnvelheti az olvashatsgot s ezzel cskkentheti a hibk gyakorisgt. Ha nem szksges kezdeti rtkads, a kezdrtk_meghatroz (inicializl) utasts res is lehet. Ha a felttelt elhagyjuk, a for utasts rkk a ciklusban marad, hacsak a felhasznl kifejezetten kilpsre nem knyszerti egy break, return, goto, vagy throw utastssal, vagy valami kevsb egyszer mdon, pldul az exit() (9.4.1.1) meghvsval. Ha a kifejezst elhagyjuk, a ciklusmagban kell mdostanunk egy ciklusvltozt. Ha a ciklus nem az egyszer bevezetnk egy ciklusvltozt, megvizsgljuk a felttelt, mdostjuk a ciklusvltozt fajtbl val, ltalban jobb, ha while utastssal fejezzk ki, de a for is segthet olyan ciklusok rsnl, melyeknek nincs meghatrozott lellsi felttele:
for(;;) { // ... } // "rkk" (vgtelen ciklus)

A while utasts egyszeren vgrehajtja a ciklusmagot, amg felttele hamiss nem vlik. Akkor hajlok arra, hogy a while-t rszestsem elnyben a for-ral szemben, amikor nincs magtl rtetd ciklusvltoz vagy amikor a ciklusvltoz mdostsa termszetes mdon a ciklusmag kzepn trtnik. A bemeneti ciklus egy olyan ciklusra plda, amelyben nincs magtl rtetd ciklusvltoz:
while(cin>>ch) // ...

Tapasztalatom szerint a do utasts knnyen hibk s tvedsek forrsa lehet. Ennek az az oka, hogy a ciklusmag mindig vgrehajtdik egyszer, mieltt a felttel kirtkeldik. Ahhoz azonban, hogy a ciklusmag megfelelen mkdjn, valamilyen felttelnek mr az els alkalommal is teljeslnie kell. A vrtnl sokkal gyakrabban vettem szre azt, hogy egy felt-

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

183

tel nem gy teljeslt, ahogy az elvrhat lett volna; vagy amikor a programot elszr megrtk s teszteltk, vagy ksbb, amikor a kdot mdostottk. Ezenkvl jobban szeretem a felttelt ell, ahol jl lthatom. Kvetkezskppen n magam prblom elkerlni a do utastsokat.

6.3.3.1. Deklarcik a for utastsban Vltozkat a for utasts kezdrtk-ad rszben adhatunk meg. Ha ez deklarci, akkor az ltala bevezetett vltoz (vagy vltozk) hatkre a for utasts vgig terjed:
void f(int v[ ], int max) { for (int i = 0; i<max; i++) v[i] = i*i; }

Ha az index vgs rtkt tudni kell a for ciklusbl val kilps utn, a ciklusvltozt a cikluson kvl kell megadni (pl. 6.3.4.).

6.3.4. Goto
A C++-ban megtallhat a hrhedt goto :
goto azonost ; azonost : utasts

A goto az ltalnos magasszint programozsban kevs dologra hasznlhat, de nagyon hasznos lehet, amikor a C++ kdot program s nem kzvetlenl egy szemly kszti; hasznlhatjuk pldul olyan elemzben, melyet egy kdkszt program (kdgenertor) hozott ltre valamilyen nyelvtan alapjn. A goto akkor is hasznos lehet, ha a hatkonysg alapvet kvetelmny, pldul valamilyen vals idej alkalmazs bels ciklusban. A goto kevs rtelmes hasznlatnak egyike a mindennapi kdban az, hogy kilpnk egy begyazott ciklusbl vagy switch utastsbl (a break csak a legbels ciklusbl vagy switch utastsbl lp ki):
void f() { int i; int j;

Forrs: http://www.doksi.hu

184

Alapok

for (i = 0; i<n; i++) for (j = 0; j<m; j++) if (nm[i][j] == a) goto found; // nem tallhat // ... found: // nm[i][j] == a }

A ciklus vgre ugr continue utasts mkdsvel a 6.1.5-ben foglalkoztunk.

6.4. Megjegyzsek s behzs


A program olvasst s megrtst sokkal kellemesebb teheti, ha okosan hasznljuk a megjegyzseket s a behzst. Szmos behzsi stlus hasznlatos s nem ltok alapvet okot arra, hogy egyiket a msikkal szemben elnyben rszestsk (br a legtbb programozhoz hasonlan nekem is van vlasztott stlusom a knyv nyilvn tkrzi is azt). Ugyanez vonatkozik a megjegyzsek stlusra is. A megjegyzseket szmos mdon lehet rosszul hasznlni, ami gy nagymrtkben rontja a program olvashatsgt. A fordtprogram nem rti a megjegyzsek tartalmt, ezrt nincs md arra, hogy biztostsa azt, hogy egy megjegyzs [1] rtelmes, [2] a programmal sszhangban ll s [3] idszer legyen. Szmos program olyan megjegyzseket tartalmaz, melyek rthetetlenek, flrerthetek, vagy egyszeren hibsak. A rossz megjegyzsek rosszabbak, mint ha egyltaln nem hasznlnnk megjegyzst. Ha valamit lerhatunk magval a programnyelvvel, akkor tegyk azt, ne megjegyzsben emltsk meg. Ez az szrevtel az ilyenfajta megjegyzsekre vonatkozik:
// a "v" vltznak kezdrtket kell adni // a "v" vltozt csak az "f()" fggvny hasznlhatja // az "init()" fggvnyt minden ms fggvny eltt meg kell hvni ebben a fjlban

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

185

// a "cleanup()" fggvnyt meg kell hvni a program vgn // a "weird()" fggvnyt ne hasznljuk // az "f()" fggvnynek kt paramtert kell adni

A C++ megfelel hasznlata az ilyen megjegyzseket ltalban szksgtelenn teszi. A fentieket pldul kivlthatjuk, ha alkalmazzuk az sszeszerkesztsi (9.2) vagy az osztlyokra vonatkoz lthatsgi, kezdrtk-adsi s felszmolsi szablyokat (10.4.1). Mihelyt valamit vilgosan lertunk a nyelvvel, msodszor mr nem kell megemltennk egy megjegyzsben:
a = b+c; // a-bl b+c lesz count++; // nveljk a szmllt

Az ilyen megjegyzsek mg az egyszeren feleslegeseknl is rosszabbak, mert nvelik az elolvasand szveg hosszt, gyakran sszezavarjk a program szerkezett, s lehet, hogy hibsak. Meg kell azonban jegyeznnk, hogy az ilyen megjegyzsek szleskren hasznlatosak tantsi clokra az olyan programozsi nyelvekrl szl knyvekben, mint amilyen ez is. Ez az egyik, amiben egy knyvben lv program klnbzik egy igazi programtl. n a kvetkezket szeretem: 1. Minden forrsfjlban van megjegyzs, amely lerja, mi a kzs a fjlban lev deklarcikban, utal a segdanyagokra, ltalnos tleteket ad a kd mdostsval kapcsolatban stb. 2. Minden osztlyhoz, sablonhoz s nvtrhez tartozik megjegyzs. 3. Minden nem magtl rtetd fggvnyhez van olyan megjegyzs, amely lerja a fggvny cljt, a felhasznlt algoritmust (ha az nem nyilvnval), s esetleg azt, hogy mit felttelez krnyezetrl. 4. Minden globlis s nvtr-vltozhoz, illetve konstanshoz van megjegyzs. 5. Van nhny megjegyzs ott, ahol a kd nem nyilvnval s/vagy ms rendszerre nem tltethet. 6. A fentieken kvl kevs megjegyzs van.

Forrs: http://www.doksi.hu

186

Alapok

Pldul:
// tbl.c: Implementation of the symbol table. /* */ Gaussian elimination with partial pivoting. See Ralston: "A first course ..." pg 411.

// swap() assumes the stack layout of an SGI R6000. /******************************* Copyright (c) 1997 AT&T, Inc. All rights reserved *******************************/

A jl megvlasztott s jl megrt megjegyzsek alapvet rszt kpezik a j programnak. J megjegyzseket rni legalbb olyan nehz, mint megrni magt a programot. Olyan mvszet, melyet rdemes mvelni. Jegyezzk meg azt is, hogy ha kizrlag a // megjegyzseket hasznljuk egy fggvnyben, akkor ennek a fggvnynek brmely rszt megjegyzsbe tehetjk a /* */ jellssel (ez fordtva is igaz).

6.5. Tancsok
[1] Rszestsk elnyben a standard knyvtrat a tbbi knyvtrral s a kzzel rt kddal szemben. 6.1.8. [2] Kerljk a bonyolult kifejezseket. 6.2.3. [3] Ha ktsgeink vannak az opertorok precedencijval kapcsolatban, zrjelezznk. 6.2.3. [4] Kerljk a tpusknyszertst (cast). 6.2.7. [5] Ha explicit tpuskonverzi szksges, rszestsk elnyben a jobban definilt konverzis opertorokat a C stlus talaktssal szemben. 6.2.7. [6] Kizrlag jl meghatrozott szerkezeteknl hasznljuk a T(e) jellst. 6.2.8. [7] Kerljk az olyan kifejezseket, melyek kirtkelsi sorrendje nem meghatro-

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

187

zott. 6.2.2. [8] Kerljk a goto-t. 6.3.4. [9] Kerljk a do utastst. 6.3.3. [10] Ne adjunk meg vltozt addig, amg nincs rtk, amivel feltlthetnnk. 6.3.1, 6.3.2.1, 6.3.3.1. [11] A megjegyzseket frisstsk rendszeresen. 6.4. [12] Tartsunk fenn kvetkezetes stlust. 6.4. [13] A globlis operator new() helyettestsre adjunk meg inkbb egy operator new() tagot (15.6). 6.2.6.2. [14] Bemenet beolvassakor mindig vegyk szmtsba a rosszul megadott bemenetet is. 6.1.3.

6.6. Gyakorlatok
1. (*1) rjuk meg a kvetkez for utastssal egyenrtk while utastst:
for (i=0; i<max_length; i++) if (input_line[i] == '?') quest_count++;

Ezt rjuk t gy, hogy ciklusvltozknt mutatt hasznlunk, azaz gy, hogy a vizsglat *p=='?' alak legyen. 2. (*1) Zrjelezzk teljesen a kvetkez kifejezseket:
a = b + c * d << 2 & 8 a & 077 != 3 a == b || a == c && c < 5 c = x != 0 0 <= i < 7 f(1,2)+3 a = - 1 + + b -- - 5 a = b == c ++ a=b=c=0 a[4][2] *= * b ? c : * d * 2 a-b,c=d

3. (*2) Olvassuk be reshellyel elvlasztott (nv- s rtk-) prok sorozatt, ahol a nv egyetlen reshellyel elvlasztott sz, az rtk pedig egy egsz vagy lebegpontos rtk. Szmtsuk ki s rjuk ki minden nvre az rtkek sszegt s szmtani kzept, valamint az sszes nvre vonatkoz sszeget s szmtani kzepet. Tipp: 6.1.8. 4. (*1) rjuk meg a bitenknti logikai opertorok (6.2.4) rtktblzatt a 0 s 1

Forrs: http://www.doksi.hu

188

Alapok

operandusok sszes lehetsges prostsra. 5. (*1,5) Talljunk 5 klnbz C++ szerkezetet, melynek jelentse nem meghatrozott (C.2). (*1,5.) Talljunk 5 klnbz C++ szerkezetet, melynek jelentse a nyelvi megvalststl fgg (C.2). 6. (*1) Adjunk 10 klnbz pldt nem hordozhat C++ kdra. 7. (*2) rjunk 5 kifejezst, melyek kirtkelsi sorrendje nem meghatrozott. Hajtsuk ket vgre s nzzk meg, mit csinl velk egy de lehetleg tbb C++vltozat. 8. (*1,5) Mi trtnik a rendszernkben, ha nullval osztunk? Mi trtnik tlcsorduls s alulcsorduls esetn? 9. (*1) Zrjelezzk teljesen a kvetkez kifejezseket:
*p++ *--p ++a-(int*)p->m *p.m *a[i]

10. (*2) rjuk meg a kvetkez fggvnyeket: strlen(), ami egy C stlus karakterlnc hosszt adja vissza, strcpy(), ami egy karakterlncot msol egy msikba, s strcmp(), ami kt karakterlncot hasonlt ssze. Gondoljuk meg, mi legyen a paramterek s a visszatrsi rtk tpusa. Ezutn hasonltsuk ssze a fggvnyeket a standard knyvtrban lv vltozatokkal, ahogy azok a <cstring>-ben (<string.h>-ban) szerepelnek s ahogy a 20.4.1 pontban lertuk azokat. 11. (*1) Nzzk meg, hogyan reagl a fordtprogramunk ezekre a hibkra:
void f(int a, int b) { if (a = 3) // ... if (a&077 == 0) // ... a := b+1; }

Ksztsnk tbb egyszer hibt s nzzk meg, hogyan reagl a fordtprogram. 12. (*2) Mdostsuk gy a 6.6[3] programot, hogy a kzps rtket (medin) is kiszmtsa. 13. (*2) rjuk meg a cat() fggvnyt, amelynek kt C stlus karakterlnc paramtere van s egy olyan karakterlncot ad vissza, amely a paramterek sszefzsbl ll el. Az eredmnyeknek foglaljunk helyet a new-val. 14. (*2) rjuk meg a rev() fggvnyt, amelynek egy karakterlnc paramtere van s

Forrs: http://www.doksi.hu

6. Kifejezsek s utastsok

189

megfordtja a benne lv karaktereket. Azaz a rev(p) lefutsa utn p utols karaktere az els lesz s gy tovbb. 15. (*1,5) Mit csinl a kvetkez plda s mirt rna valaki ilyesmit?
void send(int* to, int* from, int count) // Duff programja. A megjegyzseket szndkosan trltem. { int n = (count+7)/8; switch (count%8) { case 0: do { *to++ = *from++; case 7: *to++ = *from++; case 6: *to++ = *from++; case 5: *to++ = *from++; case 4: *to++ = *from++; case 3: *to++ = *from++; case 2: *to++ = *from++; case 1: *to++ = *from++; } while (--n>0); } }

16. (*2) rjuk meg az atoi(const char*) fggvnyt, amely egy szmokat tartalmaz karakterlncot kap s visszaadja a megfelel egszet. Pldul atoi("123") 123 lesz. Mdostsuk az atoi()-t gy, hogy kezelje a C++ oktlis s hexadecimlis jellst is, az egyszer, tzes szmrendszerbeli szmokkal egytt. Mdostsuk a fggvnyt gy is, hogy kezelje a C++ karakterkonstans jellst is. 17. (*2) rjunk egy olyan itoa(int i, char b[ ]) fggvnyt, amely ltrehozza b-ben i karakterlnc brzolst s visszaadja b-t. 18. (*2) Gpeljk be teljes egszben a szmolgp pldt s hozzuk mkdsbe. Ne takartsunk meg idt azzal, hogy mr begpelt szvegrszeket hasznlunk. A legtbbet a kis buta hibk kijavtsbl fogunk tanulni. 19. (*2) Mdostsuk a szmolgpet, hogy kirja a hibk sornak szmt is. 20. (*3) Tegyk lehetv, hogy a felhasznl fggvnyeket adhasson meg a szmolgphez. Tipp: adjunk meg egy fggvnyt mveletek sorozataknt, gy, mintha a felhasznl gpelte volna be azokat. Az ilyen sorozatokat karakterlncknt vagy szimblumok (tokenek) listjaknt trolhatjuk. Ezutn olvassuk be s hajtsuk vgre ezeket a mveleteket, amikor a fggvny meghvdik. Ha azt akarjuk, hogy egy felhasznli fggvnynek paramterei legyenek, akkor arra kln jellst kell kitallnunk. 21. (*1,5) Alaktsuk t a szmolgpet, hogy a symbol szerkezetet hasznlja s ne a statikus number_value s string_value vltozkat. 22. (*2,5) rjunk olyan programot, amely kiveszi a megjegyzseket a C++ progra-

Forrs: http://www.doksi.hu

190

Alapok

mokbl. Azaz, olvassunk a cin-rl, tvoltsuk el mind a //, mind a /* */ megjegyzseket, majd rjuk ki az eredmnyt a cout-ra. Ne trdjnk azzal, hogy a kimenet szp legyen (az egy msik, sokkal nehezebb feladat lenne). Ne trdjnk a hibs programokkal. vakodjunk a //, /* s */ hasznlattl a megjegyzsekben, karakterlncokban s karakterkonstansokban. 23. (*2) Nzznk meg nhny programot, hogy elkpzelsnk lehessen a mostansg hasznlatos stlusok (behzsok, elnevezsek s megjegyzsek) vltozatossgrl.

Forrs: http://www.doksi.hu

7
Fggvnyek
Ismtelni emberi dolog. Rekurzit rni isteni. (L. Peter Deutsch) Fggvnydeklarcik s -defincik Paramtertads Visszatrsi rtkek Fggvnytlterhels A tbbrtelmsg feloldsa Alaprtelmezett paramterek stdargs Fggvnyekre hivatkoz mutatk Makrk Tancsok Gyakorlatok

7.1. Fggvnydeklarcik
A C++-ban ltalban gy vgznk el valamit, hogy meghvunk r egy fggvnyt, s a fggvny definicijval rjuk le, hogyan kell azt elvgezni. A fggvnyt azonban nem hvhatjuk meg gy, hogy elzetesen nem deklarltuk. A fggvny deklarcija megadja a fggvny nevt, visszatrsi rtknek tpust (ha van ilyen), s azon paramterek szmt s tpust, amelyeket t kell adni a fggvny meghvsakor:
Elem* next_elem(); char* strcpy(char* to, const char* from); void exit(int);

Forrs: http://www.doksi.hu

192

Alapok

A paramtertads ugyanazt a szerepet tlti be, mint a kezdeti rtkads. A fordtprogram ellenrzi a paramterek tpust s automatikus tpuskonverzit vgez, ha szksges:
double sqrt(double); double sr2 = sqrt(2); double sq3 = sqrt("three"); // sqrt() meghvsa double(2) paramterrel // hiba: sqrt() double tpus paramtert ignyel

Az ilyen ellenrzs s konverzi jelentsgt nem szabad albecslni. A fggvnydeklarci paramterneveket is tartalmazhat, melyek segtik a program olvasjt. A fordtprogram ezeket egyszeren nem veszi figyelembe. Amint 4.7-ben emltettk, a void visszatrsi tpus azt jelenti, hogy a fggvny nem ad vissza rtket.

7.1.1. Fggvnydefincik
Minden fggvnyt, amit meghvunk a programban, valahol (de csak egyszer) meg kell hatroznunk. A fggvnydefinci (fggvny-meghatrozs) olyan deklarci, amelyben megadjuk a fggvny trzst:
extern void swap(int*, int*); void swap(int* p, int* q) { int t = *p; *p = *q; *q = t; } // deklarci // definci

A fggvnyek definciinak s deklarciinak ugyanazt a tpust kell meghatrozniuk. A paramterek nevei azonban nem rszei a tpusnak, gy nem kell azonosaknak lennik. Nem ritka az olyan fggvnydefinci, amely nem hasznlja fel az sszes paramtert:
void search(table* t, const char* key, const char*) { // a harmadik paramter nem hasznlatos }

Forrs: http://www.doksi.hu

7. Fggvnyek

193

Amint ltjuk, azt a tnyt, hogy egy paramtert nem hasznlunk fel, gy jellhetjk, hogy nem nevezzk meg a paramtert. A nvtelen paramterek jellemzen a program egyszerstse folytn vagy annak ksbbi bvthetsgt biztostand kerlnek a kdba. Mindkt esetben azzal, hogy br nem hasznljuk fel, de a helykn hagyjuk a paramtereket, biztostjuk, hogy a fggvnyt meghvkat nem rintik a mdostsok. A fggvnyeket a fordt ltal a hvs sorban kifejtendknt (inline-knt) is megadhatjuk:
inline int fac(int n) { return (n<2) ? 1 : n*fac(n-1); }

Az inline kulcssz javaslat a fordtprogram szmra, hogy a fac() meghvsnl prblja meg annak kdjt a hvs sorban ltrehozni, ahelyett, hogy elbb ltrehozn azt, majd a szoksos fggvnyhv eljrs szerint hvn meg. Egy okos fordtprogram a fac(6) meghvsakor ltre tudja hozni a720 konstanst. Az egymst klcsnsen meghv (klcsnsen rekurzv) helyben kifejtett fggvnyek, illetve a bemenettl fggen magukat jrahv vagy nem jrahv helyben kifejtett fggvnyek lehetsge miatt nem biztos, hogy egy inline fggvny minden hvsa a hvs sorban jn ltre. A fordtprogramok intelligencijnak mrtke nem rhat el, gy elfordulhat, hogy az egyik 720-at, a msik 6*fac(5)-t, a harmadik pedig a fac(6) nem helyben kifejtett hvst hozza ltre. Ha nem rendelkeznk kivtelesen intelligens fordt- s szerkeszt-programmal, a hvs sorban trtn ltrehozst akkor biztosthatjuk, ha a fggvny kifejtse s nem csak deklarcija is a hatkrben szerepel (9.2). Az inline kulcssz nem befolysolja a fggvny rtelmezst. Nevezetesen az ilyen fggvnyeknek s static vltoziknak (7.1.2.) is ugyangy egyedi cmk van.

7.1.2. Statikus vltozk


A loklis (helyi) vltozk akkor kapnak kezdrtket, amikor a vgrehajts elr a definicijukhoz. Alaprtelmezs szerint ez a fggvny minden meghvsakor megtrtnik, az egyes fggvnyhvsoknak pedig sajt msolatuk van a vltozrl. Ha egy loklis vltozt static-knt vezetnk be, akkor azt a fggvny minden meghvsakor egyetlen, lland cm objektum jelli majd. A vltoz csak egyszer kap rtket, amikor a vgrehajts elri annak els defincijt:

Forrs: http://www.doksi.hu

194

Alapok

void f(int a) { while (a--) { static int n = 0; // egyszer kap kezdrtket int x = 0; // 'a' alkalommal kap kezdrtket (az f() minden meghvsakor) } cout << "n == " << n++ << ", x == " << x++ << '\n';

int main() { f(3); }

A fenti a kvetkezket rja ki:


n == 0, x == 0 n == 1, x == 0 n == 2, x == 0

A statikus vltozk anlkl biztostanak emlkezetet a fggvnynek, hogy globlis vltozt vezetnnek be, amelyet ms fggvnyek is elrhetnek s mdostssal hasznlhatatlann tehetnek (lsd mg 10.2.4).

7.2. Paramtertads
Amikor egy fggvny meghvdik, a fordtprogram a formlis paramterek szmra trterletet foglal le, az egyes formlis paramterek pedig a megfelel valdi (aktulis) paramter-rtkkel tltdnek fel. A paramtertads szerepe azonos a kezdeti rtktadsval. A fordtprogram ellenrzi, hogy az aktulis paramterek tpusa megegyezik-e a formlis paramterek tpusval, s vgrehajt minden szabvnyos s felhasznli tpuskonverzit. A tmbk tadsra egyedi szablyok vonatkoznak (7.2.1), de van lehetsg nem ellenrztt (7.6) s alaprtelmezett paramterek (7.5) tadsra is. Vegyk a kvetkez pldt:
void f(int val, int& ref) { val++; ref++; }

Forrs: http://www.doksi.hu

7. Fggvnyek

195

Amikor f() meghvdik, val++ az els aktulis paramter helyi msolatt nveli, ref++ pedig a msodik aktulis paramtert. Az albbi
void g() { int i = 1; int j = 1; f(i,j); }

j-t fogja nvelni, de i-t nem. Az els paramter (i) rtk szerint addik t, a msodik (j) referencia szerint. Amint 5.5-ben emltettk, azok a fggvnyek, melyek mdostjk a referencia szerint tadott paramtereiket, nehezen olvashatv teszik a programot s ltalban kerlendk (ellenben lsd 21.3.2-et). szreveheten hatkonyabb lehet, ha egy nagy objektumot referencia, nem pedig rtk szerint adunk t. Ebben az esetben a paramtert megadhatjuk const-knt, hogy jelezzk, csak hatkonysgi okokbl hasznlunk referencit s nem szeretnnk lehetv tenni, hogy a hvott fggvny mdosthassa az objektum rtkt:
void f(const Large& arg) { // "arg" rtke nem mdosthat, csak explicit tpuskonverzival }

A referencia-paramter deklarcijban a const elhagysa azt fejezi ki, hogy szndkunkban ll a vltozt mdostani:
void g(Large& arg); // ttelezzk fel, hogy g() mdostja arg-ot

Hasonlan, a const-knt megadott mutat paramter azt jelzi az olvasnak, hogy a paramter ltal mutatott objektum rtkt a fggvny nem vltoztatja meg:
int strlen(const char*); char* strcpy(char* to, const char* from); int strcmp(const char*, const char*); // karakterek szma egy C stlus // karakterlncban // C stlus karakterlnc msolsa // C stlus karakterlncok sszehasonltsa

A const paramterek hasznlatnak fontossga a program mretvel egytt n. Jegyezzk meg, hogy a paramtertads szerepe klnbzik a (nem kezdeti) rtkadstl. Ez a const paramterek, a referencia-paramterek, s nhny felhasznli tpus paramter esetben lnyeges (10.4.4.1).

Forrs: http://www.doksi.hu

196

Alapok

Literlt, llandt s olyan paramtert, amely talaktst ignyel, t lehet adni const& paramterknt, de nem const&-knt nem. Mivel a const T& paramterek konverzija megengedett, biztostott, hogy egy ilyen paramternek pontosan ugyanazokat az rtkeket lehet adni, mint egy T tpus rtknek, azltal, hogy az rtket ideiglenes vltozban adjuk t (amennyiben ez szksges):
float fsqrt(const float&); void g(double d) { float r = fsqrt(2.0f); r = fsqrt(r); r = fsqrt(d); } // Fortran stlus sqrt referencia-paramterrel

// a 2.0f-et tartalmaz ideiglenes vltozra hivatkoz // referencia tadsa // r-re hivatkoz referencia tadsa // a float(d)-t tartalmaz ideiglenes vltozra hivatkoz referencia // tadsa

Mivel a nem const referencia tpus paramterek konverzija nem megengedett, az ideiglenes vltozk bevezetsbl add buta hibk elkerlhetk:
void update(float& i); void g(double d, float r) { update(2.0f); // hiba: konstans paramter update(r); // r-re hivatkoz referencia tadsa update(d); // hiba: tpuskonverzi szksges }

Ha ezek a hvsok szablyosak lennnek, az update() csendben mdostotta volna azokat az ideiglenes vltozkat, amelyek azonnal trldtek. Ez rendszerint kellemetlen meglepetsknt rn a programozt.

7.2.1. Tmb paramterek


Ha fggvnyparamterknt tmbt hasznlunk, a tmb els elemre hivatkoz mutat addik t:
int strlen(const char*); void f() { char v[ ] = "egy tmb"; int i = strlen(v); int j = strlen("Nicholas"); }

Forrs: http://www.doksi.hu

7. Fggvnyek

197

Azaz egy T[ ] tpus paramter T* tpusv lesz alaktva, ha paramterknt addik t. Ebbl az kvetkezik, hogy ha egy paramterknt alkalmazott tmb egy elemhez rtket rendelnk, a tmb paramter is mdosul, vagyis a tmbk abban klnbznek a tbbi tpustl, hogy nem rtk szerint addnak t (ez nem is lehetsges). A tmb mrete nem hozzfrhet a hvott fggvny szmra. Ez bosszant lehet, de tbb md van r, hogy a problmt megkerljk. A C stlus karakterlncok nulla vgzdsek, gy mretk knnyen kiszmthat. Ms tmbknl a mretet egy tovbbi paramterrel adhatjuk meg:
void compute1(int* vec_ptr, int vec_size); struct Vec { int* ptr; int size; }; void compute2(const Vec& v); // msik mdszer // egyik mdszer

Vlaszthatjuk azt is, hogy tmbk helyett olyan tpusokat hasznlunk, mint a vector (3.7.1, 16.3). A tbbdimenzis tmbk nmileg bonyolultabbak (lsd C.7), de helyettk gyakran hasznlhatunk mutatkbl ll tmbket, melyek nem ignyelnek klnleges bnsmdot:
char* day[ ] = { "htf", "kedd", "szerda", "cstrtk", "pntek", "szombat", "vasrnap" };

A vector s a hozz hasonl tpusok a beptett, alacsonyszint tmbk s mutatk helyett is hasznlhatk.

7.3. Visszatrsi rtk


A main() kivtelvel(3.2) minden nem void-knt megadott fggvnynek rtket kell visszaadnia. Megfordtva, a void fggvnyek nem adhatnak vissza rtket:
int f1() { } void f2() { } // hiba: nincs visszatrsi rtk // rendben

Forrs: http://www.doksi.hu

198

Alapok

int f3() { return 1; } void f4() { return 1; } int f5() { return; } void f6() { return; }

// rendben // hiba: visszatrsi rtk void fggvnyben // hiba: visszatrsi rtk hinyzik // rendben

A visszatrsi rtket a return utasts hatrozza meg:


int fac(int n) { return (n>1) ? n*fac(n-1) : 1; }

Az nmagukat meghv fggvnyeket rekurzv (jrahv) fggvnyeknek nevezzk. Egy fggvnyben tbb return utasts is lehet:
int fac2(int n) { if (n > 1) return n*fac2(n-1); return 1; }

A paramtertadshoz hasonlan a fggvnyrtk visszaadsnak szerepe is azonos a kezdeti rtkadsval. A return utastst gy tekintjk, hogy az egy visszatrsi tpus, nv nlkli vltoznak ad kezdrtket. A fordtprogram sszehasonltja a return kifejezs tpust a visszatrsi tpussal s minden szabvnyos s felhasznli tpustalaktst vgrehajt:
double f() { return 1; } // 1 automatikusan double(1)-gy alakul

Minden egyes alkalommal, amikor egy fggvny meghvdik, paramtereinek s loklis (automatikus) vltozinak egy j msolata jn ltre. A tr a fggvny visszatrse utn ismt felhasznlsra kerl, ezrt loklis vltozra hivatkoz mutatt soha nem szabad visszaadni, mert a mutatott hely tartalma kiszmthatatlan mdon megvltozhat:
int* fp() { int local = 1; /* ... */ return &local; } // rossz

Ez a hiba kevsb gyakori, mint referencit hasznl megfelelje:


int& fr() { int local = 1; /* ... */ return local; } // rossz

Szerencsre a fordtprogram ltalban figyelmeztet, hogy loklis vltozra vonatkoz hivatkozst adtunk vissza.

Forrs: http://www.doksi.hu

7. Fggvnyek

199

A void fggvnyek nem adhatnak vissza rtket, de meghvsuk nem is eredmnyez ilyet, kvetkezskppen egy void fggvny return utastsban szerepl kifejezsknt hasznlhatunk void fggvnyt:
void g(int* p); void h(int* p) { /* ... */ return g(p); } // rendben: res visszatrsi rtk

Ez a fajta visszatrs olyan sablon (template) fggvnyek rsnl fontos, ahol a visszatrsi tpus egy sablonparamter (lsd 18.4.4.2).

7.4. Tlterhelt fggvnynevek


A legtbb esetben j tlet klnbz fggvnyeknek klnbz neveket adni, de amikor egyes fggvnyek lnyegben ugyanazt a mveletet vgzik klnbz tpus objektumokon, knyelmesebb lehet ugyangy elnevezni azokat. Azt, hogy klnbz tpusokra vonatkoz mveletekre ugyanazt a nevet hasznljuk, tlterhelsnek (overloading) nevezzk. Ez a mdszer a C++ alapmveleteinl is hasznlatos. Azaz, csak egyetlen nv van az sszeadsra (+), az mgis hasznlhat egsz, lebegpontos, s mutat tpus rtkek sszeadsra is. A gondolatot a programoz ltal ksztett fggvnyekre is kiterjeszthetjk:
void print(int); void print(const char*); // egsz kirsa // C stlus karakterlnc kirsa

Ami a fordtprogramot illeti, az egyetlen, ami kzs az azonos nev fggvnyekben, a nv. A fggvnyek felteheten hasonlak valamilyen rtelemben, de a nyelv nem korltozza s nem is segti a programozt. Ezrt a tlterhelt fggvnynevek elsdlegesen jellsbeli knyelmet adnak. Ez a knyelem az olyan hagyomnyos nev fggvnyeknl igazn lnyeges, mint az sqrt, print, s open. Amikor a nv jelentse fontos, ez a knyelem alapvetv vlik. Ez trtnik pldul a +, * s << opertorok, a konstruktorok (11.7), valamint az ltalnostott (generikus) programozs (2.7.2, 18. fejezet) esetben. Amikor egy f fggvny meghvdik, a fordtprogramnak ki kell tallnia, melyik f nev fggvnyt hvja meg. Ezt gy teszi, hogy minden egyes f nev fggvny formlis paramtereinek tpust sszehasontja az aktulis paramterek tpusval, majd azt a fggvnyt hvja meg, amelynek paramterei a legjobban illeszkednek, s fordtsi idej hibt ad, ha nincs jl illeszked fggvny:

Forrs: http://www.doksi.hu

200

Alapok

void print(double); void print(long); void f() { print(1L); print(1.0); print(1); }

// print(long) // print(double) // hiba, tbbrtelm: print(long(1)) vagy print(double(1))?

A fordtprogram a tlterhelt fggvnyek halmazbl gy vlasztja ki a megfelel vltozatot, hogy megkeresi azt a fggvnyt, amelyiknl a hvs paramter-kifejezsnek tpusa a legjobban illeszkedik a fggvny formlis paramtereire. Ahhoz, hogy mindez elvrsainknak (kzelten) megfelel mdon trtnjen, az albbiakat kell megksrelni (ebben a sorrendben): 1. Pontos illeszkeds: nincs szksg konverzira vagy csak egyszer konverzikat kell vgezni (pldul tmb nevt mutatra, fggvny nevt fggvnyre hivatkoz mutatra, vagy T-t const T-re). 2. Kiterjesztst hasznl illeszkeds: csak egsz rtkre kiterjeszts (integral promotion) szksges (bool-rl int-re, char-rl int-re, short-rl int-re, illetve ezek unsigned megfelelirl int-re C.6.1), valamint float-rl double-ra. 3. Szabvnyos konverzikat hasznl illeszkeds: int-rl double-ra, double-rl intre, Derived*-rl Base*-ra (12.2), T*-rl void*-ra (5.6), vagy int-rl unsigned int-re (C.6). 4. Felhasznli konverzikat hasznl illeszkeds (11.4). 5. Fggvnydeklarciban hrom pontot () hasznl illeszkeds (7.6). Ha azon a szinten, ahol elszr tallunk megfelel illeszkedst, kt illeszkedst is tallunk, a hvst a fordt tbbrtelmknt elutastja. A tlterhelst felold szablyok elssorban azrt ennyire alaposan kidolgozottak, mert figyelembe kellett venni a C s a C++ beptett numerikus tpusokra vonatkoz bonyolult szablyait (C.6). Vegyk a kvetkez pldt:
void print(int); void print(const char*); void print(double); void print(long); void print(char); void h(char c, int i, short s, float f) { print(c); // pontos illeszkeds: print(char) meghvsa print(i); // pontos illeszkeds: print(int) meghvsa

Forrs: http://www.doksi.hu

7. Fggvnyek

201

print(s); print(f); print('a'); print(49); print(0); print("a");

// kiterjeszts egssz: print(int) meghvsa // float kiterjesztse double-l: print(double) // pontos illeszkeds: print(char) meghvsa // pontos illeszkeds: print(int) meghvsa // pontos illeszkeds: print(int) meghvsa // pontos illeszkeds: print(const char*) meghvsa

A print(0) hvs a print(int)-et hvja meg, mert 0 egy int. A print('a') hvs a print(char)-t, mivel 'a' egy char (4.3.1). Az talaktsok (konverzik) s a kiterjesztsek kztt azrt tesznk klnbsget, mert elnyben akarjuk rszesteni a biztonsgos kiterjesztseket (pldul char-rl int-re) az olyan, nem biztonsgos mveletekkel szemben, mint az int-rl char-ra trtn talakts. A tlterhels feloldsa fggetlen a szba jhet fggvnyek deklarcis sorrendjtl. A tlterhels viszonylag bonyolult szablyrendszeren alapul, gy a programoz nha meglepdhet azon, melyik fggvny hvdik meg. Ez azonban mg mindig a kisebbik rossz. Vegyk figyelembe a tlterhels alternatvjt: gyakran hasonl mveleteket kell vgrehajtanunk tbbfle tpus objektumon. Tlterhels nlkl tbb fggvnyt kellene megadnunk, klnbz nevekkel:
void print_int(int); void print_char(char); void print_string(const char*);

// C stlus karakterlnc

void g(int i, char c, const char* p, double d) { print_int(i); // rendben print_char(c); // rendben print_string(p); // rendben print_int(c); print_char(i); print_string(i); print_int(d); // rendben? print_int(int(c)) meghvsa // rendben? print_char(char(i)) meghvsa // hiba // rendben? print_int(int(d)) meghvsa

A tlterhelt print()-hez kpest tbb nvre s arra is emlkeznnk kell, hogy a neveket helyesen hasznljuk. Ez fraszt lehet, meghistja az ltalnostott programozsra (2.7.2) irnyul erfesztseket, s ltalban arra sztnzi a programozt, hogy viszonylag alacsony

Forrs: http://www.doksi.hu

202

Alapok

szint tpusokra irnytsa figyelmt. Mivel nincs tlterhels, ezen fggvnyek paramterein brmilyen szabvnyos konverzit elvgezhetnk, ami szintn hibkhoz vezethet. Ebbl az kvetkezik, hogy a fenti pldban a ngy hibs paramterrel val hvs kzl csak egyet vesz szre a fordtprogram. A tlterhels nveli annak az eslyt, hogy egy nem megfelel paramtert a fordtprogram elutast.

7.4.1. A tlterhels s a visszatrsi tpus


A tlterhels feloldsnl a visszatrsi tpust nem vesszk figyelembe. Ez azrt szksges, hogy az egyes opertorokra (11.2.1, 11.2.4) vagy fggvnyhvsra vonatkoz felolds krnyezetfggetlen maradjon. Vegyk a kvetkezt:
float sqrt(float); double sqrt(double); void f(double da, float fla) { float fl = sqrt(da); double d = sqrt(da); fl = sqrt(fla); d = sqrt(fla); }

// sqrt(double) meghvsa // sqrt(double) meghvsa // sqrt(float) meghvsa // sqrt(float) meghvsa

Ha a visszatrsi tpust a fordtprogram figyelembe venn, tbb nem lenne lehetsges, hogy elszigetelten nzzk az sqrt() egy hvst s eldntsk, azzal melyik fggvnyt hvtk meg.

7.4.2. A tlterhels s a hatkr


A klnbz, nem nvtr hatkrben megadott fggvnyek nem tlterheltek:
void f(int); void g() { void f(double); f(1); }

// f(double) meghvsa

Forrs: http://www.doksi.hu

7. Fggvnyek

203

Vilgos, hogy f(int) lett volna a legjobb illeszkeds f(1)-re, de a hatkrben csak f(double) lthat. Az ilyen esetekben helyi deklarcikat adhatunk a kdhoz vagy trlhetjk azokat, hogy megkapjuk a kvnt viselkedst. Mint mindig, a szndkos elfeds hasznos mdszer lehet, a nem szndkos azonban meglepetseket okozhat. Ha osztly-hatkrk (15.2.2) vagy nvtr-hatkrk (8.2.9.2) kztt tnyl tlterhelst szeretnnk, using deklarcikat vagy using utastsokat hasznlhatunk (8.2.2). Lsd mg 8.2.6-ot.

7.4.3. A tbbrtelmsg kzi feloldsa


Tbbrtelmsghez vezethet, ha egy fggvnynek tl sok (vagy tl kevs) tlterhelt vltozatt adjuk meg:
void f1(char); void f1(long); void f2(char*); void f2(int*); void k(int i) { f1(i); // tbbrtelm: f1(char) vagy f1(long) f2(0); // tbbrtelm: f2(char*) vagy f2(int*) }

Ahol lehetsges, az ilyen esetekben gy kell tekintsk egy fggvny tlterhelt vltozatainak halmazt, mint egszet, s gondoskodnunk kell rla, hogy a vltozatok azonos rtelmek legyenek. Ez tbbnyire gy oldhat meg, ha a fggvnynek egy olyan j vltozatt adjuk hozz az eddigiekhez, amely feloldja a tbbrtelmsget:
inline void f1(int n) { f1(long(n)); }

A fenti fggvnyvltozat hozzadsa feloldja az sszes f1(i)-hez hasonl tbbrtelmsget a szlesebb long int tpus javra. A hvsok feloldsra tpusknyszertst is hasznlhatunk:
f2(static_cast<int*>(0));

Ez azonban ltalban csnya s ideiglenes szksgmegolds, hiszen a kvetkez hamarosan bekvetkez hasonl fggvnyhvssal ismt foglalkoznunk kell majd.

Forrs: http://www.doksi.hu

204

Alapok

Nhny kezd C++ programozt bosszantanak a fordtprogram ltal kirt tbbrtelmsgi hibk, a tapasztaltabbak viszont becslik ezeket az zeneteket, mert hasznos jelzi a tervezsi hibknak.

7.4.4. Tbb paramter feloldsa


A tlterhelst felold szablyok alapjn biztosthatjuk, hogy a legegyszerbb algoritmus (fggvny) lesz felhasznlva, amikor a szmtsok hatkonysga s pontossga jelentsen klnbzik a szban forg tpusoknl:
int pow(int, int); double pow(double, double); complex pow(double, complex); complex pow(complex, int); complex pow(complex, double); complex pow(complex, complex); void k(complex z) { int i = pow(2,2); double d = pow(2.0,2.0); complex z2 = pow(2,z); complex z3 = pow(z,2); complex z4 = pow(z,z); }

// pow(int,int) meghvsa // pow(double,double) meghvsa // pow(double,complex) meghvsa // pow(complex,int) meghvsa // pow(complex,complex) meghvsa

A kett vagy tbb paramter tlterhelt fggvnyek kztti vlaszts folyamn minden paramterre kivlasztdik a legjobban illeszked fggvny, a 7.4 szablyai alapjn. Az a fggvny hvdik meg, amely az adott paramterre a legjobban, a tbbire pedig jobban vagy ugyangy illeszkedik. Ha nem ltezik ilyen fggvny, a hvst a fordt tbbrtelmknt elutastja:
void g() { double d = pow(2.0,2); }

// hiba: pow(int(2.0),2) vagy pow(2.0,double(2))?

A fggvnyhvs azrt tbbrtelm, mert a pow (double,double) els paramterre 2.0, a pow(int,int) msodik paramterre pedig 2 a legjobb illeszkeds.

Forrs: http://www.doksi.hu

7. Fggvnyek

205

7.5. Alaprtelmezett paramterek


Egy ltalnos fggvnynek ltalban tbb paramterre van szksge, mint amennyi az egyszer esetek kezelshez kell. Nevezetesen az objektumokat (10.2.3) ltrehoz fggvnyek gyakran szmos lehetsget nyjtanak a rugalmassg rdekben. Vegynk egy fggvnyt, amely egy egszt r ki. sszernek ltszik megadni a felhasznlnak a lehetsget, hogy meghatrozza, milyen szmrendszerben rja ki a fggvny az egszt, a legtbb program azonban az egszeket tzes szmrendszer szerint rja ki. Pldul a
void print(int value, int base =10); void f() { print(31); print(31,10); print(31,16); print(31,2); } // az alaprtelmezett alap 10

ezt a kimenetet eredmnyezheti:


31 31 1f 11111

Az alaprtelmezett (default) paramter hatst elrhetjk tlterhelssel is:


void print(int value, int base); inline void print(int value) { print(value,10); }

A tlterhels viszont kevsb teszi nyilvnvalv az olvas szmra, hogy az a szndkunk, hogy legyen egy egyszer print fggvnynk s egy rvidtsnk. Az alaprtelmezett paramter tpusnak ellenrzse a fggvny deklarcijakor trtnik s a paramter a fggvny hvsakor rtkeldik ki. Alaprtelmezett rtkeket csak a zr paramtereknek adhatunk:
int f(int, int =0, char* =0); // rendben int g(int =0, int =0, char*); // hiba int h(int =0, int, char* =0); // hiba

Forrs: http://www.doksi.hu

206

Alapok

Jegyezzk meg, hogy a * s az = kztti szkz lnyeges (a *= rtkad opertor, 6.2):


int nasty(char*=0); // szintaktikus hiba

Az alaprtelmezett paramterek ugyanabban a hatkrben egy ksbbi deklarcival nem ismtelhetk meg s nem mdosthatk:
void f(int x = 7); void f(int = 7); void f(int = 8); void g() { void f(int x = 9); // ... } // hiba: az alaprtelmezett paramter nem adhat meg ktszer // hiba: klnbz alaprtelmezett paramterek

// rendben: ez a deklarci elfedi a klst

Hibalehetsget rejt magban, ha egy nevet gy adunk meg egy begyazott hatkrben, hogy a nv elfedi ugyanannak a nvnek egy kls hatkrben lev deklarcijt.

7.6. Nem meghatrozott szm paramter


Nhny fggvny esetben nem hatrozhat meg a hvsban elvrt paramterek szma s tpusa. Az ilyen fggvnyeket gy adhatjuk meg, hogy a paramter-deklarcik listjt a jellssel zrjuk le, melynek jelentse s taln nhny tovbbi paramter:
int printf(const char* ...);

A fenti azt hatrozza meg, hogy a C standard knyvtrnak printf() fggvnye (21.8) meghvsakor legalbb egy char* tpus paramtert vr, de lehet, hogy van ms paramtere is:
printf("Hell, vilg!\n"); printf("A nevem %s %s\n", vezetek_nev, kereszt_nev); printf("%d + %d = %d\n",2,3,5);

Forrs: http://www.doksi.hu

7. Fggvnyek

207

Az ilyen fggvnyek olyan adatokra tmaszkodnak, amelyek nem elrhetk a fordtprogram szmra, amikor az a paramterek listjt rtelmezi. A printf() esetben az els paramter egy formtum-vezrl, amely egyedi karaktersorozatokat tartalmaz, lehetv tve, hogy a printf() helyesen kezelje a tbbi paramtert: a %s pldul azt jelenti, hogy vrj egy char* paramtert, a %d pedig azt, hogy vrj egy int paramtert. A fordtprogram viszont ltalban nem tudhatja (s nem is biztosthatja), hogy a vrt paramterek tnyleg ott vannak s megfelel tpusak-e:
#include <stdio.h> int main() { printf("A nevem %s %s\n",2); }

A fenti kdot a fordt lefordtja s (a legjobb esetben) furcsnak ltsz kimenetet hoz ltre. (Prbljuk ki!) Termszetesen ha egy paramter nem deklarlt, a fordtprogram nem fog elegend informcival rendelkezni a szabvnyos tpusellenrzs s -konverzi elvgzshez. Ebben az esetben egy short vagy egy char int-knt addik t, egy float pedig double-knt, a programoz pedig nem felttlenl ezt vrja. Egy jl megtervezett programban legfeljebb nhny olyan fggvnyre van szksg, melynek paramterei nem teljesen meghatrozottak. A tlterhelt vagy alaprtelmezett paramtereket hasznl fggvnyek arra hasznlhatk, hogy megoldjk a tpusellenrzst a legtbb olyan esetben, amikor a paramterek tpust szksgbl meghatrozatlanul hagyjuk. A hrom pont csak akkor szksges, ha a paramterek szma s tpusa is vltozik. Leggyakrabban akkor hasznljuk, amikor olyan C knyvtri fggvnyekhez ksztnk felletet, amelyek mg nem hasznljk ki a C++ ltal nyjtott jabb lehetsgeket:
int fprintf(FILE*, const char* ...); int execl(const char* ...); // a <cstdio> fejllomnybl // UNIX fejllomnybl

A <cstdarg> fejllomnyban szabvnyos makrkat tallunk, melyekkel hozzfrhetnk az ilyen fggvnyek nem meghatrozott paramtereihez. Kpzeljk el, hogy egy olyan hibafggvnyt runk, amelynek van egy egsz paramtere, ami a hiba slyossgt jelzi, s ezt tetszleges hosszsg (tbb karakterlncbl ll) szveg kveti. Elkpzelsnk az, hogy gy hozzuk ltre a hibazenetet, hogy minden szt kln karakterlnc paramterknt adunk t. Ezen paramterek listjt egy char-ra hivatkoz nullpointer kell, hogy lezrja:

Forrs: http://www.doksi.hu

208

Alapok

extern void error(int ...); extern char* itoa(int, char[ ]); const char* Null_cp = 0;

// lsd 6.6.[17]

int main(int argc, char* argv[ ]) { switch (argc) { case 1: error(0,argv[0],Null_cp); break; case 2: error(0,argv[0],argv[1],Null_cp); break; default: char buffer[8]; error(1,argv[0], "with",itoa(argc-1,buffer),"arguments", Null_cp); } // ... }

Az itoa() azt a karakterlncot adja vissza, amelyik a fggvny egsz tpus paramternek felel meg. Vegyk szre, hogy ha a 0 egsz rtket hasznltuk volna befejezsknt, a kd nem lett volna hordozhat: nhny nyelvi megvalsts a nulla egszt s a nulla mutatt (nullpointer) nem azonos mdon brzolja. Ez a plda szemllteti a nehzsgeket s azt a tbbletmunkt, amellyel a programoz szembenz, amikor a tpusellenrzst elnyomja a hrom pont. A hibafggvnyt gy adhatjuk meg:
void error(int severity ...) { va_list ap; va_start(ap,severity); for (;;) { // a "severity" (slyossg) utn nullval lezrt char*-ok // kvetkeznek // kezdeti paramterek

char* p = va_arg(ap,char*); if (p == 0) break; cerr << p << ' '; // paramterek visszalltsa

va_end(ap); cerr << '\n'; if (severity) exit(severity);

Forrs: http://www.doksi.hu

7. Fggvnyek

209

Elszr meghatrozzuk a va_list-et s a va_start() meghvsval rtket adunk neki. A va_start makr paramterei a va_list neve s az utols formlis paramter neve. A va_arg() makrt arra hasznljuk, hogy sorba rendezzk a nem megnevezett paramtereket. A programoznak minden egyes hvskor meg kell adnia egy tpust; a va_arg() felttelezi, hogy ilyen tpus aktulis paramter kerlt tadsra, de ltalban nincs md arra, hogy ezt biztostani is tudja. Mieltt mg visszatrnnk egy olyan fggvnybl, ahol a va_start()-ot hasznltuk, meg kell hvnunk a va_end()-et. Ennek az az oka, hogy a va_start() gy mdosthatja a vermet, hogy a visszatrst nem lehet sikeresen vghezvinni. A va_end() helyrelltja ezeket a mdostsokat.

7.7. Fggvnyre hivatkoz mutatk


A fggvnyekkel csak kt dolgot csinlhatunk: meghvhatjuk ket s felhasznlhatjuk a cmket. Amikor a fggvny cmt vesszk, az gy kapott mutatt hasznlhatjuk arra, hogy meghvjuk a fggvnyt:
void error(string s) { /* ... */ } void (*efct)(string); void f() { efct = &error; efct("error"); } // mutat fggvnyre

// efct az error fggvnyre mutat // error meghvsa efct-n keresztl

A fordtprogram r fog jnni, hogy efct egy mutat s meghvja az ltala mutatott fggvnyt. Azaz, egy fggvnyre hivatkoz mutatt nem ktelez a * opertorral feloldanunk. Ugyangy nem ktelez a & hasznlata sem a fggvny cmnek lekrdezsre:
void (*f1)(string) = &error; void (*f2)(string) = error; void g() { f1("Vasa"); (*f1)("Mary Rose"); } // rendben // ez is j; jelentse ugyanaz, mint az &error-nak

// rendben // ez is j

A fggvnyekre hivatkoz mutatk paramtereinek tpust ugyangy meg kell adnunk, mint a fggvnyeknl. A mutatkat hasznl rtkadsokban gyelni kell a teljes fggvny tpusra:

Forrs: http://www.doksi.hu

210

Alapok

void (*pf)(string); void f1(string); int f2(string); void f3(int*); void f() { pf = &f1; pf = &f2; pf = &f3; pf("Hra"); pf(1); } int i = pf("Zeusz");

// mutat void(string)-re // void(string) // int(string) // void(int*)

// rendben // hiba: rossz visszatrsi tpus // hiba: rossz paramtertpus // rendben // hiba: rossz paramtertpus // hiba: void rtkads int-nek

A paramtertads szablyai a kzvetlen fggvnyhvsok s a fggvnyek mutatn keresztl trtn meghvsa esetben ugyanazok. Gyakran knyelmes, ha nevet adunk egy fggvnyre hivatkoz mutat tpusnak, hogy ne mindig a meglehetsen nehezen rthet deklarciformt hasznljuk. me egy plda egy UNIX-os rendszer-fejllomnybl:
typedef void (*SIG_TYP)(int); typedef void (*SIG_ARG_TYP)(int); SIG_TYP signal(int, SIG_ARG_TYP); // a <signal.h> fejllomnybl

A fggvnyekre hivatkoz mutatkbl ll tmbk gyakran hasznosak. Pldul az egeret hasznl szvegszerkesztm menrendszere az egyes mveleteket jell fggvnyekre hivatkoz mutatkbl sszelltott tmbkkel van megvalstva. Itt nincs lehetsgnk, hogy a rendszert rszletesen ismertessk, de az alapvet tlet ez:
typedef void (*PF)(); PF edit_ops[ ] = { // szerkesztmveletek &cut, &paste, &copy, &search }; PF file_ops[ ] = { // fjlkezels &open, &append, &close, &write };

Az egr gombjaival kivlasztott menpontokhoz kapcsold mveleteket vezrl mutatkat gy hatrozhatjuk meg s tlthetjk fel rtkkel:
PF* button2 = edit_ops; PF* button3 = file_ops;

Forrs: http://www.doksi.hu

7. Fggvnyek

211

A teljes megvalstshoz tbb informcira van szksg ahhoz, hogy minden menelemet meghatrozhassunk. Pldul trolnunk kell valahol azt a karakterlncot, amelyik meghatrozza a kirand szveget. Ahogy a rendszert hasznljuk, az egrgombok jelentse gyakran megvltozik a krnyezettel egytt. Az ilyen vltozsokat (rszben) gy hajtjuk vgre, hogy mdostjuk a gombokhoz kapcsolt mutatk rtkt. Amikor a felhasznl kivlaszt egy menpontot (pldul a 3-as elemet a 2-es gomb szmra), a megfelel mvelet hajtdik vgre:
button2[2](); // button2 harmadik fggvnynek meghvsa

Akkor tudnnk igazn nagyra rtkelni a fggvnyekre hivatkoz mutatk kifejezerejt, ha nlklk prblnnk ilyen kdot rni s mg jobban viselked rokonaik, a virtulis fggvnyek (12.2.6) nlkl. Egy ment futsi idben gy mdosthatunk, hogy j fggvnyeket tesznk a mvelettblba, de j menket is knnyen ltrehozhatunk. A fggvnyekre hivatkoz mutatk arra is hasznlhatk, hogy a tbbalak (polimorf) eljrsok azaz amelyeket tbb, klnbz tpus objektumra lehet alkalmazni egyszer formjt adjk:
typedef int (*CFT)(const void*, const void*); void ssort(void* base, size_t n, size_t sz, CFT cmp) /* A "base" vektor "n" elemnek rendezse nvekv sorrendbe a "cmp" ltal mutatott sszehasonlt fggvny segtsgvel. Az elemek "sz" mretek. */ { Shell rendezs (Knuth, 3. ktet, 84.o.)

for (int gap=n/2; 0<gap; gap/=2) for (int i=gap; i<n; i++) for (int j=i-gap; 0<=j; j-=gap) { char* b = static_cast<char*>(base); // szksges tpusknyszerts char* pj = b+j*sz; // &base[j] char* pjg = b+(j+gap)*sz; // &base[j+gap] if (cmp(pjg,pj)<0) { // base[j] s base[j+gap] felcserlse for (int k=0; k<sz; k++) { char temp = pj[k]; pj[k] = pjg[k]; pjg[k] = temp; } } }

Forrs: http://www.doksi.hu

212

Alapok

Az ssort() nem ismeri azoknak az objektumoknak a tpust, amelyeket rendez, csak az elemek szmt (a tmb mrett), az egyes elemek mrett, s azt a fggvnyt, melyet meg kell hvnia, hogy elvgezze az sszehasonltst. Az ssort() tpust gy vlasztottuk meg, hogy megegyezzen a szabvnyos C knyvtri qsort() rendez eljrs tpusval. A valdi programok a qsort()-ot, a C++ standard knyvtrnak sort algoritmust (18.7.1), vagy egyedi rendez eljrst hasznlnak. Ez a kdolsi stlus gyakori C-ben, de nem a legelegnsabb mdja, hogy ezt az algoritmust C++-ban rjuk le (lsd 13.3, 13.5.2). Egy ilyen rendez fggvnyt a kvetkezkppen lehetne egy tblzat rendezsre hasznlni:
struct User { char* name; char* id; int dept; }; User heads[ ] = { "Ritchie D.M.", "Sethi R.", "Szymanski T.G.", "Schryer N.L.", "Schryer N.L.", "Kernighan B.W.", }; "dmr", "ravi", "tgs", "nls", "nls", "bwk", 11271, 11272, 11273, 11274, 11275, 11276

void print_id(User* v, int n) { for (int i=0; i<n; i++) cout << v[i].name << '\t' << v[i].id << '\t' << v[i].dept << '\n'; }

Elszr meg kell hatroznunk a megfelel sszehasonlt fggvnyeket, hogy rendezni tudjunk. Az sszehasonlt fggvnynek negatv rtket kell visszaadnia, ha az els paramtere kisebb, mint a msodik, nullt, ha paramterei egyenlek, egyb esetben pedig pozitv szmot:
int cmp1(const void* p, const void* q) // nevek (name) sszehasonltsa { return strcmp(static_cast<const User*>(p)->name,static_cast<const User*>(q)->name); } int cmp2(const void* p, const void* q) // osztlyok (dept) sszehasonltsa { return static_cast<const User*>(p)->dept - static_cast<const User*>(q)->dept; }

Forrs: http://www.doksi.hu

7. Fggvnyek

213

Ez a program rendez s kir:


int main() { cout << "Fnkk bcsorrendben:\n"; ssort(heads,6,sizeof(User),cmp1); print_id(heads,6); cout << '\n'; cout << "Fnkk osztlyok szerint:\n"; ssort(heads,6,sizeof(User),cmp2); print_id(heads,6);

Egy tlterhelt fggvny cmt gy hasznlhatjuk fel, hogy egy fggvnyre hivatkoz mutathoz rendeljk vagy annak kezdrtkl adjuk. Ebben az esetben a cl tpusa alapjn vlasztunk a tlterhelt fggvnyek halmazbl:
void f(int); int f(char); void (*pf1)(int) = &f; int (*pf2)(char) = &f; void (*pf3)(char) = &f; // void f(int) // int f(char) // hiba: nincs void f(char)

Egy fggvnyt egy fggvnyre hivatkoz mutatn keresztl pontosan a megfelel paramter- s visszatrsi tpusokkal kell meghvni. Ezen tpusokra vonatkozan nincs automatikus konverzi, ha fggvnyekre hivatkoz mutatkat adunk rtkl vagy tltnk fel kezdrtkkel. Ez azt jelenti, hogy
int cmp3(const mytype*,const mytype*);

nem megfelel paramter az ssort() szmra. Ha cmp3-at elfogadnnk az ssort paramtereknt, megszegnnk azt a vllalst, hogy a cmp3-at mytype* tpus paramterekkel fogjuk meghvni (lsd mg 9.2.5-t).

Forrs: http://www.doksi.hu

214

Alapok

7.8. Makrk
A makrk nagyon fontosak a C-ben, de kevesebb a hasznuk a C++-ban. Az els makrkra vonatkoz szably: ne hasznljuk ket, ha nem szksgesek. Majdnem minden makr a programozsi nyelv, a program, vagy a programoz gyenge pontjt mutatja. Mivel trendezik a programkdot, mieltt a fordtprogram ltn azt, szmos programozsi eszkz szmra komoly problmt jelentenek. gy ha makrt hasznlunk, szmthatunk arra, hogy az olyan eszkzk, mint a hibakeresk, kereszthivatkozs-vizsglk s hatkonysgvizsglk gyengbb szolgltatst fognak nyjtani. Ha makrt kell hasznlnunk, olvassuk el figyelmesen C++-vltozatunk elfordtjnak (preprocessor) hivatkozsi kziknyvt s ne prbljunk tl okosak lenni. Kvessk azt a szokst, hogy a makrkat gy nevezzk el, hogy sok nagybet legyen bennk. A makrk formai kvetelmnyeit az A.11 mutatja be. Egy egyszer makrt gy adhatunk meg:
#define NAME a sor maradk rsze

ahol a NAME szimblum elfordul, ott kicserldik a sor maradk rszre. Pldul a
named = NAME

kifejezst a kvetkez vltja fel:


named = a sor maradk rsze

Megadhatunk paramterekkel rendelkez makrt is:


#define MAC(x,y) argument1: x argument2: y

Amikor MAC-ot hasznljuk, paramterknt meg kell adnunk kt karakterlncot. Ezek x-et s y-t fogjk helyettesteni, amikor MAC behelyettestdik. Pldul a
expanded = MAC(foo bar, yuk yuk)

gy alakul t:
expanded = argument1: foo bar argument2: yuk yuk

Forrs: http://www.doksi.hu

7. Fggvnyek

215

A makrneveket nem terhelhetjk tl s a makr-elfordt rekurzv hvsokat sem tud kezelni:


#define PRINT(a,b) cout<<(a)<<(b) #define PRINT(a,b,c) cout<<(a)<<(b)<<(c) /* problms?: jbli definci, nem tlterhels */ #define FAC(n) (n>1)?n*FAC(n-1):1 /* problms: rekurzv makr */

A makrk karakterlncokat kezelnek, keveset tudnak a C++ nyelvtanrl s semmit sem a C++ tpusairl, illetve a hatkrk szablyairl. A fordtprogram csak a makr behelyettestett formjt ltja, gy akkor jelzi a makrban lv esetleges hibt, amikor a makr behelyettestdik, s nem akkor, amikor a makrt kifejtjk, ami nagyon homlyos hibazenetekhez vezet. me nhny lehetsges makr:
#define CASE break;case #define FOREVER for(;;)

Nhny teljesen flsleges makr:


#define PI 3.141593 #define BEGIN { #define END }

s nhny veszlyes makr:


#define SQUARE(a) a*a #define INCR_xx (xx)++

Hogy lssuk, mirt veszlyesek, prbljuk meg behelyettesteni ezt:


int xx = 0; void f() { int xx = 0; int y = SQUARE(xx+2); INCR_xx; } // globlis szmll

// loklis vltoz // y=xx+2*xx+2 vagyis y=xx+(2*xx)+2 // a loklis xx nvelse

Forrs: http://www.doksi.hu

216

Alapok

Ha makrt kell hasznlnunk, hasznljuk a :: hatkr-jelzt, amikor globlis nevekre (4.9.4) hivatkozunk, s a makr paramterek elfordulsait tegyk zrjelbe, ahol csak lehetsges:
#define MIN(a,b) (((a)<(b))?(a):(b))

Ha bonyolult makrkat kell rnunk, amelyek megjegyzsekre szorulnak, blcs dolog /* */ megjegyzseket hasznlnunk, mert a C++ eszkzk rszeknt nha C elfordtkat hasznlnak, ezek viszont nem ismerik a // jellst:
#define M2(a) something(a) /* rtelmes megjegyzs */

Makrk hasznlatval megtervezhetjk sajt, egyni nyelvnket. Ha azonban ezt a kibvtett nyelvet rszestjk elnyben a sima C++-szal szemben, az a legtbb C++ programoz szmra rthetetlen lesz. Tovbb a C elfordt egy nagyon egyszer makr-feldolgoz. Ha valami nem magtl rtetdt akarunk csinlni, akkor az vagy lehetetlennek, vagy szksgtelenl nehznek bizonyulhat. A const, inline, template, enum s namespace megoldsokat arra szntk, hogy a hagyomnyos elfordtott szerkezeteket kivltsk:
const int answer = 42; template<class T> inline T min(T a, T b) { return (a<b)?a:b; }

Amikor makrt runk, nem ritka, hogy egy j nvre van szksgnk valami szmra. Kt karakterlncot a ## makropertorral sszefzve pldul j karakterlncot hozhatunk ltre:
#define NAME2(a,b) a##b int NAME2(hack,cah)();

Ez a kvetkezt eredmnyezi a fordtprogram szmra:


int hackcah();

A
#undef X

utasts biztostja, hogy X nev makr nem lesz definilva akkor sem, ha az utasts eltt szerepelt ilyen. Ez bizonyos vdelmet ad a nem kvnt makrk ellen, de nem tudhatjuk, hogy egy kdrszletben mit felttelezznk X hatsairl.

Forrs: http://www.doksi.hu

7. Fggvnyek

217

7.8.1. Feltteles fordts


A makrk egy bizonyos hasznlatt majdnem lehetetlen elkerlni. Az #ifdef azonost direktva arra utastja a fordtprogramot, hogy felttelesen minden bemenetet figyelmen kvl hagyjon, amg az #endif utastssal nem tallkozik. Pldul az
int f(int a #ifdef arg_two ,int b #endif );

kdrszletbl a fordtprogram ennyit lt (kivve ha az arg_two nev makrt a #define elfordt direktvval korbban definiltuk):
int f(int a );

Ez megzavarja azokat az eszkzket, amelyek sszer viselkedst tteleznek fel a programozrl. Az #ifdef legtbb felhasznlsa kevsb bizarr, s ha mrsklettel hasznljk, kevs krt okoz. Lsd mg 9.3.3-at. Az #ifdef-et vezrl makrk neveit figyelmesen kell megvlasztani, hogy ne tkzzenek a szoksos azonostkkal:
struct Call_info { Node* arg_one; Node* arg_two; // ... };

Ez az rtatlannak ltsz forrsszveg zavart fog okozni, ha valaki a kvetkezt rja:


#define arg_two x

Sajnos a szokvnyos s elkerlhetetlenl beptend fejllomnyok sok veszlyes s szksgtelen makrt tartalmaznak.

Forrs: http://www.doksi.hu

218

Alapok

7.9. Tancsok
[1] Legynk gyanakvak a nem const referencia paramterekkel kapcsolatban; ha azt akarjuk, hogy a fggvny mdostsa paramtert, hasznljunk inkbb mutatkat s rtk szerinti visszaadst. 5.5. [2] Hasznljunk const referencia paramtereket, ha a lehet legritkbbra kell cskkentennk a paramterek msolst. 5.5. [3] Hasznljuk a const-ot szleskren, de kvetkezesen. 7.2. [4] Kerljk a makrkat. 7.8. [5] Kerljk a nem meghatrozott szm paramterek hasznlatt. 7.6. [6] Ne adjunk vissza loklis vltozkra hivatkoz mutatkat vagy ilyen referencikat. 7.3. [7] Akkor hasznljuk a tlterhelst, ha a fggvnyek elvben ugyanazt a mveletet hajtjk vgre klnbz tpusokon. 7.4. [8] Amikor egszekre vonatkozik a tlterhels, hasznljunk fggvnyeket, hogy megszntessk a tbbrtelmsget. 7.4.3. [9] Ha fggvnyre hivatkoz mutat hasznlatt fontolgatjuk, vizsgljuk meg, hogy egy virtulis fggvny (2.5.5) vagy sablon (2.7.2) hasznlata nem jobb megolds-e. 7.7. [10] Ha makrkat kell hasznlnunk, hasznljunk csnya neveket, sok nagybetvel. 7.8.

7.10. Gyakorlatok
1. (*1) Deklarljuk a kvetkezket: fggvny, amelynek egy karakterre hivatkoz mutat s egy egszre mutat referencia paramtere van s nem ad vissza rtket; ilyen fggvnyre hivatkoz mutat; fggvny, amelynek ilyen mutat paramtere van; fggvny, amely ilyen mutatt ad vissza. rjuk meg azt a fggvnyt, amelynek egy ilyen mutatj paramtere van s visszatrsi rtkknt paramtert adja vissza. Tipp: hasznljunk typedef-et. 2. (*1) Mit jelent a kvetkez sor? Mire lehet j?
typedef int (&rifii) (int, int);

Forrs: http://www.doksi.hu

7. Fggvnyek

219

3. (*1,5) rjunk egy Hell, vilg!-szer programot, ami parancssori paramterknt vesz egy nevet s kirja, hogy Hell, nv!. Mdostsuk ezt a programot gy, hogy tetszleges szm nv paramtere lehessen s mondjon hellt minden egyes nvvel. 4. (*1,5) rjunk olyan programot, amely tetszleges szm fjlt olvas be, melyek nevei parancssori paramterknt vannak megadva, s kirja azokat egyms utn a cout-ra. Mivel ez a program sszefzi a paramtereit, hogy megkapja a kimenetet, elnevezhetjk cat-nek. 5. (*2) Alaktsunk egy kis C programot C++ programm. Mdostsuk a fejllomnyokat gy, hogy minden meghvott fggvny deklarlva legyen s hatrozzuk meg minden paramter tpust. Ahol lehetsges, cserljk ki a #define utastsokat enum-ra, const-ra vagy inline-ra. Tvoltsuk el az extern deklarcikat a .c fjlokbl, s ha szksges, alaktsunk t minden fggvnyt a C++ fggvnyek formai kvetelmnyeinek megfelelen. Cserljk ki a malloc() s free() hvsokat new-ra, illetve delete-re. Tvoltsuk el a szksgtelen konverzikat. 6. (*2) rjuk jra az ssort()-ot (7.7) egy hatkonyabb rendezsi algoritmus felhasznlsval. Tipp: qsort(). 7. (*2,5) Vegyk a kvetkezt:
struct Tnode { string word; int count; Tnode* left; Tnode* right; };

rjunk fggvnyt, amellyel j szavakat tehetnk egy Tnode-okbl ll fba. rjunk fggvnyt, amely kir egy Tnode-okbl ll ft. rjunk olyan fggvnyt, amely egy Tnode-okbl ll ft gy r ki, hogy a szavak bcsorrendben vannak. Mdostsuk a Tnode-ot, hogy (csak) egy mutatt troljon, ami egy tetszlegesen hossz szra mutat, amit a szabad tr karaktertmbknt trol, a new segtsgvel. Mdostsuk a fggvnyeket, hogy a Tnode j definicijt hasznljk. 8. (*2,5) rjunk fggvnyt, amely ktdimenzis tmbt invertl. Tipp: C.7. 9. (*2) rjunk titkost programot, ami a cin-rl olvas s a kdolt karaktereket kirja a cout-ra. Hasznlhatjuk a kvetkez, egyszer titkost smt: c karakter titkostott formja legyen c^key[i], ahol key egy karakterlnc, amely parancssori paramterknt adott. A program ciklikus mdon hasznlja a key-ben lv karaktereket, amg a teljes bemenetet el nem olvasta. Ha nincs megadva key (vagy a paramter null-karakterlnc), a program ne vgezzen titkostst.

Forrs: http://www.doksi.hu

220

Alapok

10. (*3,5) rjunk programot, ami segt megfejteni a 7.10[9]-ben lert mdszerrel titkostott zeneteket, anlkl, hogy tudn a kulcsot. Tipp: lsd David Kahn: The Codebreakers, Macmillan, 1967, New York; 207-213. o. 11. (*3) rjunk egy error nev fggvnyt, amely %s, %c s %d kifejezseket tartalmaz, printf stlus, formzott karakterlncokat vesz paramterknt s ezen kvl tetszleges szm paramtere lehet. Ne hasznljuk a printf()-et. Nzzk meg a 21.8-at, ha nem tudjuk, mit jelent a %s, %c s %d. Hasznljuk a <cstdarg>-ot. 12. (*1) Hogyan vlasztannk meg a typedef hasznlatval meghatrozott fggvnyekre hivatkoz mutattpusok neveit? 13. (*2) Nzznk meg nhny programot, hogy elkpzelsnk lehessen a mostansg hasznlatos nevek stlusnak vltozatossgrl. Hogyan hasznljk a nagybetket? Hogyan hasznljk az alhzst? Mikor hasznlnak rvid neveket, mint amilyen az i s x? 14. (*1) Mi a hiba ezekben a makrkban?
#define PI = 3.141593; #define MAX(a,b) a>b?a:b #define fac(a) (a)*fac((a)-1)

15. (*3) rjunk makrfeldolgozt, amely egyszer makrkat definil s cserl ki (ahogy a C elfordt teszi). Olvassunk a cin-rl s rjunk a cout-ra. Elszr ne prbljunk paramterekkel rendelkez makrkat kezelni. Tipp: az asztali szmolgp (6.1) tartalmaz egy szimblumtblt s egy lexikai elemzt, amit mdosthatunk. 16. (*2) rjuk meg magunk a print() fggvnyt a 7.5-bl. 17. (*2) Adjunk hozz a 6.1 pontban lv asztali szmolgphez olyan fggvnyeket, mint az sqrt(), log(), s sin(). Tipp: adjuk meg elre a neveket, a fggvnyeket pedig fggvnyre hivatkoz mutatkbl ll tmbn keresztl hvjuk meg. Ne felejtsk el ellenrizni a fggvnyhvsok paramtereit. 18. (*1) rjunk olyan faktorilis fggvnyt, amely nem hvja meg nmagt. Lsd mg 11.14[6]-ot. 19. (*2) rjunk fggvnyeket, amelyek egy napot, egy hnapot, s egy vet adnak hozz egy Date-hez, ahogy azt a 6.6[13]-ban lertuk. rjunk fggvnyt, ami megadja, hogy egy adott Date a ht melyik napjra esik. rjunk olyan fggvnyt, ami megadja egy adott Date-re kvetkez els htf Date-jt.

Forrs: http://www.doksi.hu

8
Nvterek s kivtelek
Ez a 787-es v! I.sz.? (Monty Python) Nincs olyan ltalnos szably, ami all ne lenne valamilyen kivtel. (Robert Burton)

Modulok, felletek s kivtelek Nvterek using using namespace Nvtkzsek feloldsa Nevek keresse Nvterek sszefzse Nvtr-lnevek Nvterek s C kd Kivtelek throw s catch A kivtelek s a programok szerkezete Tancsok Gyakorlatok

8.1. Modulok s felletek


Minden valsgos program klnll rszekbl ll. Mg az egyszer Hell, vilg! program is legalbb kt rszre oszthat: a felhasznli kdra, ami a Hell, vilg! kirst kri, s a kirst vgz I/O rendszerre.

Forrs: http://www.doksi.hu

222

Alapok

Vegyk a szmolgp pldjt a 6.1-bl. Lthatjuk, hogy 5 rszbl ll: 1. A (szintaktikai) elemzbl (parser), ami a szintaktikai elemzst vgzi, 2. az adatbeviteli fggvnybl vagy lexikai elemzbl (lexer), ami a karakterekbl szimblumokat hoz ltre 3. a (karakterlnc, rtk) prokat trol szimblumtblbl 4. a main() vezrlbl 5. s a hibakezelbl brval:

vezrl

elemz

adatbeviteli fggvny

szimblumtbla

hibakezel

A fenti brban a nyl jelentse: felhasznlja. Az egyszersts kedvrt nem jelltem, hogy mindegyik rsz tmaszkodik a hibakezelsre. Az igazat megvallva a szmolgpet hrom rszbl llra terveztem, a vezrlt s a hibakezelt a teljessg miatt adtam hozz. Amikor egy modul felhasznl egy msikat, nem szksges, hogy mindent tudjon a felhasznlt modulrl. Idelis esetben a modulok legnagyobb rsze nem ismert a felhasznl elem szmra. Kvetkezskppen klnbsget tesznk a modul s a modul fellete (interfsz) kztt. A szintaktikai elemz pldul kzvetlenl csak az adatbeviteli fggvny felletre, nem pedig a teljes lexikai elemzre tmaszkodik. Az adatbeviteli fggvny csak megvalstja a felletben kzztett szolgltatsokat. Ezt brval gy mutathatjuk be:

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

223

vezrl szintaktikai elemz fellete lexikai elemz fellete szimblumtbla fellete hibakezel szintaktikai elemz megvalstsa lexikai elemz megvalstsa szimblumtbla megvalstsa

A szaggatott vonalak jelentse: megvalstja. Ez tekinthet a program valdi felptsnek. Neknk, programozknak, az a feladatunk, hogy ezt h mdon adjuk vissza a kdban. Ha ezt tesszk, a kd egyszer, hatkony, rthet, s knnyen mdosthat lesz, mert kzvetlenl fogja tkrzni eredeti elkpzelsnket. A kvetkez rszben bemutatjuk, hogyan lehet a szmolgp program logikai felptst vilgosan kifejezni, a 9.3 pontban pedig azt, hogyan rendezhetjk el gy a program forrsszvegt, hogy abbl elnynk szrmazzon. A szmolgp kis program; a valdi letben nem hasznlnm olyan mrtkben a nvtereket s a kln fordtst (2.4.1, 9.1), mint itt. Most csak azrt hasznljuk ezeket, hogy nagyobb programok esetben is hasznos mdszereket mutassunk be, anlkl, hogy belefulladnnk a kdba. A valdi programokban minden modul, amelyet nll nvtr jell, gyakran fggvnyek, osztlyok, sablonok stb. szzait tartalmazza. A nyelvi eszkzk b vlasztknak bemutatshoz tbb lpsben bontom modulokra a szmolgpet. Az igazi programoknl nem valszn, hogy ezen lpsek mindegyikt vgrehajtannk. A tapasztalt programoz mr az elejn kivlaszthat egy krlbell megfelel tervet. Ahogy azonban a program az vek sorn fejldik, nem ritkk a drasztikus szerkezeti vltoztatsok. A hibakezels mindentt fontos szerepet tlt be a program szerkezetben. Amikor egy programot modulokra bontunk vagy egy programot modulokbl hozunk ltre, gyelnnk kell arra, hogy a hibakezels okozta modulok kztti fggsgekbl minl kevesebb legyen. A C++ kivteleket nyjt arra a clra, hogy elklntsk a hibk szlelst s jelzst azok kezelstl. Ezrt miutn trgyaltuk, hogyan brzolhatjuk a modulokat nvterekknt (8.2), bemutatjuk, hogyan hasznlhatjuk a kivteleket arra, hogy a modularitst tovbb javtsuk (8.3).

Forrs: http://www.doksi.hu

224

Alapok

A modularits fogalma sokkal tbb mdon rtelmezhet, mint ahogy ebben s a kvetkez fejezetben tesszk. Programjainkat pldul rszekre bonthatjuk prhuzamosan vgrehajtott s egymssal kapcsolatot tart folyamatok segtsgvel is. Ugyangy az nll cmterek (address spaces) s a cmterek kztti informcis kapcsolat is olyan fontos tmakrk, amelyeket itt nem trgyalunk. gy gondolom, a modularits ezen megkzeltsei nagyrszt egymstl fggetlenek s ellenttesek. rdekes mdon minden rendszer knnyen modulokra bonthat. A nehzsget a modulok kztti biztonsgos, knyelmes s hatkony kapcsolattarts biztostsa jelenti.

8.2. Nvterek
A nvterek (namespace) mindig valamilyen logikai csoportostst fejeznek ki. Azaz, ha egyes deklarcik valamilyen jellemz alapjn sszetartoznak, akkor ezt a tnyt kifejezhetjk gy is, hogy kzs nvtrbe helyezzk azokat. A szmolgp elemzjnek (6.1.1) deklarciit pldul a Parser nvtrbe tehetjk:
namespace Parser { double expr(bool); double prim(bool get) { /* ... */ } double term(bool get) { /* ... */ } double expr(bool get) { /* ... */ } }

Az expr() fggvnyt elszr deklarljuk s csak ksbb fejtjk ki, hogy megtrjk a 6.1.1ben lert fggsgi krt. A szmolgp bemeneti rszt szintn nll nvtrbe helyezhetjk:
namespace Lexer { enum Token_value { NAME, PLUS='+', PRINT=';', }; Token_value curr_tok; double number_value; string string_value; } Token_value get_token() { /* ... */ }

NUMBER, MINUS='-', ASSIGN='=',

END, MUL='*', LP='(',

DIV='/', RP=')'

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

225

A nvterek ilyen hasznlata elg nyilvnvalv teszi, mit nyjt a lexikai s a szintaktikai elemz a felhasznl programelemnek. Ha azonban a fggvnyek forrskdjt is a nvterekbe helyeztem volna, a szerkezet zavaross vlt volna. Ha egy valsgos mret nvtr deklarcijba beletesszk a fggvnytrzseket is, ltalban tbb oldalas (kpernys) informcin kell trgnunk magunkat, mire megtalljuk, milyen szolgltatsok vannak felknlva, azaz, hogy megtalljuk a felletet. Kln meghatrozott felletek helyett olyan eszkzket is biztosthatunk, amelyek kinyerik a felletet egy modulbl, amely a megvalstst tartalmazza. Ezt nem tekintem j megoldsnak. A felletek meghatrozsa alapvet tervezsi tevkenysg (lsd 23.4.3.4-et), hiszen egy modul a klnbz programelemek szmra klnbz felleteket nyjthat, radsul a felletet sokszor mr a megvalsts rszleteinek kidolgozsa eltt megtervezik. me a Parser egy olyan vltozata, ahol a felletet (interfsz) elklntjk a megvalststl (implementci):
namespace Parser { double prim(bool); double term(bool); double expr(bool); } double Parser::prim(bool get) { /* ... */ } double Parser::term(bool get) { /* ... */ } double Parser::expr(bool get) { /* ... */ }

Vegyk szre, hogy a fellet s a lnyegi programrsz sztvlasztsnak eredmnyeknt most minden fggvnynek pontosan egy deklarcija s egy defincija van. A felhasznl programelemek csak a deklarcikat tartalmaz felletet fogjk ltni. A program megvalstst ebben az esetben a fggvnytrzseket a felhasznl elem ltkrn kvl helyezzk el. Lthattuk, hogy egy tagot megadhatunk a nvtr meghatrozsn bell, s kifejthetjk ksbb, a nvtr_neve::tag_neve jellst hasznlva. A nvtr tagjait a kvetkez jells hasznlatval kell bevezetni:
namespace nvtr_nv { // deklarci s defincik }

Forrs: http://www.doksi.hu

226

Alapok

A nvtrdefincin kvl j tagot nem adhatunk meg minst formban:


void Parser::logical(bool); // hiba: nincs logical() a Parser nvtrben

A cl az, hogy knnyen meg lehessen tallni minden nevet a nvtrdeklarciban, s hogy a gpelsi, illetve az eltr tpusokbl add hibkat szrevegyk:
double Parser::trem(bool); double Parser::prim(int); // hiba: nincs trem() a Parser nvtrben // hiba: Parser::prim() logikai paramter

A nvtr (namespace) egyben hatkr (scope), vagyis nagyon alapvet s viszonylag egyszer fogalom. Minl nagyobb egy program, annl hasznosabbak a nvterek, hogy kifejezzk a program rszeinek logikai elklntst. A kznsges loklis hatkrk, a globlis hatkrk s az osztlyok maguk is nvterek (C.10.3). Idelis esetben egy program minden eleme valamilyen felismerhet logikai egysghez (modulhoz) tartozik. Ezrt elmletileg egy bonyolultabb program minden deklarcijt nll nvterekbe kellene helyezni, melyek neve a programban betlttt logikai szerepet jelzi. A kivtel a main(), amelynek globlisnak kell lennie, hogy a futsi idej krnyezet felismerje (8.3.3).

8.2.1. Minstett nevek


A nvterek kln hatkrt alkotnak. Az ltalnos hatkr-szablyok termszetesen rjuk is vonatkoznak, gy ha egy nevet elzetesen a nvtrben vagy egy krlvev blokkban adtunk meg, minden tovbbi nehzsg nlkl hasznlhatjuk. Msik nvtrbl szrmaz nevet viszont csak akkor hasznlhatunk, ha minstjk nvternek nevvel:
double Parser::term(bool get) { double left = prim(get); for (;;) switch (Lexer::curr_tok) { case Lexer::MUL: left *= prim(true); // ... } // figyeljk meg a Parser:: minstt // nem kell minst // figyeljk meg a Lexer:: minstt // figyeljk meg a Lexer:: minstt // nem kell minst

// ...

A Parser minstre itt azrt van szksg, hogy kifejezzk, hogy ez a term() az, amelyet a Parser-ben bevezettnk, s nem valamilyen ms globlis fggvny. Mivel a term()

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

227

a Parser tagja, nem kell minstenie a prim()-et. Ha azonban a Lexer minstt nem tesszk ki, a fordtprogram a curr_tok vltozt gy tekinti, mintha az nem deklarlt lenne, mivel a Lexer nvtr tagjai nem tartoznak a Parser nvtr hatkrbe.

8.2.2. Using deklarcik


Ha egy nv gyakran hasznlatos sajt nvtern kvl, bosszant lehet llandan minsteni nvternek nevvel. Vegyk a kvetkezt:
double Parser::prim(bool get) { if (get) Lexer::get_token(); // elemi szimblumok kezelse

switch (Lexer::curr_tok) { case Lexer::NUMBER: // lebegpontos konstans Lexer::get_token(); return Lexer::number_value; case Lexer::NAME: { double& v = table[Lexer::string_value]; if (Lexer::get_token() == Lexer::ASSIGN) v = expr(true); return v; } case Lexer::MINUS: // mnusz eljel (egyoperandus mnusz) return -prim(true); case Lexer::LP: { double e = expr(true); if (Lexer::curr_tok != Lexer::RP) return Error::error(") szksges"); Lexer::get_token(); // ')' lenyelse return e; } case Lexer::END: return 1; default: return Error::error("elemi szimblum szksges"); }

A Lexer minsts ismtelgetse igen fraszt, de ki lehet kszblni egy using deklarcival, amellyel egy adott helyen kijelentjk, hogy az ebben a hatkrben hasznlt get_token a Lexer get_token-je:
double Parser::prim(bool get) { using Lexer::get_token; using Lexer::curr_tok; using Error::error; // elemi szimblumok kezelse // a Lexer get_token-jnek hasznlata // a Lexer curr_tok-jnak hasznlata // az Error error-jnak hasznlata

Forrs: http://www.doksi.hu

228

Alapok

if (get) get_token(); switch (curr_tok) { case Lexer::NUMBER: // lebegpontos konstans get_token(); return Lexer::number_value; case Lexer::NAME: { double& v = table[Lexer::string_value]; if (get_token() == Lexer::ASSIGN) v = expr(true); return v; } case Lexer::MINUS: // mnusz eljel return -prim(true); case Lexer::LP: { double e = expr(true); if (curr_tok != Lexer::RP) return error(") szksges"); get_token(); // ')' lenyelse return e; } case Lexer::END: return 1; default: return error("elemi szimblum szksges"); }

A using direktva egy loklis szinonmt vezet be. A loklis szinonmkat ltalban clszer a lehet legszkebb hatkrrel hasznlni, hogy elkerljk a tvedseket. A mi esetnkben azonban az elemz minden fggvnye ugyanazokat a neveket hasznlja a tbbi modulbl, gy a using deklarcikat elhelyezhetjk a Parser nvtr meghatrozsban is:
namespace Parser { double prim(bool); double term(bool); double expr(bool); using Lexer::get_token; using Lexer::curr_tok; using Error::error; // a Lexer get_token-jnek hasznlata // a Lexer curr_tok-jnak hasznlata // az Error error-jnak hasznlata

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

229

gy a Parser fggvnyeit majdnem az eredeti vltozatukhoz (6.1.1) hasonlra egyszersthetjk:


double Parser::term(bool get) { double left = prim(get); for (;;) // szorzs s oszts

switch (curr_tok) { case Lexer::MUL: left *= prim(true); break; case Lexer::DIV: if (double d = prim(true)) { left /= d; break; } return error("oszts 0-val"); default: return left; }

Azt is megtehetnnk, hogy a lexikai szimblumok (token, nyelvi egysg) neveit a Parser nvtrbe is bevezetjk. Azrt hagyjuk ket minstett alakban, hogy emlkeztessenek, a Parser a Lexer-re tmaszkodik.

8.2.3. Using direktvk


Mit tehetnk, ha clunk az, hogy a Parser fggvnyeit annyira leegyszerstsk, hogy pontosan olyanok legyenek, mint eredeti vltozataik? Egy nagy program esetben sszernek tnik, hogy egy elz, kevsb modulris vltozatt nvtereket hasznlva alaktsuk t. A using direktva majdnem ugyangy teszi elrhetv egy nvtr neveit, mintha azokat a nvterkn kvl vezettk volna be (8.2.8):
namespace Parser { double prim(bool); double term(bool); double expr(bool); using namespace Lexer; using namespace Error; // a Lexer sszes nevt elrhetv teszi // az Error sszes nevt elrhetv teszi

Forrs: http://www.doksi.hu

230

Alapok

Ez lehetv teszi szmunkra, hogy a Parser fggvnyeit pontosan gy rjuk meg, ahogy azt eredetileg tettk (6.1.1):
double Parser::term(bool get) { double left = prim(get); for (;;) // szorzs s oszts

switch (curr_tok) { // a Lexer-beli curr_tok case MUL: // a Lexer-beli MUL left *= prim(true); break; case DIV: // a Lexer-beli DIV if (double d = prim(true)) { left /= d; break; } return error("oszts 0-val"); // az Error-beli error default: return left; }

A using direktvk a nvterekben ms nvterek beptsre hasznlhatk (8.2.8), fggvnyekben jellsbeli segtsgknt vehetk biztonsgosan ignybe (8.3.3.1). A globlis using direktvk a nyelv rgebbi vltozatairl val tllsra szolglnak (8.2.9), egybknt jobb, ha kerljk ket.

8.2.4. Tbb fellet hasznlata


Vilgos, hogy a Parser szmra ltrehozott nvtr nem a fellet, amit a Parser a felhasznl programelem szmra nyjt. Inkbb olyan deklarcihalmaznak tekinthetjk, ami az egyes elemz fggvnyek knyelmes megrshoz szksges. A Parser fellete a felhasznl elemek szmra sokkal egyszerbb kellene, hogy legyen:
namespace Parser { double expr(bool); }

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

231

Szerencsre a kt nvtr-meghatrozs egyttesen ltezhet, gy mindkett felhasznlhat ott, ahol az a legmegfelelbb. Lthatjuk, hogy a Parser nvtr kt dolgot nyjt: [1] Kzs krnyezetet az elemzt megvalst fggvnyek szmra [2] Kls felletet, amit az elemz a felhasznl programelem rendelkezsre bocst Ennek rtelmben a main() vezrlkd csak a kvetkezt kell, hogy lssa:
namespace Parser { double expr(bool); } // felhasznli fellet

Brmelyik felletet is talltuk a legjobbnak az elemz fggvnyek kzs krnyezetnek brzolsra, a fggvnyeknek ltniuk kell azt:
namespace Parser { double prim(bool); double term(bool); double expr(bool); using Lexer::get_token; using Lexer::curr_tok; using Error::error; // fellet a megvalstshoz

// a Lexer get_token-jnek hasznlata // a Lexer curr_tok-jnak hasznlata // az Error error-jnak hasznlata

brval:

Parser'

Parser

Driver

Parser megvalsts

A nyilak a ltal nyjtott felleten alapul viszonyokat fejezik ki.

Forrs: http://www.doksi.hu

232

Alapok

A Parser (Parser prime) a felhasznl programelemek szmra nyjtott szk fellet; nem C++ azonost. Szndkosan vlasztottam, hogy jelljem, ennek a felletnek nincs kln neve a programban. A kln nevek hinya nem okozhat zavart, mert a programozk az egyes felletek szmra klnbz s maguktl rtetd neveket tallnak ki, s mert a program fizikai elrendezse (lsd 9.3-at) termszetesen klnbz (fjl)neveket ad. A programozi fellet nagyobb a felhasznlknak nyjtottnl. Ha ez a fellet egy valdi rendszer valsgos mret moduljnak fellete lenne, sokkal gyakrabban vltozna, mint a felhasznlk ltal lthat fellet. Fontos, hogy a modulokat hasznl fggvnyeket (ebben az esetben a Parser-t hasznl main()-t) elklntsk az ilyen mdostsoktl. A kt fellet brzolsra nem kell nll nvtereket hasznlnunk, de ha akarnnk, megtehetnnk. A felletek megtervezse az egyik legalapvetbb tevkenysg, de ktl fegyver. Kvetkezskppen rdemes vgiggondolni, valjban mit prblunk megvalstani, s tbb megoldst is kiprblni. Az itt bemutatott megolds az ltalunk megtekintettek kzl a legegyszerbb s gyakran a legjobb. Legfbb gyengje, hogy a kt fellet neve nem klnbzik, valamint hogy a fordtprogram szmra nem ll rendelkezsre elegend informci, hogy ellenrizze a nvtr kt definicijnak kvetkezetessgt. A fordtprogram azonban rendszerint akkor is megprblja ellenrizni az sszefggseket, ha erre nincs mindig lehetsge, a szerkesztprogram pedig szreveszi a legtbb olyan hibt, amin a fordtprogram tsiklott. Az itt bemutatott megoldst hasznlom a fizikai modularits (9.3) trgyalsra is, s ezt ajnlom arra az esetre is, amikor nincsenek tovbbi logikai megszortsok (lsd mg 8.2.7-et). 8.2.4.1. Fellettervezsi mdszerek A felletek clja az, hogy a lehetsges mrtkig cskkentsk a programok klnbz rszei kztt fennll fggsgeket. A kisebb fellet knnyebben rthet rendszerhez vezet, melynek adatrejtsi tulajdonsgai jobbak, knnyebben mdosthat s gyorsabban lefordthat. Amikor a fggsgeket nzzk, fontos emlkeznnk arra, hogy a fordtprogramok s a programozk az albbi egyszer hozzllssal viszonyulnak hozzjuk: ha egy definci az X pontrl lthat (a hatkrben van), akkor brmi, ami az X pontban van lerva, brmitl fgghet, ami abban a definciban lett meghatrozva. Persze a helyzet ltalban nem ennyire rossz, mert a legtbb definci a legtbb kd szmra nem br jelentsggel. Korbbi definciinkat adottnak vve vegyk a kvetkezt:

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

233

namespace Parser { // ... double expr(bool); // ... } int main() { // ... Parser::expr(false); // ... }

// fellet a megvalstshoz

A main() fggvny csak a Parser::expr() fggvnytl fgg, de idre, gondolkodsra, szmolgatsra stb. van szksg ahhoz, hogy erre rjjjnk. Kvetkezskppen a valsgos mret programok esetben a programozk s a fordtsi rendszerek tbbnyire biztosra mennek s felttelezik, hogy ahol elfordulhat fggsg, ott el is fordul, ami teljesen sszer megkzelts. Clunk ezrt az, hogy gy fejezzk ki programunkat, hogy a lehetsges fggsgek halmazt a valban rvnyben lev fggsgek halmazra szktjk. Elszr megprbljuk a magtl rtetdt: a mr meglv megvalstsi fellet segtsgvel az elemz szmra felhasznli felletet hatrozunk meg:
namespace Parser { // ... double expr(bool); // ... } namespace Parser_interface { using Parser::expr; } // fellet a megvalstshoz

// fellet a felhasznlknak

Nyilvnval, hogy a Parser_interface-t hasznl programelemek kizrlag s csupn kzvetett mdon a Parser::expr() fggvnytl fggnek. Mgis, ha egy pillantst vetnk a fggsgek brjra, a kvetkezt ltjuk:

Forrs: http://www.doksi.hu

234

Alapok

Parser

Parser_interface

Driver

Parser megvalsts

Most a Driver (a vezrl) tnik sebezhetnek a Parser fellet vltozsaival szemben, pedig azt hittk, jl elszigeteltk tle. Mg a fggsg ilyen megjelense sem kvnatos, gy megszortjuk a Parser_interface fggsgt a Parser-tl, gy, hogy a megvalstsi felletnek csak az elemz szmra lnyeges rszt (ezt korbban Parser'-nek neveztk) tesszk lthatv ott, ahol a Parser_interface-t meghatrozzuk:
namespace Parser { double expr(bool); } namespace Parser_interface { using Parser::expr; } // fellet a felhasznlknak

// eltr nev fellet a felhasznlknak

brval:

Parser'

Parser

Parser_interface

Driver

Parser megvalsts

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

235

A Parser s a Parser' egysgessgt biztostand, az egyetlen fordtsi egysgen dolgoz fordtprogram helyett ismt a fordtsi rendszer egszre tmaszkodunk. Ez a megolds csak abban klnbzik a 8.2.4-ben szerepltl, hogy kiegszl a Parser_interface nvtrrel. Ha akarnnk, a Parser_interface-t egy sajt expr() fggvnnyel konkrtan is brzolhatnnk:
namespace Parser_interface { double expr(bool); }

Most a Parser-nek nem kell a hatkrben lennie, hogy meghatrozhassuk a Parser_interface-t. Csak ott kell lthatnak lennie, ahol a Parser_interface::expr() fggvnyt kifejtjk:
double Parser_interface::expr(bool get) { return Parser::expr(get); }

Az utbbi vltozatot brval gy szemlltethetjk:

Parser_interface

Parser

Parser_interface megvalsts

Driver

Parser megvalsts

A fggsgeket ezzel a lehet legkevesebbre cskkentettnk. Mindent kifejtettnk s megfelelen elneveztnk. Mgis, ezt a megoldst a legtbb esetben tlznak tallhatjuk.

Forrs: http://www.doksi.hu

236

Alapok

8.2.5. A nvtkzsek elkerlse


A nvterek logikai szerkezetek kifejezsre valk. A legegyszerbb eset, amikor kt szemly ltal rt kdot kell megklnbztetnnk. Ez gyakran fontos gyakorlati jelentsggel br. Ha csak egyetlen globlis hatkrt hasznlunk, igen nehz lesz a programot klnll rszekbl ltrehozni. Az a problma merlhet fel, hogy az nllnak felttelezett rszek mindegyike ugyanazokat a neveket hasznlja, gy amikor egyetlen programban egyestjk azokat, a nevek tkzni fognak. Vegyk a kvetkezt:
// my.h: char f(char); int f(int); class String { /* ... */ }; // your.h: char f(char); double f(double); class String { /* ... */ };

Ha a fentieket meghatrozzuk, egy harmadik szemly csak nehezen hasznlhatja egyszerre a my.h-t s a your.h-t is. A kzenfekv megolds, hogy mindkt deklarcihalmazt sajt, kln nvtrbe helyezzk:
namespace My { char f(char); int f(int); class String { /* ... */ }; } namespace Your { char f(char); double f(double); class String { /* ... */ }; }

Most mr alkalmazhatjuk a My s a Your deklarciit, ha minstket (8.2.1), using deklarcikat (8.2.2) vagy using direktvkat (8.2.3) hasznlunk. 8.2.5.1. Nvtelen nvterek Gyakran hasznos deklarcik halmazt nvtrbe helyezni, pusztn azrt, hogy vdekezznk a lehetsges nvtkzsekkel szemben. A clunk az, hogy a kd helyileg maradjon rvnyes, nem pedig az, hogy felletet nyjtsunk a felhasznlknak:

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

237

#include "header.h" namespace Mine { int a; void f() { /* ... */ } int g() { /* ... */ } }

Mivel nem akarjuk, hogy a Mine nv ismert legyen az adott krnyezeten kvl is, nem rdemes olyan felesleges globlis nevet kitallni, amely vletlenl tkzhet valaki ms neveivel. Ilyen esetben a nvteret nvtelenl hagyhatjuk:
#include "header.h" namespace { int a; void f() { /* ... */ } int g() { /* ... */ } }

Vilgos, hogy kell lennie valamilyen mdszernek arra is, hogy kvlrl frhessnk hozz egy nvtelen nvtr (unnamed namespace) tagjaihoz. A nvtelen nvtrhez tartozik egy rejtett using direktva is. Az elz deklarci egyenrtk a kvetkezvel:
namespace $$$ { int a; void f() { /* ... */ } int g() { /* ... */ } } using namespace $$$;

Itt $$$ valamilyen nv, amely egyedi abban a hatkrben, ahol a nvteret meghatroztuk. A klnbz fordtsi egysgekben lv nvtelen nvterek mindig klnbzek. Ahogy azt szerettk volna, nincs md arra, hogy egy nvtelen nvtr egy tagjt egy msik fordtsi egysgbl megnevezhessk.

8.2.6. Nevek keresse


Egy T tpus paramterrel rendelkez fggvnyt ltalban a T-vel azonos nvtrben szoks megadni. Kvetkezskppen ha egy fggvnyt nem tallunk meg hasznlati krnyezetben, akkor paramtereinek nvterben fogjuk keresni:
namespace Chrono { class Date { /* ... */ };

Forrs: http://www.doksi.hu

238

Alapok

bool operator==(const Date&, const std::string&); std::string format(const Date&); // ... // string brzols

void f(Chrono::Date d, int i) { std::string s = format(d); std::string t = format(i); }

// Chrono::format() // hiba: a hatkrben nincs format()

Ez a keressi szably a minstk hasznlatval ellenttben sok gpelstl kmli meg a programozt, s nem is szennyezi gy a nvteret, mint a using direktva (8.2.3). Alkalmazsa klnsen fontos az opertorok operandusai (11.2.4) s a sablonparamterek (C.13.8.4) esetben, ahol a minstk hasznlata nagyon fraszt lehet. Vegyk szre, hogy maga a nvtr a hatkrben kell, hogy legyen, a fggvnyt pedig csak akkor tallhatjuk meg s hasznlhatjuk fel, ha elbb bevezettk. Termszetesen egy fggvny tbb nvtrbl is kaphat paramtereket:
void f(Chrono::Date d, std::string s) { if (d == s) { // ... } else if (d == "1914 augusztus 4") { // ... } }

Az ilyen esetekben a fggvnyt a fordtprogram a szoksos mdon, a hvs hatkrben, illetve az egyes paramterek nvterben (belertve a paramterek osztlyt s alaposztlyt is) keresi, s minden tallt fggvnyre elvgzi a tlterhels feloldst (7.4). Nevezetesen, a fordt a d==s hvsnl az operator==-t az f()-et krlvev hatkrben, az (==-t stringekre meghatroz) std nvtrben, s a Chrono nvtrben keresi. Ltezik egy std::operator==(), de ennek nincs Date paramtere, ezrt a Chrono::operator==()-t hasznlja, amelynek viszont van. Lsd mg 11.2.4-et.

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

239

Amikor egy osztlytag meghv egy nvvel rendelkez fggvnyt, az osztly s bzisosztlynak tagjai elnyben rszeslnek azokkal a fggvnyekkel szemben, melyeket a fordtprogram a paramterek tpusa alapjn tallt. Az opertoroknl ms a helyzet (11.2.1, 11.2.4).

8.2.7. Nvtr-lnevek
Ha a felhasznlk nvtereiknek rvid neveket adnak, a klnbz nvterek nevei knynyebben tkzhetnek:
namespace A { // ... } // rvid nv, (elbb-utbb) tkzni fog

A::String s1 = "Grieg"; A::String s2 = "Nielsen";

Valdi kdban viszont ltalban nem clszer hossz nvtrneveket hasznlni:


namespace American_Telephone_and_Telegraph { // ... } // tl hossz

American_Telephone_and_Telegraph::String s3 = "Grieg"; American_Telephone_and_Telegraph::String s4 = "Nielsen";

A dilemmt gy oldhatjuk fel, ha a hosszabb nvtrneveknek rvid lneveket (alias) adunk:


// hasznljunk nvtr-lneveket a nevek rvidtsre: namespace ATT = American_Telephone_and_Telegraph; ATT::String s3 = "Grieg"; ATT::String s4 = "Nielsen";

A nvtr-lnevek azt is lehetv teszik a felhasznlnak, hogy a knyvtrra hivatkozzon s egyetlen deklarciban hatrozza meg, valjban melyik knyvtrra gondol:
namespace Lib = Foundation_library_v2r11; // ... Lib::set s; Lib::String s5 = "Sibelius";

Forrs: http://www.doksi.hu

240

Alapok

Ez nagymrtkben egyszerstheti a knyvtrak msik vltozatra trtn cserjt. Azltal, hogy kzvetlenl Lib-et hasznlunk a Foundation_library_v2r11 helyett, a Lib lnv rtknek mdostsval s a program jrafordtsval a v3r02 vltozatra frissthetjk a knyvtrat. Az jrafordts szre fogja venni a forrsszint sszefrhetetlensgeket. Msrszrl, a (brmilyen tpus) lnevek tlzott hasznlata zavart is okozhat.

8.2.8. Nvterek sszefzse


Egy felletet gyakran mr ltez felletekbl akarunk ltrehozni:
namespace His_string { class String { /* ... */ }; String operator+(const String&, const String&); String operator+(const String&, const char*); void fill(char); // ... } namespace Her_vector { template<class T> class Vector { /* ... */ }; // ... } namespace My_lib { using namespace His_string; using namespace Her_vector; void my_fct(String&); }

Ennek alapjn a My_lib nvteret hasznlva mr megrhatjuk a programot:


void f() { My_lib::String s = "Byron"; // ... } using namespace My_lib; void g(Vector<String>& vs) { // ... my_fct(vs[5]); // ... }

// megtallja a My_lib::His_string::String nevet

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

241

Ha az emltett nvtrben egy explicit mdon minstett nv (mint a My_lib::String) nem bevezetett, a fordt a nevet a using direktvkban szerepl nvterekben (pldul His_string) fogja keresni. Egy elem valdi nvtert csak akkor kell tudnunk, ha valamit megakarunk hatrozni:
void My_lib::fill(char c) { // ... } void His_string::fill(char c) { // ... } void My_lib::my_fct(String& v) { } // ... // hiba: a My_lib-ben nincs megadva fill()

// rendben: fill() szerepel a His_string-ben

// rendben; a String jelentse My_lib::String, ami // His_string::String

Idelis esetben egy nvtr 1. logikailag sszetartoz szolgltatsok halmazt fejezi ki, 2. nem ad hozzfrst a nem kapcsold szolgltatsokhoz, 3. s nem r nagy jellsbeli terhet a felhasznlra. Az itt s a kvetkez rszekben bemutatott sszefzsi, beptsi mdszerek az #includedal (9.2.1) egytt komoly tmogatst nyjtanak ehhez. 8.2.8.1. Kivlaszts Alkalmanknt elfordul, hogy egy nvtrbl csak nhny nvhez akarunk hozzfrni. Ezt meg tudnnk tenni gy is, hogy olyan nvtr-deklarcit runk, amely csak azokat a neveket tartalmazza, melyeket szeretnnk. Pldul megadhatnnk a His_string azon vltozatt, amely csak magt a String-et s az sszefz opertort nyjtja:
namespace His_string { // csak egy rsze a His_string-nek class String { /* ... */ }; String operator+(const String&, const String&); String operator+(const String&, const char*); }

Forrs: http://www.doksi.hu

242

Alapok

Ez azonban knnyen zavaross vlhat, hacsak nem mi vagyunk a His_string tervezi vagy karbantarti. A His_string valdi meghatrozsnak mdostsa ebben a deklarciban nem fog tkrzdni. Az adott nvtrben szerepl szolgltatsok kivlasztst jobban ki lehet fejezni using deklarcikkal:
namespace My_string { using His_string::String; using His_string::operator+; }

// brmelyik His_string-beli + hasznlhat

A using deklarci az adott nv minden deklarcijt a hatkrbe helyezi, gy pldul egyetlen using deklarcival egy tlterhelt fggvny sszes vltozatt bevezethetjk. gy ha a His_string-et gy mdostjk, hogy egy tagfggvnyt vagy az sszefz mvelet egy tlterhelt vltozatt adjk a String-hez, akkor ez a vltoztats automatikusan hozzfrhet lesz a My_string-et hasznl elemek szmra. Fordtva is igaz: ha a His_string-bl eltvoltunk egy szolgltatst vagy megvltozatjuk a His_string fellett, a fordtprogram fel fogja ismerni a My_string minden olyan hasznlatt, amelyre ez hatssal van (lsd mg 15.2.2). 8.2.8.2. sszefzs s kivlaszts A (using direktvkkal trtn) sszefzs s a (using deklarcikkal trtn) kivlaszts sszekapcsolsa azt a rugalmassgot eredmnyezi, amelyre a legtbb valdi programban szksgnk van. Ezek rvn gy adhatunk hozzfrst klnfle eszkzkhz, hogy feloldjuk az egybeptskbl add nvtkzseket s tbbrtelmsgeket:
namespace His_lib { class String { /* ... */ }; template<class T> class Vector { /* ... */ }; // ... } namespace Her_lib { template<class T> class Vector { /* ... */ }; class String { /* ... */ }; // ... } namespace My_lib { using namespace His_lib; using namespace Her_lib; // minden a His_lib-bl // minden a Her_lib-bl

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

243

using His_lib::String; using Her_lib::Vector; template<class T> class List { /* ... */ }; // ...

// az esetleges tkzsek feloldsa a His_lib javra // az esetleges tkzsek feloldsa a Her_lib javra // tovbbiak

Amikor megvizsglunk egy nvteret, a nvtrben lv, kifejezetten megadott nevek (belertve a using deklarcikkal megadottakat is) elnyben rszeslnek azokkal a nevekkel szemben, melyeket ms hatkrkbl tettnk hozzfrhetv a using direktvval (lsd mg C.10.1-et). Kvetkezskppen a My_libet hasznl elemek szmra a String s Vector nevek tkzst a fordtprogram a His_lib::String s Her_lib::Vector javra fogja feloldani. Tovbb a My_lib::List lesz hasznlatos alaprtelmezs szerint, fggetlenl attl, hogy szerepel-e List a His_lib vagy Her_lib nvtrben. Rendszerint jobban szeretem vltozatlanul hagyni a neveket, amikor j nvtrbe teszem azokat. Ily mdon nem kell ugyanannak az elemnek kt klnbz nevre emlkeznem. Nha azonban j nvre van szksg, vagy egyszeren j, ha van egy j nevnk:
namespace Lib2 { using namespace His_lib; using namespace Her_lib; using His_lib::String; using Her_lib::Vector; typedef Her_lib::String Her_string; // minden a His_lib-bl // minden a Her_lib-bl // az esetleges tkzsek feloldsa a His_lib javra // az esetleges tkzsek feloldsa a Her_lib javra // tnevezs

template<class T> class His_vec // "tnevezs" : public His_lib::Vector<T> { /* ... */ }; template<class T> class List { /* ... */ }; // tovbbiak // ...

Az tnevezsre nincs kln nyelvi eljrs. Ehelyett az j elemek meghatrozsra val ltalnos mdszerek hasznlatosak.

8.2.9. Nvterek s rgi kdok


Sok milli sor C s C++ kd tmaszkodik globlis nevekre s ltez knyvtrakra. Hogyan hasznlhatjuk a nvtereket arra, hogy cskkentsk az ilyen kdokban lv problmkat? A mr ltez kdok jrarsa nem mindig jrhat t. Szerencsre a C knyvtrakat gy is

Forrs: http://www.doksi.hu

244

Alapok

hasznlhatjuk, mintha azokat egy nvtrben deklarltk volna. A C++-ban rt knyvtrak esetben ez nem gy van (9.2.4), msrszrl viszont a nvtereket gy terveztk, hogy a lehet legcseklyebb krokozssal be lehessen azokat pteni a rgebbi C++ programokba is. 8.2.9.1. Nvterek s a C Vegyk a hagyomnyosan els C programot:
#include <stdio.h> int main() { printf("Hell, vilg!\n"); }

Ezt a programot nem lenne j tlet szttrdelni. Az sem sszer, ha a szabvnyos knyvtrakat egyedi megoldsoknak tekintjk. Emiatt a nvterekre vonatkoz nyelvi szablyokat gy hatroztk meg, hogy viszonylag knnyedn lehessen egy nvterek nlkl megrt program szerkezett nvterek hasznlatval vilgosabban kifejezni. Tulajdonkppen erre plda a szmolgp program (6.1). Ennek megvalstshoz a kulcs a using direktva. A stdio.h C fejllomnyban lv szabvnyos bemeneti/kimeneti szolgltatsok deklarcii pldul egy nvtrbe kerltek, a kvetkezkppen:
// stdio.h: namespace std { // ... int printf(const char* ... ); // ... } using namespace std;

Ez megrzi a visszirny kompatibilitst. Azoknak viszont, akik nem akarjk, hogy a nevek automatikusan hozzfrhetk legyenek, ksztettnk egy j fejllomnyt is, a cstdio-t:
// cstdio: namespace std { // ... int printf(const char* ... ); // ... }

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

245

A C++ standard knyvtrnak azon felhasznli, akik aggdnak a deklarcik msolsa miatt, a stdio.h-t termszetesen gy fogjk meghatrozni, hogy beleveszik a cstdio-t:
// stdio.h: #include<cstdio> using namespace std;

A using direktvkat elsdlegesen a nyelv rgebbi vltozatairl val tllst segt eszkzknek tekintem. A legtbb olyan kdot, amely ms nvtrben lv nevekre hivatkozik, sokkal vilgosabban ki lehet fejezni minstsekkel s using deklarcikkal. A nvterek s az sszeszerkeszts kztti kapcsolatot a 9.2.4 rszben trgyaljuk. 8.2.9.2. Nvterek s tlterhels A tlterhels (7.4) nvtereken keresztl mkdik. Ez alapvet ahhoz, hogy a mr meglv knyvtrakat a forrskd lehet legkisebb mdostsval fejleszthessk nvtereket hasznlv. Pldul:
// old A.h: void f(int); // ... // old B.h: void f(char); // ... // old user.c: #include "A.h" #include "B.h" void g() { }

f('a');

// f()-et hvja B.h-bl

Ezt a programot anlkl alakthatjuk nvtereket hasznl vltozatra, hogy a tnyleges programkdot megvltoztatnnk:

Forrs: http://www.doksi.hu

246

Alapok

// new A.h: namespace A { void f(int); // ... } // new B.h: namespace B { void f(char); // ... } // new user.c: #include "A.h" #include "B.h" using namespace A; using namespace B; void g() { }

f('a');

// f()-et hvja B.h-bl

Ha teljesen vltozatlanul akartuk volna hagyni a user.c-t, a using direktvkat a fejllomnyokba tettk volna. 8.2.9.3. A nvterek nyitottak A nvterek nyitottak; azaz szmos nvtr deklarcijbl adhatunk hozzjuk neveket:
namespace A { int f(); } namespace A { int g(); } // most f() az A tagja

// most A kt tagja f() s g()

Ezltal gy hozhatunk ltre egyetlen nvtren bell lv nagy programrszeket, ahogy egy rgebbi knyvtr vagy alkalmazs lt az egyetlen globlis nvtren bell. Hogy ezt megtehessk, a nvtr-meghatrozsokat szt kell osztanunk szmos fejllomny s forrsfjl k-

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

247

ztt. Ahogy azt a szmolgp pldjban (8.2.4.) mutattuk, a nvterek nyitottsga lehetv teszi szmunkra, hogy a klnbz programelemeknek klnbz felleteket nyjtsunk azltal, hogy egy adott nvtr klnbz rszeit mutatjuk meg nekik. Ez a nyitottsg szintn a nyelv rgebbi vltozatairl val tllst segti. Pldul a
// sajt fejllomny: void f(); // sajt fggvny // ... #include<stdio.h> int g(); // sajt fggvny // ...

jrarhat anlkl, hogy a deklarcik sorrendjt megvltoztatnnk:


// sajt fejllomny: namespace Mine { void f(); // sajt fggvny // ... } #include<stdio.h> namespace Mine { int g(); // sajt fggvny // ... }

Amikor j kdot rok, jobban szeretek sok kisebb nvteret hasznlni (lsd 8.2.8), mint igazn nagy programrszeket egyetlen nvtrbe rakni. Ez azonban gyakran kivitelezhetetlen, ha nagyobb programrszeket alaktunk t nvtereket hasznl vltozatra. Amikor egy nvtr elzetesen bevezetett tagjt kifejtjk, biztonsgosabb a Mine:: utastsformt hasznlni ahelyett, hogy jra megnyitnnk a Mine-t:
void Mine::ff() { // ... } // hiba: nincs ff() megadva Mine-ban

A fordtprogram ezt a hibt szreveszi. Mivel azonban egy nvtren bell j fggvnyeket is meghatrozhatunk, a fordtprogram a fentivel azonos jelleg hibt az jra megnyitott nvterekben mr nem rzkeli:

Forrs: http://www.doksi.hu

248

Alapok

namespace Mine { // Mine jra megnyitsa fggvnyek meghatrozshoz void ff() { } } // ... // hopp! nincs ff() megadva Mine-ban; ezzel a defincival adjuk hozz // ...

A fordtprogram nem tudhatja, hogy nem egy j ff() fggvnyt akartunk meghatrozni. A meghatrozsokban szerepl nevek minstsre hasznlhatunk nvtr-lneveket (8.2.7), de az adott nvtr jbli megnyitsra nem.

8.3. Kivtelek
Ha egy program klnll modulokbl ll klnsen ha ezek kln fejlesztett knyvtrakbl szrmaznak , a hibakezelst kt klnll rszre kell sztvlasztanunk: 1. az olyan hibaesemnyek jelzsre, melyeket nem lehet helyben megszntetni, 2. illetve a mshol szlelt hibk kezelsre. A knyvtr ltrehozja felismerheti a futsi idej hibkat, de ltalban nem tud mit kezdeni velk. A knyvtrt felhasznl programelem tudhatn, hogyan birkzzon meg a hibkkal, de nem kpes szlelni azokat msklnben a felhasznl kdjban szerepelnnek a hibkat kezel eljrsok s nem a knyvtr talln meg azokat. A szmolgp pldjban ezt a problmt azzal kerltk ki, hogy a program egszt egyszerre terveztk meg, ezltal beilleszthettk a hibakezelst a teljes szerkezetbe. Amikor azonban a szmolgp logikai rszeit klnbz nvterekre bontjuk szt, ltjuk, hogy minden nvtr fgg az Error nvtrtl (8.2.2), az Error-ban lv hibakezel pedig arra tmaszkodik, hogy minden modul megfelelen viselkedik, miutn hiba trtnt. Tegyk fel, hogy nincs lehetsgnk a szmolgp egszt megtervezni s nem akarjuk, hogy az Error s a tbbi modul kztt szoros legyen a kapcsolat. Ehelyett tegyk fel, hogy a elemzt s a tbbi rszt gy rtk meg, hogy nem tudtk, hogyan szeretn a vezrl kezelni a hibkat.

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

249

Br az error() nagyon egyszer volt, magban foglalt egy hibakezelsi mdszert:


namespace Error { int no_of_errors; double error(const char* s) { std::cerr << "hiba: " << s << '\n'; no_of_errors++; return 1; }

Az error() fggvny egy hibazenetet r ki, olyan alaprtelmezett rtket ad, mely lehetv teszi a hv szmra, hogy folytassa a szmolst, s egy egyszer hiballapotot kvet nyomon. Fontos, hogy a program minden rsze tudjon az error() ltezsrl s arrl, hogyan lehet meghvni, illetve mit vrhat tle. Ez tl sok felttel lenne egy olyan program esetben, amit kln fejlesztett knyvtrakbl hoztunk ltre. A hibajelzs s a hibakezels sztvlasztsra sznt C++ eszkz a kivtel. Ebben a rszben rviden lerjuk a kivteleket, abban a krnyezetben, ahogy a szmolgp pldjban lennnek hasznlatosak. A 14. fejezet tfogbban trgyalja a kivteleket s azok hasznlatt.

8.3.1. Dobs s elkaps


A kivteleket (exception) arra talltk ki, hogy segtsenek megoldani a hibk jelzst:
struct Range_error { int i; Range_error(int ii) { i = ii; } };

// konstruktor (2.5.2, 10.2.3)

char to_char(int i) { if (i<numeric_limits<char>::min() || numeric_limits<char>::max()<i) throw Range_error(i); return i; }

// lsd 22.2

A to_char() fggvny vagy az i szmrtkt adja vissza karakterknt, vagy Range_error kivtelt vlt ki. Az alapgondolat az, hogy ha egy fggvny olyan problmt tall, amellyel nem kpes megbirkzni, kivtelt vlt ki (kivtelt dob, throw), azt remlve, hogy (kzvetett vagy kzvetlen) meghvja kpes kezelni a problmt. Ha egy fggvny kpes erre, je-

Forrs: http://www.doksi.hu

250

Alapok

lezheti, hogy el akarja kapni (catch) azokat a kivteleket, melyek tpusa megegyezik a problma jelzsre hasznlt tpussal. Ahhoz pldul, hogy meghvjuk a to_char()-t s elkapjuk azt a kivtelt, amit esetleg kivlthat, a kvetkezt rhatjuk:
void g(int i) { try { char c = to_char(i); // ... } catch (Range_error) { cerr << "hopp\n"; } }

A
catch ( /* ... */ ) { // ... }

szerkezetet kivtelkezelnek (exception handler) nevezzk. Csak kzvetlenl olyan blokk utn hasznlhat, amit a try kulcssz elz meg, vagy kzvetlenl egy msik kivtelkezel utn. A catch szintn kulcssz. A zrjelek olyan deklarcit tartalmaznak, amely a fggvnyparamterek deklarcijhoz hasonl mdon hasznlatos. A deklarci hatrozza meg azon objektum tpust, melyet a kezel elkaphat. Nem ktelez, de megnevezheti az elkapott objektumot is. Ha pldul meg akarjuk tudni a kivltott Range_error rtkt, akkor pontosan gy adhatunk nevet a catch paramternek, ahogy a fggvnyparamtereket nevezzk meg:
void h(int i) { try { char c = to_char(i); // ... } catch (Range_error x) { cerr << "hopp: to_char(" << x.i << ")\n"; } }

Ha brmilyen try blokkban szerepl vagy onnan meghvott kd kivtelt vlt ki, a try blokk kezelit kell megvizsglni. Ha a kivtel tpusa megegyezik a kezelnek megadott tpussal,

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

251

a kezel vgrehajtja a megfelel mveletet. Ha nem, a kivtelkezelket figyelmen kvl hagyjuk s a try blokk gy viselkedik, mint egy kznsges blokk. Ha a kivtelt nem kapja el egyetlen try blokk sem, a program befejezdik (14.7). A C++ kivtelkezelse alapveten nem ms, mint a vezrls tadsa a hv fggvny megfelel rsznek. Ahol szksges, a hibrl informcit adhatunk a hvnak. A C programozk gy gondolhatnak a kivtelkezelsre, mint egy olyan, jl viselked eljrsra, amely a setjmp/longjmp (16.1.2) hasznlatt vltja fel. Az osztlyok s a kivtelkezels kztti klcsnhatst a 14. fejezetben trgyaljuk.

8.3.2. A kivtelek megklnbztetse


Egy program futsakor ltalban szmos hiba lphet fel, melyeket klnbz nev kivteleknek feleltethetnk meg. n a kivtelkezels cljra kln tpusokat szoktam megadni. Ez a lehet legkisebbre cskkenti a cljukkal kapcsolatos zavart. Beptett tpusokat, mint amilyen az int, viszont sohasem hasznlok kivtelknt. Egy nagy programban nem lenne hatkony md arra, hogy megtalljam a ms clra hasznlt int kivteleket, ezrt sosem lehetnk biztos abban, hogy az int egy effle eltr hasznlata nem okoz-e zavart az n kdomban. Szmolgpnknek (6.1) ktfajta futsi idej hibt kell kezelnie: a formai kvetelmnyek megsrtst s a nullval val oszts ksrlett. A kezelnek nem kell rtket tadni abbl a kdbl, amelyik felismerte a nullval val oszts ksrlett, gy a nullval val osztst egy egyszer res tpussal brzolhatjuk:
struct Zero_divide { };

Msrszt a kezel a nyelvi hibkrl bizonyra szeretne jelzst kapni. Itt egy karakterlncot adunk t:
struct Syntax_error { const char* p; Syntax_error(const char* q) { p = q; } };

A knyelmesebb jells vgett a szerkezethez hozzadtam egy konstruktort (2.5.2, 10.2.3). Az elemzt hasznl programrszben megklnbztethetjk a kt kivtelt, ha mindkettjk szmra hozzadunk egy-egy kezelt a try blokkhoz, gy szksg esetn a megfelel kezelbe lphetnk. Ha az egyik kezel aljn kiesnk, a vgrehajts a kezelk listjnak vgtl folytatdik:

Forrs: http://www.doksi.hu

252

Alapok

try {

} catch (Syntax_error) { // szintaktikus hiba kezelse } catch (Zero_divide) { // nullval oszts kezelse } // akkor jutunk ide, ha expr() nem okozott kivtelt vagy ha egy Syntax_error // vagy Zero_divide kivtelt elkaptunk (s kezeljk nem trt vissza, // nem vltott ki kivtelt, s ms mdon sem vltoztatta meg a vezrlst).

// ... expr(false); // kizrlag akkor jutunk ide, ha expr() nem okozott kivtelt // ...

A kezelk listja nmileg egy switch utastshoz hasonlt, de itt nincs szksg break utastsokra. E listk formai kvetelmnyei rszben ezrt klnbznek a case-tl, rszben pedig azrt, hogy jelljk, minden kezel kln hatkrt (4.9.4) alkot. A fggvnyeknek nem kell az sszes lehetsges kivtelt elkapniuk. Az elz try blokk pldul nem prblta elkapni az elemz bemeneti mveletei ltal kivltott kivteleket, azok csupn keresztlmennek a fggvnyen, megfelel kezelvel rendelkez hvt keresve. A nyelv szempontjbl a kivteleket rgtn kezeltnek tekintjk, amint belpnek a kezeljkbe, ezrt a try blokkot meghv programrsznek kell foglalkoznia azokkal a kivtelekkel, melyek a kezel vgrehajtsa kzben lpnek fel. A kvetkez pldul nem okoz vgtelen ciklust:
class Input_overflow { /* ... */ }; void f() { try {

// ... } catch (Input_overflow) { // ... throw Input_overflow(); }

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

253

A kivtelkezelk egymsba is gyazhatk:


class XXII { /* ... */ }; void f() { // ... try {

// ... } catch (XXII) { try {

} // ...

// valami bonyolult } catch (XXII) { // a bonyolult kezel nem jrt sikerrel }

Ilyen gyakran rossz stlusra utal egymsba gyazott kivtelkezelket azonban ritkn runk.

8.3.3. Kivtelek a szmolgpben


Az alapvet kivtelkezel eljrsokbl kiindulva jrarhatjuk a 6.1 rszben szerepl szmolgpet, hogy klnvlasszuk a futsi idben tallt hibk kezelst a szmolgp f programrsztl. Ez a program olyan elrendezst eredmnyezi, amely jobban hasonlt a klnll, lazn kapcsold rszekbl ltrehozott programokra. Elszr kikszblhetjk az error() fggvnyt. Helyette az elemz fggvnyek csak a hibk jelzsre hasznlatos tpusokrl fognak tudni:
namespace Error { struct Zero_divide { }; struct Syntax_error { const char* p; Syntax_error(const char* q) { p = q; } };

Forrs: http://www.doksi.hu

254

Alapok

Az elemz hrom szintaktikus hibt ismer fel:


Lexer::Token_value Lexer::get_token() { using namespace std; // az input, isalpha(), stb. hasznlata miatt (6.1.7) // ... default: // NAME, NAME =, vagy hiba if (isalpha(ch)) { input->putback(ch); *input >> string_value; return curr_tok=NAME; string_value = ch; while (input->get(ch) && isalnum(ch)) string_value.push_back(ch); input->putback(ch); return curr_tok=NAME; } throw Error::Syntax_error("rossz szimblum");

double Parser::prim(bool get) { // ...

// elemi szimblumok kezelse

case Lexer::LP: { double e = expr(true); if (curr_tok != Lexer::RP) throw Error::Syntax_error("')' szksges"); get_token(); // ')' lenyelse return e; } case Lexer::END: return 1; default: throw Error::Syntax_error("elemi szimblum szksges"); }

Ha az elemz ilyen hibt tall, a throw-t hasznlja arra, hogy tadja a vezrlst egy kezelnek, amelyet valamilyen (kzvetett vagy kzvetlen) hv fggvny hatroz meg. A throw opertor egy rtket is tad a kezelnek. Pldul a
throw Syntax_error("elemi szimblum szksges");

a kezelnek egy Syntax_error objektumot ad t, amely a primary expected karakterlncra hivatkoz mutatt tartalmazza.

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

255

A nullval val oszts hibjnak jelzshez nem szksges semmilyen adatot tadni:
double Parser::term(bool get) // szorzs s oszts { // ... case Lexer::DIV: if (double d = prim(true)) { left /= d; break; } throw Error::Zero_divide(); } // ...

Most mr elkszthetjk a vezrlt, hogy az kezelje a Zero_divide s Syntax_error kivteleket:


int main(int argc, char* argv[ ]) { // ... while (*input) { try { Lexer::get_token(); if (Lexer::curr_tok == Lexer::END) break; if (Lexer::curr_tok == Lexer::PRINT) continue; cout << Parser::expr(false) << '\n'; } catch(Error::Zero_divide) { cerr << "nullval oszts ksrlete\n"; if (Lexer::curr_tok != Lexer::PRINT) skip(); } catch(Error::Syntax_error e) { cerr << "formai hiba:" << e.p << "\n"; if (Lexer::curr_tok != Lexer::PRINT) skip(); } } if (input != &cin) delete input; return no_of_errors;

Ha nem trtnt hiba a PRINT (azaz sorvge vagy pontosvessz) szimblummal lezrt kifejezs vgn, a main() meghvja a skip() helyrellt fggvnyt. A skip() az elemzt egy meghatrozott llapotba prblja lltani, azltal, hogy eldobja a karaktereket addig, amg sorvgt vagy pontosvesszt nem tall. A skip() fggvny, a no_of_errors s az input kzenfekv vlaszts a Driver nvtr szmra:

Forrs: http://www.doksi.hu

256

Alapok

namespace Driver { int no_of_errors; std::istream* input; void skip(); } void Driver::skip() { no_of_errors++; while (*input) { char ch; input->get(ch); switch (ch) { case '\n': case ';': return; } // karakterek elvetse sortrsig vagy pontosvesszig

A skip() kdjt szndkosan rtuk az elemz kdjnl alacsonyabb elvonatkoztatsi szinten. gy az elemzben lv kivtelek nem kapjk el, mikzben ppen az elemz kivteleinek kezelst vgzik. Megtartottam azt az tletet, hogy megszmoljuk a hibkat, s ez a szm lesz a program visszatrsi rtke. Gyakran hasznos tudni a hibkrl, mg akkor is, ha a program kpes volt helyrellni a hiba utn. A main()-t nem tesszk a Driver nvtrbe. A globlis main() a program indt fggvnye (3.2), gy a main() egy nvtren bell rtelmetlen. Egy valsgos mret programban a main() kdjnak legnagyobb rszt a Driver egy kln fggvnybe tennm t. 8.3.3.1. Ms hibakezel mdszerek Az eredeti hibakezel kd rvidebb s elegnsabb volt, mint a kivteleket hasznl vltozat. Ezt azonban gy rte el, hogy a program rszeit szorosan sszekapcsolta. Ez a megkzelts nem felel meg olyan programok esetben, melyeket kln fejlesztett knyvtrakbl hoztak ltre. Felvetdhet, hogy a klnll skip() hibakezel fggvnyt a main()-ben, egy llapotvltoz bevezetsvel kszbljk ki:
int main(int argc, char* argv[ ]) { // ... bool in_error = false; // rossz stlus

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

257

while (*Driver::input) { try { Lexer::get_token(); if (Lexer::curr_tok == Lexer::END) break; if (Lexer::curr_tok == Lexer::PRINT) { in_error = false; continue; } if (in_error == false) cout << Parser::expr(false) << '\n'; } catch(Error::Zero_divide) { cerr << "nullval oszts ksrlete\n"; ++ Driver::no_of_errors; in_error = true; } catch(Error::Syntax_error e) { cerr << "formai hiba:" << e.p << "\n"; ++ Driver::no_of_errors; in_error = true; } } if (Driver::input != &std::cin) delete Driver::input; return Driver::no_of_errors;

Ezt szmos okbl rossz tletnek tartom: 1. Az llapotvltozk gyakran zavart okoznak s hibk forrsai lehetnek, klnsen akkor, ha lehetsget adunk r, hogy elszaporodjanak s hatsuk nagy programrszekre terjedjen ki. Nevezetesen az in_error-t hasznl main()-t kevsb olvashatnak tartom, mint a skip() fggvnyt hasznl vltozatot. 2. ltalban jobb kln tartani a hibakezelst s a kznsges kdot. 3. Veszlyes, ha a hibakezels elvonatkoztatsi szintje megegyezik annak a kdnak az absztrakcis szintjvel, ami a hibt okozta; a hibakezel kd ugyanis megismtelheti azt a hibt, amely a hibakezelst elszr kivltotta. (A gyakorlatok kztt szerepel, hogy mi trtnik, ha a main() in_error-t hasznl. 8.5[7]). 4. Tbb munkval jr az egsz kdot mdostani a hibakezels hozzadsval, mint kln hibakezel fggvnyeket adni a kdhoz. A kivtelkezels nem helyi problmk megoldsra val. Ha egy hiba helyben kezelhet, akkor majdnem mindig ezt is kell tennnk. Pldul nincs ok arra, hogy kivtelt hasznljunk a tl sok paramter hiba fellpsekor:

Forrs: http://www.doksi.hu

258

Alapok

int main(int argc, char* argv[]) { using namespace std; using namespace Driver; switch (argc) { case 1: // olvass szabvnyos bemenetrl input = &cin; break; case 2: // karakterlnc paramter beolvassa input = new istringstream(argv[1]); break; default: cerr << "tl sok paramter\n"; return 1; } } // mint korbban

A kivtelek tovbbi trgyalsa a 14. fejezetben trtnik.

8.4. Tancsok
[1] Hasznljunk nvtereket a logikai felpts kifejezsre. 8.2. [2] A main() kivtelvel minden nem loklis nevet helyezznk valamilyen nvtrbe. 8.2. [3] A nvtereket gy tervezzk meg, hogy utna knyelmesen hasznlhassuk, anlkl, hogy vletlenl hozzfrhetnnk ms, fggetlen nvterekhez. 8.2.4. [4] Lehetleg ne adjunk a nvtereknek rvid neveket. 8.2.7. [5] Ha szksges, hasznljunk nvtr-lneveket a hossz nvtrnevek rvidtsre. 8.2.7. [6] Lehetleg ne rjunk nehz jellsbeli terheket nvtereink felhasznlira. 8.2.2., 8.2.3. [7] Hasznljuk a Nvtr::tag jellst, amikor a nvtr tagjait meghatrozzuk. 8.2.8. [8] A using namespacet csak a C-rl vagy rgebbi C++-vltozatokrl val tllskor, illetve helyi hatkrben hasznljuk. 8.2.9. [9] Hasznljunk kivteleket arra, hogy a szoksos feldolgozst vgz kdrszt elvlasszuk attl a rsztl, amelyben a hibkkal foglalkozunk. 8.3.2.

Forrs: http://www.doksi.hu

8. Nvterek s kivtelek

259

[10] Inkbb felhasznli tpusokat hasznljunk kivtelekknt, mint beptett tpusokat. 8.3.2. [11] Ne hasznljunk kivteleket, amikor a helyi vezrlsi szerkezetek is megfelelek. 8.3.3.1.

8.5. Gyakorlatok
1. (*2,5) rjunk string elemeket tartalmaz ktirny lncolt lista modult a 2.4-ben tallhat Stack modul stlusban. Prbljuk ki gy, hogy ltrehozunk egy programnyelvekbl ll listt. Adjunk erre listra egy sort() fggvnyt s egy olyat, ami megfordtja a listban szerepl karakterlncok sorrendjt. 2. (*2) Vegynk egy nem tl nagy programot, amely legalbb egy olyan knyvtrat hasznl, ami nem hasznl nvtereket. Mdostsuk gy, hogy a knyvtr nvtereket hasznljon. Tipp: 8.2.9. 3. (*2) Ksztsnk modult a szmolgp programbl nvterek felhasznlsval a 2.4 pont stlusban. Ne hasznljunk globlis using direktvkat. Jegyezzk fel, milyen hibkat vtettnk. Tegynk javaslatokat arra, miknt kerlhetnnk el az ilyen hibkat a jvben. 4. (*1) rjunk programot, amelyben egy fggvny kivtelt dob, egy msik pedig elkapja. 5. (*2) rjunk programot, amely olyan egymst hv fggvnyekbl ll, ahol a hvs mlysge 10. Minden fggvnynek adjunk egy paramtert, amely eldnti, melyik szinten lpett fel a kivtel. A main()-nel kapjuk el a kivteleket s rjuk ki, melyiket kaptuk el. Ne felejtsk el azt az esetet, amikor a kivtelt a kivlt fggvnyben kapjuk el. 6. (*2) Mdostsuk a 8.5[5] programjt gy, hogy megmrjk, van-e klnbsg a kivtelek elkapsnak nehzsgben attl fggen, hogy a stack osztlyon bell hol jtt ltre kivtel. Adjunk minden fggvnyhez egy karakterlnc objektumot s mrjk meg jra a klnbsget. 7. (*1) Talljuk meg a hibt a 8.3.3.1-ben szerepl main() els vltozatban. 8. (*2) rjunk fggvnyt, amely vagy visszaad egy rtket, vagy egy paramter alapjn eldobja azt. Mrjk meg a kt mdszer futsi idejnek klnbsgt. 9. (*2) Mdostsuk a 8.5[3]-ban lv szmolgpet kivtelek hasznlatval. Jegyezzk fel, milyen hibkat vtettnk. Tegynk javaslatokat arra, miknt kerlhetnnk el az ilyen hibkat a jvben. 10. (*2,5) rjuk meg a plus(), minus(), multiply() s divide() fggvnyeket, amelyek ellenrzik a tlcsordulst s az alulcsordulst, s kivteleket vltanak ki, ha ilyen hibk trtnnek. 11. (*2) Mdostsuk a szmolgpet, hogy a 8.5[10] fggvnyeit hasznlja.

Forrs: http://www.doksi.hu

9
Forrsfjlok s programok
A formnak a rendeltetshez kell igazodnia. (Le Corbusier) Kln fordts sszeszerkeszts Fejllomnyok A standard knyvtr fejllomnyai Az egyszeri definils szablya sszeszerkeszts nem C++ kddal Az sszeszerkeszts s a fggvnyekre hivatkoz mutatk Fejllomnyok hasznlata a modularits kifejezsre Egyetlen fejllomnyos elrendezs Tbb fejllomnyos elrendezs llomnyrszemek Programok Tancsok Gyakorlatok

9.1. Kln fordts


A fjl (az egyes fjlrendszerekben) a trols s fordts hagyomnyos egysge. Vannak olyan rendszerek, amelyek a C++ programokat nem fjlok halmazaknt troljk s fordtjk, s a programok sem fjlok formjban jelennek meg a programoz szmra. Ez a lers azonban csak azokra a rendszerekre sszpontost, amelyek a fjlok hagyomnyos hasznlatra tmaszkodnak.

Forrs: http://www.doksi.hu

262

Alapok

Egy teljes programot rendszerint lehetetlen egy fjlban trolni, mr csak azrt sem, mert a szabvnyos knyvtrak s az opercis rendszer forrskdja ltalban nem szerepel a program forrsban. A valsgos mret alkalmazsokban az sem knyelmes s clszer, ha a felhasznl sajt kdjt egyetlen fjl trolja. A program elrendezsi mdja segthet kihangslyozni a program logikai felptst, segtheti az olvast a program megrtsben s segthet abban is, hogy a fordtprogram kiknyszertse ezt a logikai szerkezetet. Amikor a fordtsi egysg a fjl, akkor a teljes fjlt jra kell fordtani, ha (brmilyen kis) vltoztatst hajtottak vgre rajta, vagy egy msik fjlon, amelytl az elz fgg. Az jrafordtsra hasznlt id mg egy kzepes mret program esetben is jelentsen cskkenthet, ha a programot megfelel mret fjlokra bontjuk. A felhasznl a fordtprogramnak egy forrsfjlt (source file) ad t. Ezutn a fjl elfordtsa trtnik: azaz vgrehajtdik a makrfeldolgozs (7.8), az #include utastsok pedig beptik a fejllomnyokat (2.4.1, 9.2.1). Az elfeldolgozs eredmnyt fordtsi egysgnek (translation unit) hvjk. A fordtprogram valjban csak ezekkel dolgozik s a C++ szablyai is ezek formjt rjk le. Ebben a knyvben csak ott teszek klnbsget a forrsfjl s a fordtsi egysg kztt, ahol meg kell klnbztetni azt, amit a programoz lt, s amit a fordtprogram figyelembe vesz. Ahhoz, hogy a programoz lehetv tegye az elklntett fordtst, olyan deklarcikat kell megadnia, amelyek biztostjk mindazt az informcit, ami ahhoz szksges, hogy a fordtsi egysget a program tbbi rsztl elklntve lehessen elemezni. A tbb fordtsi egysgbl ll programok deklarciinak ugyangy kvetkezetesnek kell lennik, mint az egyetlen forrsfjlbl ll programoknak. A rendszernkben vannak olyan eszkzk, amelyek segtenek ezt biztostani; nevezetesen a szerkesztprogram (linker), amely szmos kvetkezetlensget kpes szrevenni. Ez az a program, ami sszekapcsolja a kln fordtott rszeket. A szerkesztt nha (zavar mdon) betltnek (loader) is szoktk nevezni. A teljes sszeszerkesztst el lehet vgezni a program futsa eltt. Emellett lehetsg van arra is, hogy ksbb j kdot adjunk a programhoz (dinamikus szerkeszts). A program fizikai szerkezetn ltalban a forrsfjlokba szervezett programot rtik. A program forrsfjlokra val fizikai sztvlasztst a program logikai felptse kell, hogy irnytsa. A programok forrsfjlokba rendezst is ugyanaz a fggsgi kapcsolat vezrli, mint azok nvterekbl val sszelltst. A program logikai s fizikai szerkezetnek azonban nem kell megegyeznie. Hasznos lehet pldul tbb forrsfjlt hasznlni egyetlen nvtr fggvnyeinek trolsra, nvtr-meghatrozsok egy gyjtemnyt egyetlen fjlban trolni, vagy egy nvtr definciit tbb fjl kztt sztosztani (8.2.4). Elszr ttekintnk nhny, az sszeszerkesztshez kapcsold rszletet s szakkifejezst, majd ktfle mdjt ismertetjk annak, hogyan lehet fjlokra sztvlasztani a szmolgpet (6.1, 8.2).

Forrs: http://www.doksi.hu

9. Forrsfjlok s programok

263

9.2. sszeszerkeszts
A fggvnyek, osztlyok, sablonok, vltozk, nvterek, felsorolsok s felsorolk neveit kvetkezetesen kell hasznlni az sszes fordtsi egysgben, kivve, ha kifejezetten loklisknt nem hatroztuk meg azokat. A programoz feladata biztostani, hogy minden nvtr, osztly, fggvny stb. megfelelen legyen deklarlva minden olyan fordtsi egysgben, amelyben szerepel, s hogy minden deklarci, amely ugyanarra az egyedre vonatkozik, egysges legyen. Vegyk pldul a kvetkez kt fjlt:
// file1.c: int x = 1; int f() { /* csinlunk valamit */ } // file2.c: extern int x; int f(); void g() { x = f(); }

A file2.c-ben lv g() ltal hasznlt x s f() meghatrozsa a file1.c-ben szerepel. Az extern kulcssz jelzi, hogy a file2.c-ben az x deklarcija (csak) deklarci s nem definci (4.9). Ha x mr rendelkezne kezdrtkkel, a fordtprogram az extern kulcsszt egyszeren figyelmen kvl hagyn, mert a kezdrtket is meghatroz deklarcik egyben defincinak is minslnek. Egy objektumot a programban csak pontosan egyszer hatrozhatunk meg. Deklarlni tbbszr is lehet, de a tpusoknak pontosan meg kell egyeznik:
// file1.c: int x = 1; int b = 1; extern int c; // file2.c: int x; extern double b; extern int c; // jelentse int x = 0;

Itt hrom hiba van: x-et ktszer definiltuk, b-t ktszer deklarltuk klnbz tpusokkal, c-t pedig ktszer deklarltuk, de egyszer sem definiltuk. Az effajta hibkat (szerkesztsi hiba, linkage error) a fordtprogram ami egyszerre csak egy fjlt nz nem ismeri fel, a szerkeszt azonban a legtbbet igen. Jegyezzk meg, hogy a globlis vagy nvtr-hatkr-

Forrs: http://www.doksi.hu

264

Alapok

ben kezdrtk nlkl megadott vltozk alaprtelmezs szerint kapnak kezdrtket. Ez nem vonatkozik a loklis vltozkra (4.9.5, 10.4.2) vagy a szabad trban ltrehozott objektumokra (6.2.6). A kvetkez programrszlet kt hibt tartalmaz:
// file1.c: int x; int f() { return x; } // file2.c: int x; int g() { return f(); }

A file2.c-ben az f() meghvsa hiba, mert f()-et a file2.c nem deklarlja. Ezenkvl a szerkeszt nem fogja sszeszerkeszteni a programot, mert x-et ktszer definiltuk. Jegyezzk meg, hogy az f() meghvsa a C nyelvben nem lenne hiba (B.2.2). Az olyan neveket, amelyeket a nevet meghatroz fordtsi egysgtl klnbz fordtsi egysgben is hasznlhatunk, kls szerkesztsnek (external linkage) nevezzk. Az elz pldkban szerepl sszes nv kls nv. Az olyan neveket, amelyekre csak abban a fordtsi egysgben lehet hivatkozni, ahol meghatrozsuk szerepel, bels szerkeszts nvnek nevezzk. A helyben kifejtett (inline) fggvnyeket (7.1.1, 10.2.9) minden olyan fordtsi egysgben definilni kell azonos mdon (9.2.3) , amelyben hasznlatosak. Ezrt a kvetkez plda nem csak rossz stlusra vall, hanem szablytalan is:
// file1.c: inline int f(int i) { return i; } // file2.c: inline int f(int i) { return i+1; }

Sajnos, ezt a hibt a C++ egyes vltozatai nehezen veszik szre, ezrt a helyben kifejtett kd s a kls szerkeszts kvetkez klnben teljesen logikus prostsa tiltott, hogy a fordtprogram-rk lete knnyebb legyen:
// file1.c: extern inline int g(int i); int h(int i) { return g(i); } // hiba: g() nincs definilva ebben a fordtsi egysgben // file2.c: extern inline int g(int i) { return i+1; }

Forrs: http://www.doksi.hu

9. Forrsfjlok s programok

265

Alaprtelmezs szerint a const-ok (5.4) s a typedef-ek (4.9.7) bels szerkesztsek. Kvetkezskppen ez a plda szablyos (br zavar lehet):
// file1.c: typedef int T; const int x = 7; // file2.c: typedef void T; const int x = 8;

Az olyan globlis vltozk, amelyek egy adott fordtsi egysgben loklisnak szmtanak, gyakran okoznak zavart, ezrt legjobb elkerlni ket. A globlis konstansokat s a helyben kifejtett fggvnyeket rendszerint csak fejllomnyokba (9.2.1) szabadna tennnk, hogy biztostsuk a kvetkezetessget. A konstansokat kifejezett utastssal tehetjk kls szerkesztsv:
// file1.c: extern const int a = 77; // file2.c: extern const int a; void g() { }

cout << a << '\n';

Itt g() 77-et fog kirni. A nvtelen nvtereket (8.2.5) arra hasznlhatjuk, hogy a neveket egy adott fordtsi egysgre nzve lokliss tegyk. A nvtelen nvterek s a bels szerkeszts hatsa nagyon hasonl:
// file 1.c: namespace { class X { /* ... */ }; void f(); int i; // ... } // file2.c: class X { /* ... */ }; void f(); int i; // ...

Forrs: http://www.doksi.hu

266

Alapok

A file1.c-ben lv f() fggvny nem azonos a file2.c-ben lv f() fggvnnyel. Ha van egy adott fordtsi egysgre nzve loklis nevnk s ugyanazt a nevet hasznljuk mshol egy kls szerkeszts egyed szmra is, akkor magunk keressk a bajt. A C nyelv s a rgebbi C++ programokban a static kulcsszt hasznltk (zavaran) annak a kifejezsre, hogy hasznlj bels szerkesztst (B.2.3). A static kulcsszt lehetleg csak fggvnyeken (7.2.1) s osztlyokon (10.2.4) bell hasznljuk.

9.2.1. Fejllomnyok
A tpusoknak ugyanannak az objektumnak, fggvnynek, osztlynak stb. minden deklarcijban egysgesnek kell lennik, kvetkezskppen a fordtnak tadott s ksbb sszeszerkesztett forrskdnak is. A klnbz fordtsi egysgekben lv deklarcik egysgessgnek elrsre nem tkletes, de egyszer mdszer, hogy a vgrehajthat kdot s/vagy adatlersokat tartalmaz forrsfjlokba beptjk (#include) a felletre vonatkoz informcikat tartalmaz fejllomnyokat (header). Az #include szvegkezel eszkz, ami arra val, hogy a forrskd-rszeket egyetlen egysgbe (fjlba) gyjtsk ssze a fordtshoz. Az
#include "beptend"

utasts a beptend fjl tartalmra cserli azt a sort, amelyben az #include elfordul. A fjl tartalmnak C++ forrsszvegnek kell lennie, mert a fordtprogram ennek olvassval halad tovbb. A standard knyvtrbeli fejllomnyok beptshez a fjl nevt idzjelek helyett a < > zrjelprok kz kell foglalni:
#include <iostream> #include "myheader.h" // a szabvnyos include knyvtrbl // az aktulis knyvtrbl

Sajnos a bept utastsban a szkzk mind a < >, mind a " " belsejben fontosak:
#include < iostream > // nem fogja megtallni az <iostream>-et

Furcsnak tnhet, hogy egy fjlt minden egyes alkalommal jra kell fordtani, ha valahov mshov beptjk, de a beptett fjlok jellemzen csak deklarcikat tartalmaznak, s nem olyan kdot, amelyet a fordtprogramnak alaposan elemeznie kellene. Tovbb a leg-

Forrs: http://www.doksi.hu

9. Forrsfjlok s programok

267

tbb modern C++-vltozat valamilyen formban tmogatja az elfordtott fejllomnyokat, hogy cskkentse a munkt, amit ugyanannak a fejllomnynak az ismtelt fordtsa jelent. Alapszablyknt fogadjuk el, hogy egy fejllomnyban a kvetkezk szerepelhetnek:

Nevestett nvterek Tpusdefincik Sablondeklarcik Sablondefincik Fggvnydeklarcik Helyben kifejtett fggvnyek defincii Adatdeklarcik Konstansdefincik Felsorolsok Nvdeklarcik Bept utastsok Makrdefincik Feltteles fordtsi utastsok Megjegyzsek

namespace N { /* */ } struct Point { int x, y; }; template<class T> class Z; template<class T> class V { /* */ }; extern int strlen(const char*); inline char get(char* p) { return *p++; } extern int a; const float pi = 3.141593; enum Light { red, yellow, green }; class Matrix; #include <algorithm> #define VERSION 12 #ifdef __cplusplus /* check for end of file */

Mindez nem nyelvi kvetelmny, csak sszer mdja az #include hasznlatnak a logikai szerkezet kifejezsre. Ezzel ellenttben egy fejllomny sohasem tartalmazhatja a kvetkezket:

Kznsges fggvnydefincik Adatdefincik Agregtum-defincik Nvtelen nvterek Exportlt sablondefincik

char get(char *p) { return *p++; } int a; short tbl[ ] = { 1, 2, 3 }; namespace { /* */ } export template<class T>f(T t) { /* */ }

A fejllomnyok hagyomny szerint .h kiterjesztsek, a fggvny- s adatdefincikat tartalmaz fjlok kiterjesztse pedig .c, ezrt gyakran hvjk ezeket .h fjlok-nak s .c fjlok-nak. Ms szoksos jellseket is tallhatunk, mint a .C, .cxx, .cpp s .cc. Fordtprogramunk dokumentcija ezt jl meghatrozza.

Forrs: http://www.doksi.hu

268

Alapok

Az egyszer llandkat ajnlatos fejllomnyokba tenni. Az agregtumokat azonban nem, mert az egyes C++-vltozatok nehezen tudjk elkerlni, hogy a tbb fordtsi egysgben elfordul egyedeibl msodpldnyt ksztsenek. Ezenkvl az egyszer esetek sokkal gyakoribbak, ezrt j kd ksztshez fontosabbak. Blcs dolog nem tl okosnak lenni az #include hasznlatnl. Azt ajnlom, csak teljes deklarcikat s defincikat ptsnk be s csak globlis hatkrben, szerkesztsi blokkokban, vagy olyan nvtrdefinciknl, amikor rgi kdot alaktunk t (9.2.2) tegyk ezt. Clszer elkerlni a makrkkal val gyeskedst is. Az egyik legkevsb kedvelt foglalatossgom olyan hibt nyomon kvetni, amit egy olyan nv okoz, amelyet egy kzvetetten beptett, szmomra teljesen ismeretlen fejllomnyban szerepl makr helyettest.

9.2.2. A standard knyvtr fejllomnyai


A standard knyvtr eszkzeit szabvnyos fejllomnyok halmazn keresztl mutatjuk be (16.1.2). A standard knyvtrbeli fejllomnyokat nem kell uttaggal elltnunk; tudjuk rluk, hogy fejllomnyok, mert beillesztskhz az #include <> formt hasznljuk az #include "" helyett. A .h kiterjeszts hinya nem utal semmire a fejllomny trolsval kapcsolatban. Egy olyan fejllomny, mint a <map>, valsznleg a map.h nev szvegfjlban troldik a szoksos knyvtrban. Msfell a szabvnyos fejllomnyokat nem muszj hagyomnyos mdon trolni. Az egyes C++-vltozatok szmra megengedett, hogy kihasznljk a standard knyvtr definciinak ismerett s ezltal optimalizljk annak megvalstst, illetve a szabvnyos fejllomnyok kezelsnek mdjt. A nyelv adott megvalstsa ismerheti a beptett szabvnyos matematikai knyvtrat (22.3) s gy kezelheti az #include <cmath> utastst, mint egy kapcsolt, ami anlkl teszi elrhetv a szabvnyos matematikai fggvnyeket, hogy brmilyen fjlt beolvasnnk. A C standard knyvtrnak minden <X.h> fejllomnyhoz ltezik megfelel szabvnyos <cX> C++ fejllomny. Az #include <cstdio> pldul azt nyjtja, amit az #include <stdio.h>. A stdio.h fjl ltalban valahogy gy nz ki:
#ifdef __cplusplus // csak C++ fordtk szmra (9.2.4) namespace std { // a standard knyvtrat az std nvtr rja le (8.2.9) extern "C" { // az stdio fggvnyek C szerkesztsek (9.2.4) #endif // ... int printf(const char* ...); // ... #ifdef __cplusplus } } using namespace std; // az stdio elrhetv ttele a globlis nvtrben #endif

Forrs: http://www.doksi.hu

9. Forrsfjlok s programok

269

Azaz a deklarcik (nagy valsznsggel) kzsek, de az sszeszerkesztssel s nvterekkel kapcsolatos dolgokra oda kell figyelnnk, hogy lehetv tegyk, hogy a C s C++ osztozzanak a fejllomnyon.

9.2.3. Az egyszeri definils szablya


Egy adott osztlyt, felsorolst, sablont stb. mindig csak egyszer definilhatunk egy programban. Gyakorlati szempontbl ez azt jelenti, hogy pldul egy osztlynak, amelyet valahol egy fjlban trolunk, pontosan egy kifejtssel kell rendelkeznie. Sajnos, a nyelvi szably nem lehet ennyire egyszer. Egy osztly defincijt pldul ssze lehet lltani makrk behelyettestsvel is, de #include utastsokkal (9.2.1) szveges formban kt forrsfjlban is el lehet helyezni. Mg ennl is nagyobb baj, hogy a fjl fogalma nem rsze a C s C++ nyelvnek, gy vannak olyan vltozatok, amelyek a programokat nem forrsfjlokban troljk. Kvetkezskppen a szabvnyban lv szablyt amely azt mondja, hogy egy osztly, sablon stb. defincijnak egyedinek kell lennie valamelyest bonyolultabb s ravaszabb mdon fogalmaztuk meg. Ezt a szablyt gyakran az egyszeri definils szablynak (ODR, one-definition rule) nevezik. Azaz egy osztly, sablon, vagy helyben kifejtett fggvny ktfle definilsa kizrlag akkor fogadhat el ugyanazon egyed kt pldnyaknt, ha 1. klnbz fordtsi egysgben szerepelnek s 2. szimblumrl szimblumra megegyeznek s 3. ezen szimblumok jelentse mindkt fordtsi egysgben ugyanaz. Pldul:
// file1.c: struct S { int a; char b; }; void f(S*); // file2.c: struct S { int a; char b; }; void f(S* p) { /* ... */ }

Az ODR rtelmben a fenti plda helyes s S ugyanarra az osztlyra vonatkozik mindkt forrsfjlban. Nem blcs dolog azonban egy defincit ilyen mdon ktszer lerni. Ha valaki mdostja a file2.c-t, azt felttelezheti, hogy az ott szerepl S az S egyetlen definilsa s szabadon megvltoztathatja azt, ami nehezen felfedezhet hibt okozhat.

Forrs: http://www.doksi.hu

270

Alapok

Az ODR szndka az, hogy megengedje egy osztlydefinci beillesztst klnbz forrsfjlokba egy kzs forrsfjlbl:
// file s.h: struct S { int a; char b; }; void f(S*); // file1.c: #include "s.h" // f() hasznlata itt // file2.c: #include "s.h" void f(S* p) { /* ... */ }

brval: s.h: struct S {int a; char b}; void f(S*);

file1.c: #include "s.h" // f() hasznlata itt

file2.c: #include "s.h" void f(S*p) {/*...*/}

Nzznk pldkat az ODR szably megsrtsnek mindhrom mdjra:


// file1.c: struct S1 { int a; char b; }; struct S1 { int a; char b; }; // hiba: kt definci

Ez azrt hiba, mert egy struct-ot egyetlen fordtsi egysgben nem lehet ktszer definilni.
// file1.c: struct S2 { int a; char b; }; // file2.c: struct S2 { int a; char bb; };// hiba

Forrs: http://www.doksi.hu

9. Forrsfjlok s programok

271

Ez azrt hiba, mert S2 olyan osztlyokat nevez meg, amelyek egy tag nevben klnbznek.
// file1.c: typedef int X; struct S3 { X a; char b; }; // file2.c: typedef char X; struct S3 { X a; char b; };

// hiba

Itt az S3 kt definicija szimblumrl szimblumra megegyezik, de a plda hibs, mert az X nv (trkks mdon) mst jelent a kt fjlban. A legtbb C++-vltozat nem kpes a klnbz fordtsi egysgekben lv osztlydefincik kvetkezetessgt ellenrizni, ezrt az ODR-t megsrt deklarcik nehezen szrevehet hibkat okozhatnak. Sajnos az a mdszer sem kpes az ODR utolsknt bemutatott megszegse ellen vdelmet nyjtani, amikor a kzs definicikat fejllomnyokba tesszk s aztn azokat ptjk be. A helyi typedef-ek s makrk ugyanis mdosthatjk a beptett deklarcik jelentst:
// file s.h: struct S { Point a; char b; }; // file1.c: #define Point int #include "s.h" // ... // file2.c: class Point { /* ... */ }; #include "s.h" // ...

Az ilyen kdmdosuls ellen gy vdekezhetnk a legjobban, ha a fejllomnyokat annyira klnllv tesszk, amennyire csak lehetsges. Pldul ha a Point osztlyt az s.h llomnyban vezettk volna be, a fordtprogram felismerte volna a hibt. A sablondefincikat tbb fordtsi egysgbe is bepthetjk, amg ez nem srti az ODR-t, az exportlt sablonokat pedig gy is hasznlhatjuk, hogy csak a deklarcijukat adjuk meg:
// file1.c: export template<class T> T twice(T t) { return t+t; }

Forrs: http://www.doksi.hu

272

Alapok

// file2.c: template<class T> T twice(T t); int g(int i) { return twice(i); }

// deklarci

Az export kulcssz azt jelenti, hogy ms fordtsi egysgbl elrhet (13.7).

9.2.4. sszeszerkeszts nem C++ kddal


A C++ programok ltalban ms nyelven megrt rszleteket is tartalmaznak. Hasonlan gyakori az is, hogy C++ kdrszletet hasznlnak ms nyelven megrt programok rszeknt. Az egyttmkds a klnbz nyelven megrt programrszek kztt nem mindig knny st mg az azonos nyelven rt, de klnbz fordtprogrammal lefordtott kdrszletek kztt sem. A klnbz nyelvek s ugyanazon nyelv klnbz megvalstsai pldul klnbzkppen hasznlhatjk a gpi regisztereket a paramterek trolsra, mskppen helyezhetik azokat a verembe, klnbz lehet a beptett tpusok, pldul a karakterlncok s egszek szerkezete, illetve azon nevek formja, melyeket a fordtprogram a szerkesztnek tad, s a szerkeszttl megkvetelt tpusellenrzsek. Hogy segtsnk, sszeszerkesztsi szablyt hatrozhatunk meg az extern deklarcikra. A kvetkez plda bevezeti a C s a C++ standard knyvtraiban lev strcpy() fggvnyt, s meghatrozza, hogy a C sszeszerkesztsi szablyainak megfelelen kell azt hozzszerkeszteni a kdhoz:
extern "C" char* strcpy(char*, const char*);

A deklarci hatsa a sima deklarciktl csak az strcpy() hvsra hasznlt sszeszerkesztsi szablyban tr el.
extern char* strcpy(char*, const char*);

Az extern "C" utasts klnsen fontos a C s a C++ kztti szoros kapcsolat miatt. Jegyezzk meg, hogy az extern "C"-ben szerepl "C" az sszeszerkesztsi szablyt, nem pedig a programnyelvet jelli. Az extern "C"-t gyakran hasznljk olyan Fortran vagy assembler eljrsokkal val sszeszerkesztshez, melyek vletlenl ppen megfelelnek a C kvetelmnyeinek. Az extern "C" utasts (csak) az sszeszerkesztsi szablyt hatrozza meg, a fggvnyhvsok szerept nem befolysolja. Az extern "C"-knt megadott fggvnyekre is a C++ tpusellenrzsi s paramter-talaktsi szablyai vonatkoznak, nem pedig a gyengbb C szablyok.

Forrs: http://www.doksi.hu

9. Forrsfjlok s programok

273

Pldul:
extern "C" int f(); int g() { return f(1); }

// hiba: nem vr paramtert

Knyelmetlen lehet, ha sok deklarcihoz kell hozzadnunk az extern "C"-t, ezrt bevezettnk egy eljrst, mellyel deklarcik egy csoportjnak sszeszerkesztst hatrozhatjuk meg:
extern "C" { char* strcpy(char*, const char*); int strcmp(const char*, const char*); int strlen(const char*); // ... }

Ezt a szerkezetet, melyet gyakran szerkesztsi blokknak (linkage block) neveznek, gy is hasznlhatjuk, hogy belefoglalunk egy teljes C fejllomnyt s gy alkalmass tesszk azt a C++-ban val hasznlatra:
extern "C" { #include <string.h> }

A fenti mdszer gyakran hasznlatos arra, hogy C fejllomnyokbl C++ fejllomnyokat hozzanak ltre. Egy msik lehetsg, ha feltteles fordtst (7.8.1) hasznlunk, hogy kzs C s C++ fejllomnyt ksztsnk:
#ifdef __cplusplus extern "C" { #endif char* strcpy(char*, const char*); int strcmp(const char*, const char*); int strlen(const char*); // ... #ifdef __cplusplus } #endif

Forrs: http://www.doksi.hu

274

Alapok

A kszen kapott __cplusplus makr hasznlatval azt biztosthatjuk, hogy a C++ szerkezetek eltnjenek, amikor a fjlt C fejllomnyknt hasznljuk. Egy szerkesztsi blokkon bell brmilyen deklarci szerepelhet:
extern "C" { int g1; extern int g2; } // brmilyen deklarci jhet ide, pl: // definci // deklarci, nem definci

Ez a vltozk hatkrt s trolsi osztlyt nem rinti, gy g1 globlis vltoz marad, s definicija is lesz, nem csak deklarcija. Ha egy vltozt csak deklarlni, nem pedig definilni akarunk, az extern kulcsszt kzvetlenl a deklarci eltt kell megadnunk:
extern "C" int g3; // deklarci, nem definci

Ez els ltsra furcsnak tnik, pedig clja csak annyi, hogy a deklarci jelentse vltozatlan maradjon, amikor egy extern deklarcihoz "C"-t adunk (s a fjl jelentse is, amikor majd a szerkesztsi blokkba foglaljuk). Egy C szerkeszts nevet nvtrben is megadhatunk. A nvtr azt befolysolni fogja, hogyan lehet a nvhez hozzfrni C++ programokbl, de azt nem, hogy a szerkeszt hogyan fogja ltni a nevet. Egy jellemz plda erre az std nvtr printf() fggvnye:
#include<cstdio> void f() { std::printf("Hell, "); printf("vilg!\n"); }

// rendben // hiba: nincs globlis printf()

Mg ha std::printf()-nek nevezzk is, ez mg mindig ugyanaz a rgi C printf() (21.8). Ez lehetv teszi szmunkra, hogy C szerkeszts knyvtrakat ptsnk be egy ltalunk vlasztott nvtrbe, ahelyett, hogy a globlis nvteret szennyeznnk". Sajnos ugyanez a rugalmassg nem ll rendelkezsnkre az olyan fejllomnyok esetben, amelyek C++ szerkeszts fggvnyeket hatroznak meg a globlis nvtrben. Ennek az az oka, hogy a C++ egyedek sszeszerkesztsnl figyelembe kell venni a nvtereket is, gy a ltrehozott trgykd (object fjl) tkrzni fogja a nvterek hasznlatt vagy annak hinyt.

Forrs: http://www.doksi.hu

9. Forrsfjlok s programok

275

9.2.5. Az sszeszerkeszts s a fggvnyekre hivatkoz mutatk


Ha egy programban a C s C++ kdrszleteket keverjk, elfordulhat, hogy az egyik nyelven megrt fggvnyekre hivatkoz mutatkat a msik nyelven definilt fggvnyeknek szeretnnk tadni. Ha a kt nyelv adott vltozatainak sszeszerkesztsi szablyai, illetve a fggvnyhvsi eljrsok kzsek, a fggvnyekre hivatkoz mutatk tadsa egyszer. Ennyi kzs tulajdonsg azonban ltalban nem ttelezhet fel, gy figyelnnk kell arra, hogy biztostsuk a fggvnyek oly mdon trtn meghvst, ahogy azt a fggvny elvrja. Ha egy deklarci szmra meghatrozzuk az sszeszerkesztsi mdot, akkor az minden olyan fggvnytpusra, fggvnynvre, s vltoznvre vonatkozni fog, amit a deklarci(k) bevezet(nek). Ez mindenfle furcsa de nha alapvet sszeszerkesztsi mdot lehetv tesz. Pldul:
typedef int (*FT)(const void*, const void*); extern "C" { typedef int (*CFT)(const void*, const void*); void qsort(void* p, size_t n, size_t sz, CFT cmp); } // FT C++ szerkeszts // CFT C szerkeszts // cmp C szerkeszts

void isort(void* p, size_t n, size_t sz, FT cmp); // cmp C++ szerkeszts void xsort(void* p, size_t n, size_t sz, CFT cmp); // cmp C szerkeszts extern "C" void ysort(void* p, size_t n, size_t sz, FT cmp); // cmp C++ szerkeszts int compare(const void*, const void*); extern "C" int ccmp(const void*, const void*); void f(char* v, int sz) { qsort(v,sz,1,&compare); qsort(v,sz,1,&ccmp); isort(v,sz,1,&compare); isort(v,sz,1,&ccmp); // compare() C++ szerkeszts // ccmp() C szerkeszts

// hiba // rendben // rendben // hiba

Egy olyan nyelvi vltozat, amelyben a C s C++ ugyanazt a fggvnyhvsi mdot hasznlja, nyelvi kiterjesztsknt elfogadhatja a hibaknt megjellt eseteket.

Forrs: http://www.doksi.hu

276

Alapok

9.3. Fejllomnyok hasznlata


A fejllomnyok hasznlatnak illusztrlsra most bemutatjuk a szmolgp program (6.1, 8.2) nhny lehetsges fizikai elrendezst.

9.3.1. Egyetlen fejllomnyos elrendezs


Egy programot gy bonthatunk a legegyszerbben tbb fjlra, hogy a defincikat megfelel szm .c fjlba, a .c fjlok kztti kapcsolatot biztost tpusok deklarciit pedig egyetlen .h fjlba tesszk, melyet minden .c fjl bept (#include). A szmolgp program esetben t .c fjlt lexer.c, parser.c, table.c, error.c s main.c hasznlhatnnk a fggvnyek s adatlersok trolsra, s a dc.h fejllomnyban trolhatnnk azoknak a neveknek a deklarciit, amelyek egynl tbb fjlban hasznlatosak. A dc.h fejllomny gy nzne ki:
// dc.h: namespace Error { struct Zero_divide { }; struct Syntax_error { const char* p; Syntax_error(const char* q) { p = q; } };

#include <string> namespace Lexer { enum Token_value { NAME, PLUS='+', PRINT=';', }; NUMBER, MINUS='-', ASSIGN='=', END, MUL='*', LP='(',

DIV='/', RP=')'

extern Token_value curr_tok; extern double number_value; extern std::string string_value; } Token_value get_token();

Forrs: http://www.doksi.hu

9. Forrsfjlok s programok

277

namespace Parser { double prim(bool get); double term(bool get); double expr(bool get); using Lexer::get_token; using Lexer::curr_tok;

// elemi szimblumok kezelse // szorzs s oszts // sszeads s kivons

#include <map> extern std::map<std::string,double> table; namespace Driver { extern int no_of_errors; extern std::istream* input; void skip(); }

Minden vltozdeklarciban az extern kulcsszt hasznljuk annak biztostsra, hogy egy meghatrozs ne forduljon el tbbszr is, amikor a dc.h-t a fjlokba beptjk. Az egyes defincik a megfelel .c fjlban szerepelnek. A lnyegi kd elhagysval a lexer.c valahogy gy nz ki:
// lexer.c: #include "dc.h" #include <iostream> #include <cctype> Lexer::Token_value Lexer::curr_tok; double Lexer::number_value; std::string Lexer::string_value; Lexer::Token_value Lexer::get_token() { /* ... */ }

A fejllomny ilyen hasznlata biztostja, hogy a benne lv minden deklarci valamilyen ponton be legyen ptve abba a fjlba, amely a hozz tartoz kifejtst tartalmazza. A lexer.c fordtsakor pldul a fordtprogramnak a kvetkez kd addik t:
namespace Lexer { // ... Token_value get_token(); } // a dc.h-bl

// ... Lexer::Token_value Lexer::get_token() { /* ... */ }

Forrs: http://www.doksi.hu

278

Alapok

gy a fordtprogram biztosan szreveszi, ha egy nvhez megadott tpusok nem egysgesek. Ha a get_token()-t pldul Token_value tpus visszatrsi rtkkel vezettk volna be, de int visszatrsi rtkkel definiltuk volna, a lexer.c fordtsa tpustkzsi vagy nem megfelel tpus (type mismatch) hiba miatt nem sikerlt volna. Ha egy definci hinyzik, a szerkeszt fogja szrevenni a problmt, ha egy deklarci, akkor valamelyik .c fjl fordtsa hisul meg. A parser.c fjl gy fog kinzni:
// parser.c: #include "dc.h" double Parser::prim(bool get) { /* ... */ } double Parser::term(bool get) { /* ... */ } double Parser::expr(bool get) { /* ... */ }

A table.c pedig gy:


// table.c: #include "dc.h" std::map<std::string,double> table;

A szimblumtbla nem ms, mint egy standard knyvtrbeli map tpus vltoz. A fenti definci a table-t globlisknt hatrozza meg. Egy valsgos mret programban a globlis nvtr effle kis szennyezdsei felhalmozdnak s vgl problmkat okoznak. Csak azrt voltam itt ilyen hanyag, hogy lehetsgem legyen figyelmeztetni r. A main.c fjl vgl gy fog kinzni:
// main.c: #include "dc.h" #include <sstream> int Driver::no_of_errors = 0; std::istream* Driver::input = 0; void Driver::skip() { /* ... */ } int main(int argc, char* argv[ ]) { /* ... */ }

Forrs: http://www.doksi.hu

9. Forrsfjlok s programok

279

Ahhoz, hogy a program main() fggvnynek ismerjk fel, a main()-nek globlis fggvnynek kell lennie, ezrt itt nem hasznltunk nvteret. A program fizikai elrendezst valahogy gy lehet bemutatni: <sstream> <map> <string> <cctype> <iostream>

dc.h

main.c

parser.c

table.c

lexer.c

szrevehetjk, hogy a fell lv fejllomnyok mind a standard knyvtr fjljai. A program elemzsekor ezek a knyvtrak szmos esetben kihagyhatk, mert szleskren ismertek s stabilak. A kis programoknl az elrendezs egyszersthet, ha minden #include utastst kzs fejllomnyba tesznk. Az egyetlen fejllomnyos fizikai rszekre bonts akkor a leghasznosabb, ha a program kicsi s rszeit nem ll szndkunkban kln hasznlni. Jegyezzk meg, hogy amikor nvtereket hasznlunk, a dc.h-ban egyben a program logikai felptst is brzoljuk. Ha nem hasznlunk nvtereket, a szerkezet homlyos lesz, br ezen a megjegyzsek segthetnek. A nagyobb programok egyetlen fejllomnyos elrendezse nem mkdik a hagyomnyos, fjl alap fejlesztkrnyezetekben. A kzs fejllomny mdostsa maga utn vonja az egsz program jrafordtst s nagy a hibalehetsg, ha tbb programoz is mdostja az egyetlen fejllomnyt. Hacsak nem fektetnek hangslyt a nvterekkel s osztlyokkal kapcsolatos programozsi stlusra, a logikai felpts a program nvekedsvel egytt romlani fog.

Forrs: http://www.doksi.hu

280

Alapok

9.3.2. Tbb fejllomnyos elrendezs


Egy msik fizikai elrendezs szerint minden logikai modulnak sajt fejllomnya lenne, amely lerja a modul ltal nyjtott szolgltatsokat. Ekkor minden .c fjlhoz tartozik egy megfelel .h fjl, ami meghatrozza a .c szolgltatsait (fellett). Minden .c fjl bepti a sajt .h fjljt, s rendszerint tovbbi olyan .h fjlokat is, amelyek meghatrozzk, mire van szksge ms modulokbl ahhoz, hogy megvalstsa a felletben kzztett szolgltatsokat. Ez a fizikai elrendezs megegyezik a modul logikai felptsvel. A felhasznlknak sznt felletet a .h fjl tartalmazza, a programozi fellet egy _impl.h vgzds fjlban szerepel, a modul fggvny- s vltozdefincii stb. pedig a .c fjlokban vannak elhelyezve. Ily mdon az elemzt hrom fjl kpviseli, felhasznli fellett pedig a parser.h nyjtja:
// parser.h: namespace Parser { double expr(bool get); } // fellet a felhasznlknak

Az elemzt megvalst fggvnyek kzs krnyezett a parser_impl.h adja:


// parser_impl.h: #include "parser.h" #include "error.h" #include "lexer.h" namespace Parser { double prim(bool get); double term(bool get); double expr(bool get); using Lexer::get_token; using Lexer::curr_tok; } // fellet a megvalstshoz

A parser.h felhasznli fejllomnyt azrt ptjk be, hogy a fordtprogram ellenrizhesse a kvetkezetessget (9.3.1). Az elemz fggvnyeket a parser.c fjlban egytt troljuk azokra a fejllomnyokra vonatkoz #include utastsokkal, melyekre a Parser fggvnyeinek szksge van:
// parser.c: #include "parser_impl.h" #include "table.h" double Parser::prim(bool get) { /* ... */ } double Parser::term(bool get) { /* ... */ } double Parser::expr(bool get) { /* ... */ }

Forrs: http://www.doksi.hu

9. Forrsfjlok s programok

281

Az elemz s annak a vezrl ltali hasznlata brval gy mutathat be:

parser.h

lexer.h

error.h

table.h

parser_impl.h

main.c

parser.c

Ahogy vrtuk, ez elg jl egyezik a 8.3.3-ban lert logikai szerkezettel. Ha a table.h-t a parser_impl.h-ba ptettk volna be a parser.c helyett, a szerkezetet mg tovbb egyszersthettk volna. A table.h azonban valami olyasmire plda, ami nem szksges az elemz fggvnyek kzs krnyezetnek kifejezshez, csak a fggvnyek megvalstsainak van szksge r. Tulajdonkppen egyetlen fggvny, a prim() hasznlja, gy ha a fggsgeket valban a lehet legkevesebbre szeretnnk cskkenteni, a prim()-et tegyk kln .c fjlba s csak oda ptsk be a table.h-t: parser.h lexer.h error.h table.h

parser_impl.h

parser.c

prim.c

Forrs: http://www.doksi.hu

282

Alapok

Ilyen alapossgra a nagyobb modulokat kivve nincs szksg. A valsgos mret modulok esetben gyakori, hogy tovbbi fjlokat ptenek be ott, ahol egyes fggvnyek szmra azok szksgesek. Tovbb nem ritka, hogy egynl tbb _impl.h fjl van, mivel a modul fggvnyeinek rszhalmazai klnbz kzs krnyezetet ignyelnek. Meg kell jegyeznnk, hogy az _impl.h hasznlata nem szabvnyos s mg csak nem is gyakori megolds n egyszeren gy szeretek elnevezni dolgokat. Mirt trdnk ezzel a bonyolultabb tbb fejllomnyos elrendezssel? Nyilvn sokkal kevesebb gondolkodst ignyel, ha egyszeren minden deklarcit bedobunk egy fejllomnyba, mint ahogy azt a dc.h-nl tettk. A tbb fejllomnyos elrendezs olyan modulok s programok esetben hatsos, amelyek nagysgrendekkel nagyobbak, mint a mi apr elemznk s szmolgpnk. Alapveten azrt hasznltuk ezt az elrendezstpust, mert jobban azonostja a kapcsolatokat. Egy nagy program elemzsekor vagy mdostsakor alapvet, hogy a programoz viszonylag kis kdrszletre sszpontosthasson. A tbb fejllomnyos elrendezs segt, hogy pontosan eldnthessk, mitl fgg az elemz kd, s hogy figyelmen kvl hagyhassuk a program tbbi rszt. Az egyetlen fejllomnyos elrendezs rknyszert minket, hogy minden olyan deklarcit megnzznk, amelyet valamelyik modul hasznl, s eldntsk, hogy odaill-e. A lnyeg, hogy a kd mdostsa mindig hinyos informcik s helyi nzpont alapjn trtnik. A tbb fejllomnyos elrendezs megengedi, hogy sikeresen dolgozzunk bellrl kifel, csak helyi szemszgbl. Az egyetlen fejllomnyos elrendezs mint minden ms elrendezs, ahol egy globlis informcitr van a kzppontban fellrl lefel halad megkzeltst ignyel, gy rkk gondolkodnunk kell azon, hogy pontosan mi fgg egy msik dologtl. A jobb sszpontosts azt eredmnyezi, hogy kevesebb informci kell a modul lefordtshoz, gy az gyorsabban trtnik. A hats drmai lehet. Elfordul, hogy a fordtsi id tizedrszre cskken, pusztn azrt, mert egy egyszer fggsgelemzs a fejllomnyok jobb hasznlathoz vezet. 9.3.2.1. A szmolgp egyb moduljai A szmolgp tbbi moduljt az elemzhz hasonlan rendezhetjk el. Ezek a modulok azonban olyan kicsik, hogy nem ignyelnek sajt _impl.h fjlokat. Az ilyen fjlok csak ott kellenek, ahol egy logikai modul sok fggvnybl ll, amelyeknek kzs krnyezetre van szksgk.

Forrs: http://www.doksi.hu

9. Forrsfjlok s programok

283

A hibakezelt a kivteltpusok halmazra egyszerstettk, gy nincs szksgnk az error.c-re:


// error.h: namespace Error { struct Zero_divide { }; struct Syntax_error { const char* p; Syntax_error(const char* q) { p = q; } };

Az adatbeviteli kd (lexikai elemz, lexer) meglehetsen nagy s rendezetlen felletet nyjt:


// lexer.h: #include <string> namespace Lexer { enum Token_value { NAME, PLUS='+', PRINT=';', }; NUMBER, MINUS='-', ASSIGN='=', END, MUL='*', LP='(',

DIV='/', RP=')'

extern Token_value curr_tok; extern double number_value; extern std::string string_value; } Token_value get_token();

A lexer.h-n kvl a lexikai elemz az error.h-ra, az <iostream>-re s a <ctype>-ban megadott, a karakterek fajtit eldnt fggvnyekre tmaszkodik:
// lexer.c: #include #include #include #include "lexer.h" "error.h" <iostream> <cctype>

Forrs: http://www.doksi.hu

284

Alapok

Lexer::Token_value Lexer::curr_tok; double Lexer::number_value; std::string Lexer::string_value; Lexer::Token_value Lexer::get_token() { /* ... */ }

Az error.h #include utastsait kln tehettk volna, a Lexer-hez tartoz _impl.h fjlba, ez azonban tlzs egy ilyen kis program esetben. A modul megvalstsban szoksos mdon ptjk be (#include) a modul ltal nyjtott felletet ebben az esetben a lexer.h-t , hogy a fordtprogram ellenrizhesse a kvetkezetessget. A szimblumtbla alapveten nll, br a standard knyvtrbeli <map> fejllomny hasznlatval szmos rdekes dolog kerlhet bele, hogy hatkonyan valsthassa meg a map sablonosztlyt:
// table.h: #include <map> #include <string> extern std::map<std::string,double> table;

Mivel feltesszk, hogy az egyes fejllomnyok tbb .c fjlba is bele lehetnek ptve, a table deklarcijt kln kell vlasztanunk annak kifejtstl, mg akkor is, ha a table.c s a table.h kztti klnbsg csak az extern kulcssz:
// table.c: #include "table.h" std::map<std::string,double> table;

A vezrl tulajdonkppen mindenre tmaszkodik:


// main.c: #include "parser.h" #include "lexer.h" #include "error.h" #include "table.h"

Forrs: http://www.doksi.hu

9. Forrsfjlok s programok

285

namespace Driver { int no_of_errors; std::istream* input; void skip(); } #include <sstream> int main(int argc, char* argv[ ]) { /* ... */ }

Mivel a Driver nvteret kizrlag a main() hasznlja, a main.c-be tesszk azt. Kln is szerepelhetne driver.h fjlknt, amit az #include utastssal beptnk. A nagyobb programokat rendszerint megri gy elrendezni, hogy a vezrlnek kevesebb kzvetlen fggsge legyen. Gyakran sszer az olyan mveletekbl is minl kevesebbet alkalmazni, amit a main() tesz, nevezetesen hogy meghv egy kln forrsfjlban lv vezrl fggvnyt. Ez klnsen fontos olyan kdok esetben, amelyeket knyvtrknt akarunk hasznlni. Ekkor ugyanis nem tmaszkodhatunk a main()-ben megrt kdra s fel kell kszlnnk arra is, hogy klnbz fggvnyekbl hvjk meg kdunkat (9.6[8]). 9.3.2.2. A fejllomnyok hasznlata A programban hasznlt fejllomnyok (header file) szma tbb tnyeztl fgg. Ezen tnyezk kzl sok inkbb a rendszer fjlkezelsbl addik s nem a C++-bl. Pldul, ha szvegszerkeszt programunk nem kpes arra, hogy tbb fjlt nzznk vele egyszerre, akkor nem elnys sok fejllomnyt hasznlnunk. Hasonlan, ha 20 darab 50 soros fjl olvassa szreveheten tbb idt ignyel, mint egyetlen 1000 soros fjl, akkor ktszer is gondoljuk meg, mieltt egy kis projektben a tbb fejllomnyos stlust hasznljuk. Nhny figyelmeztets: egy tucatnyi fejllomny (termszetesen a szoksos fejllomnyokkal egytt, melyeket gyakran szzas nagysgrendben szmolhatunk) a program vgrehajtsi krnyezete szmra rendszerint mg kezelhet. Ha azonban egy nagy program deklarciit logikailag a lehet legkisebb fejllomnyokra bontjuk (pldul gy, hogy minden szerkezet deklarcijt kln fjlba tesszk), knnyen egy tbb szz fjlbl ll, kezelhetetlen zrzavar lehet az eredmny. Nagy projekteknl persze elkerlhetetlen a sok fejllomny. Az ilyeneknl tbb szz fjl (nem szmolva a szoksos fejllomnyokat) az ltalnos. Az igazi bonyodalom ott kezddik, amikor elrik az ezres nagysgrendet. A fent trgyalt alapvet mdszerek ekkor is alkalmazhatk, de az llomnyok kezelse sziszifuszi feladatt vlik. Emlkezznk, hogy

Forrs: http://www.doksi.hu

286

Alapok

a valdi mret programoknl az egyetlen fejllomnyos elrendezst ltalban nem vlaszthatjuk, mert az ilyen programok rendszerint eleve tbb fejllomnyt tartalmaznak. A ktfajta elrendezsi mdszer kztt a program alkotrszeinek ltrehozsakor kell (nha tbbszr is) vlasztanunk. Igazn nem is a mi zlsnkre van bzva, hogy az egyetlen s a tbb fejllomnyos elrendezs kzl vlasszunk. Ezek olyan egymst kiegszt mdszerek, melyeket mindig figyelembe kell vennnk a lnyegi modulok tervezsekor, s jra kell gondolnunk azokat, ahogy a rendszer fejldik. Rendkvl fontos emlkeznnk arra, hogy egy fellet nem szolglhat minden clra ugyanolyan jl. Rendszerint megri klnbsget tenni a fejleszti s a felhasznli fellet kztt. Ezenkvl sok nagyobb program szerkezete olyan, hogy clszer a felhasznlk tbbsgnek egyszer, a tapasztaltabb felhasznlknak pedig terjedelmesebb felletet nyjtani. A tapasztalt felhasznlk felletei (a teljes felletek) sokkal tbb szolgltatst ptenek be, mint amennyirl egy tlagos felhasznlnak tudnia kell. Valjban az tlagos felhasznl fellett gy hatrozhatjuk meg, hogy nem ptjk be azokat a fejllomnyokat, amelyek olyan szolgltatsokat rnak le, amelyek ismeretlenek lennnek az tlagos felhasznl szmra. Az tlagos felhasznl kifejezs nem lekicsinyl. Ahol nem muszj szakrtnek lennem, jobban szeretek tlagos felhasznl lenni. gy ugyanis kevesebb a veszekeds.

9.3.3. llomny-rszemek
A tbb fejllomnyos megkzelts gondolata az, hogy minden logikai modult kvetkezetes, nll egysgknt brzoljunk. A program egsznek szempontjbl nzve viszont azon deklarcik tbbsge, melyek ahhoz kellenek, hogy minden logikai egysg teljes legyen, felesleges. Nagyobb programoknl az ilyen flsleg (redundancia) hibkhoz vezethet, amint egy osztlylerst vagy helyben kifejtett fggvnyeket tartalmaz fejllomnyt ugyanabban a fordtsi egysgben (9.2.3) ktszer ptnk be az #include-dal. Kt vlasztsunk lehet. 1. tszervezhetjk a programunkat, hogy eltvoltsuk a flsleget, vagy 2. tallunk valamilyen mdot arra, hogy a fejllomnyok tbbszri beptse megengedett legyen. Az els megkzelts ami a szmolgp vgs vltozathoz vezetett fraszt s valsgos mret programoknl gyakorlatilag kivitelezhetetlen. A flslegre azrt is szksgnk van, hogy a program egyes egysgei elklnlten is rthetek legyenek. A fls #includeok kiszrse s az ennek eredmnyekppen ltrejtt egyszerstett program nagyon elnys lehet mind logikai szempontbl, mind azltal, hogy cskken a fordtsi id. Az sszes

Forrs: http://www.doksi.hu

9. Forrsfjlok s programok

287

elforduls megtallsa azonban ritkn sikerl, gy alkalmaznunk kell valamilyen eszkzt, ami megengedi a fls #include-ok jelenltt. Lehetleg szisztematikusan kell hasznlnunk, mert nem tudhatjuk, hogy a felhasznl mennyire alapos elemzst tart rdemesnek. A hagyomnyos megolds az, hogy a fejllomnyokba llomny-rszemeket (beptsfigyelket, include-guards) illesztnk:
// error.h: #ifndef CALC_ERROR_H #define CALC_ERROR_H namespace Error { // ... } #endif // CALC_ERROR_H

A fjlnak az #ifndef s az #endif kztti tartalmt a fordtprogram nem veszi figyelembe, ha a CALC_ERROR_H mr definilt. Ezrt amikor a fordtprogram az error.h-val elszr tallkozik, beolvassa annak tartalmt, a CALC_ERROR_H pedig rtket kap. Ha ismt tallkozna vele a fordts sorn, msodszor mr nem fogja figyelembe venni. Ez makrkkal val gyeskeds, de mkdik s mindentt jelen van a C s C++ vilgban. A standard knyvtr fejllomnyainak mindegyike tartalmaz llomny-rszemeket. A fejllomnyokat mindenfle krnyezetben hasznljk, a makrnevek tkzse ellen pedig nincs nvtr vdelem. Az llomny-rszemeknek ezrt hossz s csnya neveket szoktam vlasztani. Amint a programoz hozzszokik a fejllomnyokhoz s az llomny-rszemekhez, hajlamos kzvetlenl vagy kzvetve sok fejllomnyt bepteni. Ez nem kvnatos, mg azoknl a C++-vltozatoknl sem, melyek optimalizljk a fejllomnyok feldolgozst. Szksgtelenl hossz fordtsi idt okozhatnak s szmos deklarcit s makrt elrhetv tehetnek, ami kiszmthatatlanul s kedveztlenl befolysolhatja a program jelentst. Csak akkor ptsnk be fejllomnyokat, amikor tnyleg szksg van r.

Forrs: http://www.doksi.hu

288

Alapok

9.4. Programok
A program kln fordtott egysgek gyjtemnye, melyet a szerkesztprogram egyest. Minden, ebben a gyjtemnyben hasznlt fggvnynek, objektumnak, tpusnak stb. egyedi meghatrozssal (defincival) kell rendelkeznie (4.9, 9.2.3) s pontosan egy main() nev fggvnyt kell tartalmaznia (3.2). A program ltal vgzett f tevkenysg a main() meghvsval kezddik s az abbl val visszatrssel r vget. A main() ltal visszaadott int rtk lesz a program visszatrsi rtke, amit a main()-t meghv rendszer megkap. Ezen az egyszer meghatrozson a globlis vltozkat tartalmaz (10.4.9) vagy el nem kapott kivtelt (14.7) kivlt programok esetben finomtanunk kell.

9.4.1. Kezdeti rtkads nem loklis vltozknak


Elvileg a fggvnyeken kvl megadott, nem loklisnak szmt vltozk (azaz a globlis, nvtr-, vagy static osztlyvltozk) a main() meghvsa eltt, a fordtsi egysgben definicijuk sorrendjben kapnak kezdrtket (10.4.9). Ha egy ilyen vltoznak nincs pontosan meghatrozott (explicit) kezdrtke, akkor a tpusnak megfelel alaprtelmezett rtkkel tltdik fel (10.4.2). A beptett tpusok s felsorolsok esetben az alaprtelmezett kezdrtk a 0:
double x = 2; double y; double sqx = sqrt(x+y); // nem loklis vltozk

Itt az x s az y az sqx eltt kap kezdrtket, gy az sqrt(2) hvdik meg. A klnbz fordtsi egysgekben lv globlis vltozk kezdrtkkel val elltsnak sorrendje nem kttt, kvetkezskppen nem blcs dolog ezeknl a kezdrtkek kztt sorrendi fggsgeket ltrehozni. Tovbb nem lehetsges olyan kivtelt sem elkapni, amit egy globlis vltoz kezdeti rtkadsa vltott ki (14.7). ltalban az a legjobb, ha minl kevesebb globlis vltozt hasznlunk; fleg a bonyolult kezdeti rtkadst ignyl globlis vltozk hasznlatt kell korltoznunk. A klnbz fordtsi egysgekben lv globlis vltozk kezdeti rtkkel val feltltsnek sorrendjt szmos mdon knyszerthetjk ki, de nincs kztk olyan, amely egyszerre hordozhat s hatkony is lenne. Fleg a dinamikus csatols knyvtrak (DLL) nem kpesek

Forrs: http://www.doksi.hu

9. Forrsfjlok s programok

289

a bonyolult fggsgekkel rendelkez globlis vltozkkal boldogan egytt lni. Globlis vltozk helyett gyakran hasznlhatunk referencit visszaad fggvnyeket:
int& use_count() { static int uc = 0; return uc; }

A use_count() hvs most globlis vltozknt mkdik, kivve, hogy els hasznlatakor kap kezdrtket (5.5):
void f() { cout << ++use_count(); // ... }

// nvels s kirs

A nem loklis statikus vltozk kezdeti rtkadst brmilyen eljrs vezrelheti, amit az adott nyelvi vltozat arra hasznl, hogy elindtsa a C++ programot. Csak akkor garantlt, hogy a mdszer megfelelen mkdik, ha a main() vgrehajtsra sor kerl, ezrt el kell kerlnnk azon nem loklis vltozk hasznlatt, melyek futsi idej kezdeti rtkadst ignyelnek olyan C++ kdban, amit nem C++ program hasznl. Jegyezzk meg, hogy a kezdrtket konstans kifejezsektl kap vltozk (C.5) nem fgghetnek ms fordtsi egysgben lev objektumok rtktl s nem ignyelnek futsi idej kezdeti rtkadst, gy minden esetben biztonsgosan hasznlhatk. 9.4.1.1. A program befejezse A programok futsa szmos mdon rhet vget: A main()-bl val visszatrssel Az exit() meghvsval Az abort() meghvsval El nem kapott kivtel kivltsval

Tovbb tbbfle hibs felpts s nyelvi vltozattl fgg mdszer ltezik arra, hogy egy program sszeomoljon. Ha a program befejezsre a standard knyvtrbeli exit() fggvnyt hasznljuk, akkor meghvdnak a ltrehozott statikus objektumok destruktorai (10.4.9, 10.2.4). Ha azonban a program a standard knyvtr abort() fggvnyt hasznlja,

Forrs: http://www.doksi.hu

290

Alapok

a destruktorok meghvsra nem kerl sor. Jegyezzk meg: ez azt is jelenti, hogy az exit() nem fejezi be rgtn a programot; destruktorban val meghvsa vgtelen ciklust eredmnyezhet. Az exit() fggvny tpusa
void exit(int);

A main() visszatrsi rtkhez (3.2) hasonlan az exit() paramtere is visszaaddik a rendszernek a program visszatrsi rtkeknt. A nulla sikeres befejezst jelent. Az exit() meghvsa azt jelenti, hogy a hv fggvny loklis vltozinak s az azt hv fggvnyek hasonl vltozinak destruktorai nem hvdnak meg. A loklis objektumok megfelel megsemmistst (14.4.7) egy kivtel dobsa s elkapsa biztostja. Emellett az exit() meghvsa gy fejezi be a programot, hogy az exit() hvjnak nem ad lehetsget arra, hogy megoldja a problmt. Ezrt gyakran az a legjobb, ha a krnyezetet egy kivtel kivltsval elhagyjuk, s megengedjk egy kivtelkezelnek, hogy eldntse, mi legyen a tovbbiakban. A C (s C++) standard knyvtrnak atexit() fggvnye lehetsget ad arra, hogy kdot hajthassunk vgre a program befejezdsekor:
void my_cleanup(); void somewhere() { if (atexit(&my_cleanup)==0) { // norml programbefejezskor a my_cleanup hvdik meg } else { // hopp: tl sok atexit fggvny } }

Ez nagyban hasonlt a globlis vltozk destruktorainak a program befejezdsekor trtn automatikus meghvshoz (10.4.9, 10.2.4). Jegyezzk meg, hogy az atexit() paramternek nem lehet paramtere s nem adhat vissza rtket. Az atexit fggvnyek szmt az adott nyelvi vltozat korltozza; a fggvny nem nulla rtk visszaadsval jelzi, ha ezt a korltot elrtk. Ezek a korltozsok az atexit()-et kevsb hasznlhatv teszik, mint amilyennek els pillantsra ltszik. Az atexit(f) meghvsa eltt ltrehozott objektum destruktora az f meghvsa utn fog meghvdni, az atexit(f) meghvsa utn ltrehozott objektum destruktora pedig az f meghvsa eltt. Az exit(), abort(), s atexit() fggvnyek deklarcijt a <cstdlib> fejllomny tartalmazza.

Forrs: http://www.doksi.hu

9. Forrsfjlok s programok

291

9.5. Tancsok
[1] Hasznljuk fejllomnyokat a felletek brzolsra s a logikai szerkezet kihangslyozsra. 9.1, 9.3.2. [2] Abban a forrsfjlban ptsk be ket (#include), amelyben fggvnyeiket kifejtjk. 9.3.1. [3] Ne adjunk meg globlis egyedeket ugyanazzal a nvvel s hasonl, de klnbz jelentssel klnbz fordtsi egysgekben. 9.2. [4] Kerljk a fejllomnyokban a nem helyben kifejtend fggvnyeket. 9.2.1. [5] Csak globlis hatkrben s nvterekben hasznljuk az #include-ot. 9.2.1. [6] Csak teljes deklarcikat ptsnk be. 9.2.1 [7] Hasznljunk llomny-rszemeket. 9.3.3. [8] A C fejllomnyokat nvterekben ptsk be, hogy elkerljk a globlis neveket. 9.3.2. [9] Tegyk a fejllomnyokat klnllv. 9.2.3. [10] Klnbztessk meg a fejleszti s a felhasznli felletet. 9.3.2. [11] Klnbztessk meg az tlagos s a tapasztalt felhasznlk fellett. 9.3.2. [12] Kerljk az olyan nem loklis objektumok hasznlatt, amelyek futsi idej kezdeti rtkadst ignyelnek olyan kdban, amit nem C++ program rszeknt szndkozunk felhasznlni. 9.4.1.

9.6. Gyakorlatok
1. (*2) Talljuk meg, hol trolja rendszernk a szabvnyos fejllomnyokat. rassuk ki neveiket. Van-e olyan nem szabvnyos fejllomny, amely ezekkel egytt troldik? Be lehet-e pteni nem szabvnyos fejllomnyokat a <> jellst hasznlva? 2. (*2) Hol troldnak a nem szabvnyos foundation knyvtrak fejllomnyai? 3. (*2,5) rjunk programot, amely beolvas egy forrsfjlt s kirja a beptett fjlok neveit. Hasznljunk behzst a beptett fjlok ltal beptett fjlok kirsakor, a befoglals mlysgnek jellsre. Prbljuk ki a programot nhny valdi forrsfjlon (hogy elkpzelsnk legyen a beptett informci nagysgrl). 4. (*3) Mdostsuk az elbbi programot, hogy minden beptett fjlra kirja a megjegyzsek s a nem megjegyzsek sorainak szmt, illetve a nem megjegyzsknt szerepl, reshelyekkel elvlasztott szavak szmt.

Forrs: http://www.doksi.hu

292

Alapok

5. (*2,5) A kls beptsfigyel olyan programelem, amely a megfigyelt fjlon kvl vgzi az ellenrzst, s fordtsonknt csak egyszer vgez beptst. Ksztsnk egy ilyen szerkezeti elemet, tervezznk mdszert a tesztelsre, s fejtsk ki elnyeit s htrnyait a 9.3.3-ban lert llomny-rszemekkel szemben. Van-e a kls beptsfigyelknek brmilyen jelents futsi idbeli elnye a rendszernkben? 6. (*3) Hogyan valsul meg a dinamikus csatols (szerkeszts) a rendszernkben? Milyen megszortsok vonatkoznak a dinamikusan szerkesztett kdra? Milyen kvetelmnyeknek kell, hogy megfeleljen a kd, hogy dinamikusan csatolhat legyen? 7. (*3) Nyissunk meg s olvassunk be 100 fjlt, melyek mindegyike 1500 karaktert tartalmaz. Nyissunk meg s olvassunk be egy 150 000 karakterbl ll fjlt. Tipp: nzzk meg a pldt a 21.5.1 pontban. Van-e eltrs a teljestmnyben? Hny fjl lehet egyszerre megnyitva rendszernkben? Vlaszoljuk meg ezeket a krdseket a beptett fjlok hasznlatval kapcsolatban is. 8. (*2) Mdostsuk a szmolgpet, hogy meg lehessen hvni a main()-bl vagy ms fggvnybl is, egy egyszer fggvnyhvssal. 9. (*2) Rajzoljuk meg a modulfggsgi diagramokat (9.3.2) a szmolgp azon vltozataira, melyek az error()-t hasznltk kivtelek helyett. (8.2.2).

Forrs: http://www.doksi.hu

Msodik rsz Absztrakcis mdszerek

Ebben a rszben azzal foglalkozunk, milyen lehetsgeket nyjt a C++ nyelv j tpusok meghatrozsra s hasznlatra, illetve bemutatjuk az sszefoglal nven objektumorientlt programozsnak s ltalnostott (generikus) programozsnak nevezett eljrsokat.

Fejezetek 10. 11. 12. 13. 14. 15. Osztlyok Opertorok tlterhelse Szrmaztatott osztlyok Sablonok Kivtelkezels Osztlyhierarchik

Forrs: http://www.doksi.hu

... nincs nehezebb, ktesebb kimenetel, veszlyesebb dolog, mint j trvnyek bevezetsrt skraszllni. Mert ellensgei azok, akiknek a rgi trvnyek hasznra vannak, azok pedig, akiknek az j rendelkezsek szolglnak hasznukra, pusztn lagymatag vdelmezi... Niccolo Machiavelli (A fejedelem (vi), Lutter va fordtsa)

Forrs: http://www.doksi.hu

10
Osztlyok
Ezek a tpusok nem elvontak; ugyanannyira valsgosak, mint az int s a float. (Doug McIlroy) Fogalmak s osztlyok Osztlytagok Az elrhetsg szablyozsa Konstruktorok Statikus tagok Alaprtelmezett msols const tagfggvnyek this struct-ok Osztlyon belli fggvnydefincik Konkrt osztlyok Tagfggvnyek s segdfggvnyek Opertorok tlterhelse A konkrt osztlyok hasznlata Destruktorok Alaprtelmezett konstruktorok Loklis vltozk Felhasznli msols new s delete Tagobjektumok Tmbk Statikus trols Ideiglenes vltozk Unik Tancsok Gyakorlatok

10.1. Bevezets
A C++ nyelv osztlyai azt a clt szolgljk, hogy a programoz a beptett adattpusokkal azonos knyelmi szinten hasznlhat j adattpusokat hozhasson ltre. Ezenkvl az rklds (12. fejezet) s a sablonok (13. fejezet) segtsgvel gy szervezhetjk az egymssal kapcsolatban ll osztlyokat, hogy kapcsolataikat hatkonyan hasznlhassuk ki.

Forrs: http://www.doksi.hu

296

Absztrakcis mdszerek

A tpus egy fogalom konkrt brzolsa. A C++ beptett float tpusa pldul a +, -, * stb. mveleteivel egytt a vals szm matematikai fogalmnak egy megkzeltse. Az osztly egy felhasznli tpus. Azrt terveznk j tpust, hogy meghatrozzunk egy fogalmat, amelynek nincs kzvetlen megfelelje a nyelv beptett tpusai kztt. Lehet pldul Trunk_line tpusunk egy telefonos kapcsolatokat kezel programban, Explosion tpusunk egy videjtk szmra, vagy list<Paragraph> tpusunk egy szvegszerkeszt programban. Egy programot knnyebb megrteni s mdostani, ha abban az ltala kezelt fogalmaknak megfelel tpusok szerepelnek. Ha a programoz alkalmas osztlyokat hasznl, a program tmrebb lesz, radsul sokfle kdelemz eljrs hasznlata vlik lehetv. A fordtprogram pldul feldertheti az objektumok nem szablyos hasznlatt, amit msknt csak egy alapos ellenrzs sorn fedezhetnnk fel. j tpus definilsakor az alapvet szempont a megvalsts vletlenszer, esetleges rszleteinek (pldul a troland adatok elrendezsnek) elvlasztsa a tpus helyes hasznlathoz alapveten szksges tulajdonsgoktl, pldul az adatokat elr fggvnyek teljes listjtl. Ez az elvlaszts legjobban gy fejezhet ki, ha az adott tpus adatszerkezett rint sszes kls hasznlatot s bels rendrak fggvnyt csak az adott tpusra vonatkoz programozsi felleten keresztl tesszk elrhetv. Ez a fejezet a viszonylag egyszer, konkrt felhasznli tpusokkal foglalkozik. Idelis esetben ezek csak ltrehozsuk mdjukban klnbznek a beptett tpusoktl, a hasznlat mdjban nem.

10.2. Osztlyok
Az osztly (class) a programoz ltal meghatrozott, ms nven felhasznli tpus. Az albbiakban az osztlyok meghatrozsnak, illetve az osztlyba tartoz objektumok ltrehozsnak s hasznlatnak fbb eszkzeit mutatjuk be.

10.2.1. Tagfggvnyek
Vizsgljuk meg, hogyan brzolnnk a dtum fogalmt egy Date adatszerkezettel (struktrval, struct) s egy sor, ilyen vltozkat kezel fggvnnyel:
struct Date { int d, m, y; }; // brzols

Forrs: http://www.doksi.hu

10. Osztlyok

297

void init_date(Date& d, int, int, int); void add_year(Date& d, int n); void add_month(Date& d, int n); void add_day(Date& d, int n);

// kezdeti rtkads d-nek // n vet ad d-hez // n hnapot ad d-hez // n napot ad d-hez

Az adattpus s ezen fggvnyek kztt nincs kifejezett kapcsolat. Ilyen kapcsolatot azltal hozhatunk ltre, hogy a fggvnyeket tagfggvnyekknt adjuk meg:
struct Date { int d, m, y; void init(int dd, int mm, int yy); void add_year(int n); void add_month(int n); void add_day(int n); // kezdeti rtkads // n v hozzadsa // n hnap hozzadsa // n nap hozzadsa

};

Az osztlydefincin bell bevezetett fggvnyeket (a struct is osztly, 10.2.8) tagfggvnyeknek hvjuk. A tagfggvnyeket az adatszerkezetek tagjainak elrsre vonatkoz szoksos formban alkalmazhatjuk s csak megfelel tpus objektumra:
Date my_birthday; void f() { Date today; today.init(16,10,1996); my_birthday.init(30,12,1950); Date tomorrow = today; tomorrow.add_day(1); // ...

Minthogy a klnbz adatszerkezeteknek azonos nev fggvnyeik is lehetnek, a tagfggvnyek meghatrozsakor meg kell adnunk az adatszerkezet nevt is:
void Date::init(int dd, int mm, int yy) { d = dd; m = mm; y = yy; }

Forrs: http://www.doksi.hu

298

Absztrakcis mdszerek

A tagfggvnyekben a tagokat az objektum kifejezett megadsa nlkl is hasznlhatjuk. Ekkor a nv azon objektum megfelel tagjra vonatkozik, amelyre a tagfggvnyt meghvtuk. Amikor pldul a Date::init() tagfggvnyt alkalmazzuk a today vltozra, az m=mm rtkads a today.m vltozra vonatkozik. Ha ugyanezt a tagfggvnyt a my_birthday vltozra alkalmaznnk, az m=mm rtkads a my_birthday.m vltozra vonatkozna. A tagfggvny mindig tudja, hogy milyen objektumra hvtk meg. A
class X { ... };

kifejezst osztlydefincinak hvjuk, mert egy j tpust hatroz meg. Trtneti okokbl az osztlydefincit nha osztlydeklarciknt emltik. Azon deklarcikhoz hasonlatosan, amelyek nem defincik, az osztlydefincik az #include utasts felhasznlsval tbb forrsllomnyban is szerepeltethetk, feltve, hogy nem srtjk meg az egyszeri definils szablyt (9.2.3).

10.2.2. Az elrhetsg szablyozsa


A Date elz pontbeli deklarcija azon fggvnyek halmazt adja meg, melyekkel a Date tpus objektumot kezelhetjk. Ebbl azonban nem derl ki, hogy kizrlag ezek lehetnek mindazok a fggvnyek, amelyek kzvetlenl fggnek a Date tpus brzolstl s kzvetlenl elrhetik az ilyen tpus objektumokat. A megszortst gy fejezhetjk ki, ha struct helyett class-t hasznlunk:
class Date { int d, m, y; public: void init(int dd, int mm, int yy); void add_year(int n); void add_month(int n); void add_day(int n);

// kezdeti rtkads // n v hozzadsa // n hnap hozzadsa // n nap hozzadsa

};

A public cmke kt rszre osztja az osztly trzst. Az els, privt (private) rszbeli neveket csak a tagfggvnyek hasznlhatjk. A msodik, nyilvnos (public) rsz az osztly nyilvnos fellete. A struct szerkezetek egyszeren olyan osztlyok, melyekben a tagok alaprtelmezett elrhetsge nyilvnos (10.2.8). A tagfggvnyeket a megszokott mdon definilhatjuk s hasznlhatjuk:

Forrs: http://www.doksi.hu

10. Osztlyok

299

inline void Date::add_year(int n) { y += n; }

Mindazonltal a nem tag fggvnyek a privt tagokat nem hasznlhatjk:


void timewarp(Date& d) { d.y -= 200; // hiba: Date::y privt }

Szmos elnnyel jr, ha a tagok elrhetsgt egy pontosan megadott lista fggvnyeire korltozzuk. Pldul, ha egy hiba miatt a Date rvnytelen rtket kap (mondjuk 1985. december 36.-t), akkor biztosak lehetnk abban, hogy ez a hiba csak valamelyik tagfggvnyben lehet. Ebbl kvetkezik, hogy a hibakeress els szakasza, a hiba helynek behatrolsa mr azeltt megtrtnik, hogy a program egyltaln lefutna. Ez egyik esete annak az ltalnos megfigyelsnek, hogy az osztly viselkedsnek brmilyen mdostsa csakis a tagfggvnyek megvltoztatsval rhet el. Pldul ha megvltoztatjuk egy osztly adatbrzolst, akkor elg a tagfggvnyeket ennek megfelelen mdostanunk. Az osztlyt hasznl kd kzvetlenl csak az osztly nyilvnos fellettl fgg, ezrt nem kell jrarni (br lehet, hogy jra kell fordtani). A msik elny, hogy a leend felhasznlnak elg a tagfggvnyek meghatrozst tanulmnyoznia ahhoz, hogy megtudja, hogyan lehet hasznlni az osztlyt. A privt tagok vdelme az osztlytagok nv szerinti elrhetsgnek korltozsn mlik, ezrt a cmek megfelel kezelsvel vagy pontosan meghatrozott tpuskonverzival megkerlhet. Ez persze csals. A C++ a vletlen hibk ellen vd, nem a vdelmi rendszer tudatos megkerlse, a csals ellen. Egy ltalnos cl nyelvben csak hardverszinten lehetne a rosszindulat hasznlat ellen vdekezni, s igazi rendszerekben mg ez is nehezen kivitelezhet. Az init() fggvnyt rszben azrt vettk fel, mert ltalban clszer, ha van egy, az objektumnak rtket ad fggvnynk, rszben pedig azrt, mert az adattagok privtt ttele miatt erre knyszerltnk.

10.2.3. Konstruktorok
Az init()-hez hasonl fggvnyek hasznlata az objektumok kezdeti rtkadsra nem elegns s hibk forrsa lehet. Minthogy sehol sincs lefektetve, hogy egy objektumnak kezdrtket kell adni, a programoz elfelejtheti azt vagy ppen tbbszr is megteheti (mind-

Forrs: http://www.doksi.hu

300

Absztrakcis mdszerek

kt esetben egyformn vgzetes kvetkezmnyekkel). Jobb megolds, ha lehetv tesszk a programoznak, hogy megadjon egy olyan fggvnyt, melynek clja kifejezetten az objektumok elksztse. Mivel az ilyen fggvny ltrehozza az adott tpus rtkeket, konstruktornak (vagyis ltrehoznak, constructor) hvjuk. A konstruktort arrl ismerjk meg, hogy ugyanaz a neve, mint magnak az osztlynak:
class Date { // ... Date(int, int, int); };

// konstruktor

Ha egy osztly rendelkezik konstruktorral, akkor minden, ebbe az osztlyba tartoz objektum kap kezdrtket. Ha a konstruktornak paramterekre van szksge, azokat meg kell adni:
Date today = Date(23,6,1983); Date xmas(25,12,1990); Date my_birthday; Date release1_0(10,12); // rvidtett forma // hiba: nincs kezdrtk // hiba: a harmadik paramter hinyzik

Gyakran clszer, ha a kezdeti rtkads tbbflekppen is lehetsges. Ezt gy rhetjk el, ha tbbfle konstruktor ll rendelkezsre:
class Date { int d, m, y; public: // ... Date(int, int, int); Date(int, int); Date(int); Date(); Date(const char*); };

// nap, hnap, nap // nap, hnap, aktulis v // nap, aktulis hnap s v // alaprtelmezett Date: mai dtum // a dtum karakterlnccal brzolva

A konstruktorokra ugyanazok a tlterhelsi szablyok vonatkoznak, mint ms fggvnyekre (7.4). Amg a konstruktorok kellen klnbznek a paramterek tpusaiban, a fordtprogram ki fogja tudni vlasztani, melyiket kell az egyes meghvsokkor alkalmazni:
Date today(4); Date july4("July 4, 1983"); Date guy("5 Nov"); Date now; // alaprtelmezett kezdeti rtkads az aktulis dtummal

A Date plda esetben megfigyelhetjk a konstuktorok elburjnzst, ami ltalnos jelensg. A programoz egy osztly tervezsekor mindig ksrtst rez j s j fggvnyekkel bvteni azt, mondvn, valakinek gyis szksge lesz rjuk. Tbb gondot ignyel ugyanis

Forrs: http://www.doksi.hu

10. Osztlyok

301

mrlegelni, mire van igazn szksg s arra szortkozni. Ez az odafigyels ugyanakkor ltalban kisebb s rthetbb programokhoz vezet. Az egymssal rokon fggvnyek szmnak cskkentsre az egyik md az alaprtelmezett paramter-rtkek hasznlata (7.5). A Date osztly paramtereinek pldul egy olyan alaprtelmezett rtket adhatunk, melynek jelentse: vegyk az alaprtelmezett today-t.
class Date { int d, m, y; public: Date(int dd =0, int mm =0, int yy =0); // ... }; Date::Date(int dd, int mm, int yy) { d = dd ? dd : today.d; m = mm ? mm : today.m; y = yy ? yy : today.y; } // ellenrizzk, hogy Date rvnyes dtum-e

Ha egy paramter-rtket az alaprtelmezett rtk jelzsre hasznlunk, annak kvl kell esnie a lehetsges rtkek halmazn. A day (nap) s month (hnap) paramterek esetben ez a halmaz vilgosan meghatrozhat, a year (v) meznl azonban a zr rtk nem esik nyilvnvalan a halmazon kvlre. Szerencsre az eurpai naptrban nincs 0-dik v; az idszmtsunk utni els v (year==1) kzvetlenl az idszmtsunk eltti els v (year==-1) utn kvetkezik.

10.2.4. Statikus tagok


A Date tpushoz tartoz knyelmes alaprtelmezett rtket egy jelents rejtett problma rn hoztuk ltre: Date osztlyunk a today nev globlis vltoztl fgg. gy az osztly csak akkor hasznlhat, ha a today vltozt definiltuk s minden kdrszletben megfelelen hasznljuk. Ez a fajta megszorts az osztlyt az eredeti krnyezeten kvl hasznlhatatlann teszi. A felhasznlknak tl sok kellemetlen meglepetsben lesz rszk, amikor ilyen krnyezetfgg osztlyokat prblnak hasznlni s a kd mdostsa is problematikus lesz. Ezzel az egy kis globlis vltozval mg taln megbirkzunk, de ez a stlus vezet az eredeti programozn kvl ms szmra hasznlhatatlan kdhoz. Kerljk el! Szerencsre a kvnt clt elrhetjk a nyilvnosan elrhet globlis vltoz jelentette teherttel nlkl is. Az olyan vltozkat, melyek egy osztlyhoz tartoznak, de annak objektumaihoz nem, statikus tagnak nevezzk. A statikus tagokbl mindig pontosan egy pldny

Forrs: http://www.doksi.hu

302

Absztrakcis mdszerek

ltezik, nem pedig objektumonknt egy, mint a kznsges, nem statikus adattagokbl. Ehhez hasonlan az olyan fggvnyeket, melyek egy adott osztlytaghoz hozzfrnek, de nem szksges objektumra meghvni azokat, statikus tagfggvnynek hvjuk. Tervezzk t az osztlyt gy, hogy megrizzk az alaprtelmezett konstruktor-rtkek szerept, de kzben elkerljk a globlis vltoz hasznlatnak htrnyt:
class Date { int d, m, y; static Date default_date; public: Date(int dd =0, int mm =0, int yy =0); // ... static void set_default(int, int, int); };

A Date konstruktort immr gy hatrozhatjuk meg:


Date::Date(int dd, int mm, int yy) { d = dd ? dd : default_date.d; m = mm ? mm : default_date.m; y = yy ? yy : default_date.y; } // ellenrizzk, hogy Date rvnyes dtum-e

Amikor szksges, mdosthatjuk az alaprtelmezett rtket. Egy statikus tagra ugyangy hivatkozhatunk, mint brmilyen ms tagra, st, akr egy objektum megnevezse nlkl is; ekkor az osztly nevvel minsthetjk:
void f() { Date::set_default(4,5,1945); }

A statikus tagokat mind az adattagokat, mind a fggvnyeket definilni kell valahol:


Date Date::default_date(16,12,1770); void Date::set_default(int d, int m, int y) { Date::default_date = Date(d,m,y); }

Forrs: http://www.doksi.hu

10. Osztlyok

303

Az alaprtelmezett rtk itt Beethoven szletsi dtuma, amg valaki t nem lltja valami msra. Vegyk szre, hogy a Date() jells a Date::default_date rtket szolgltatja:
Date copy_of_default_date = Date();

Kvetkezskppen nincs szksg kln fggvnyre az alaprtelmezett dtum lekrdezshez.

10.2.5. Osztly tpus objektumok msolsa


Alaprtelmezs szerint az osztly tpus objektumok msolhatk s kezdrtkknt egy azonos tpus osztly egy objektumnak msolatt is kaphatjk, mg akkor is, ha konstruktorokat is megadtunk:
Date d = today; // kezdeti rtkads msolssal

Alaprtelmezs szerint az osztly objektum msolata minden tag msolatbl ll. Ha nem ez a megfelel viselkeds egy X osztly szmra, az X::X(const X&) msol konstruktorral megvltoztathatjuk azt. (Erre a 10.4.4.1 pontban rszletesebben is visszatrnk.) Ennek megfelelen az osztly objektumokat alaprtelmezs szerint rtkadssal is msolhatjuk:
void f(Date& d) { d = today; }

Az alaprtelmezett viselkeds itt is a tagonknti msols. Ha ez nem megfelel egy osztly szmra, a programoz megadhatja a megfelel rtkad opertort (10.4.4.1).

10.2.6. Konstans tagfggvnyek


A Date osztlyhoz eddig olyan tagfggvnyeket adtunk, melyek rtket adnak egy Date objektumnak vagy megvltoztatjk azt, de az rtk lekrdezsre sajnos nem adtunk lehetsget. Ezen knnyen segthetnk, ha ksztnk nhny fggvnyt, amelyekkel kiolvashatjuk az vet, a hnapot s a napot:
class Date { int d, m, y; public: int day() const { return d; } int month() const { return m; } int year() const; // ... };

Forrs: http://www.doksi.hu

304

Absztrakcis mdszerek

Vegyk szre a const minstt a fggvnydeklarcikban az (res) paramterlista utn. Ez azt jelenti, hogy ezek a fggvnyek nem vltoztatjk meg az objektum llapott. Termszetesen a fordtprogram megakadlyozza, hogy vletlenl megszegjk ezt az gretet:
inline int Date::year() const { return y++; // hiba: ksrlet tag rtknek mdostsra konstans fggvnyben }

Ha egy konstans tagfggvnyt osztlyn kvl hatrozzuk meg, a const uttagot ki kell rnunk:
inline int Date::year() const // helyes { return y; } inline int Date::year() { return y; } // hiba: a const minst hinyzik a tagfggvny tpusbl

Vagyis a const minsts rsze a Date::day() s Date::year() fggvnyek tpusnak. Egy konstans tagfggvnyt alkalmazhatunk lland (konstans) s vltoz (nem konstans) objektumokra is, a nem konstans tagfggvnyeket viszont csak nem konstans objektumokra:
void f(Date& d, const Date& cd) { int i = d.year(); // rendben d.add_year(1); // rendben int j = cd.year(); cd.add_year(1); // rendben // hiba: cd konstans, rtke nem mdosthat

10.2.7. nhivatkozs
Az add_year(), add_month(), s add_year() llapotfrisst fggvnyeket gy hatroztuk meg, hogy azok nem adnak vissza rtket. Az ilyen, egymssal kapcsolatban lev frisst fggvnyek esetben sokszor hasznos, ha visszaadunk egy, a frisstett objektumra mutat referencit, mert a mveleteket ekkor lncba kapcsolhatjuk (lncolhatjuk).

Forrs: http://www.doksi.hu

10. Osztlyok

305

Tegyk fel, hogy a kvetkezt szeretnnk rni:


void f(Date& d) { // ... d.add_day(1).add_month(1).add_year(1); // ... }

Ezzel egy napot, egy hnapot s egy vet adunk d-hez. Ehhez viszont minden fggvnyt gy kell megadnunk, hogy azok egy Date tpus referencit adjanak vissza:
class Date { // ... Date& add_year(int n); Date& add_month(int n); Date& add_day(int n); // n v hozzadsa // n hnap hozzadsa // n nap hozzadsa

};

Minden (nem statikus) tagfggvny tudja, melyik objektumra hvtk meg, gy pontosan hivatkozhat r:
Date& Date::add_year(int n) { if (d==29 && m==2 && !leapyear(y+n)) { d = 1; m = 3; } y += n; return *this; }

// figyeljnk februr 29-re!

A *this kifejezs azt az objektumot jelenti, amelyre a tagfggvnyt meghvtk. (Egyenrtk a Simula nyelv THIS s a Smalltalk self kifejezsvel.) Egy nem statikus tagfggvnyben a this kulcssz egy mutatt jelent arra az objektumra, amelyre a fggvnyt meghvtk. Az X osztly egy nem const tagfggvnyben a this tpusa X*. Mindazonltal a this nem kznsges vltoz, gy nem lehet a cmt felhasznlni vagy rtket adni neki. Az X osztly egy konstans tagfggvnyben a this tpusa const X* lesz, hogy ne lehessen megvltoztatni magt az objektumot (lsd mg 5.4.1).

Forrs: http://www.doksi.hu

306

Absztrakcis mdszerek

A this hasznlata legtbbszr automatikus. Pldul minden nem statikus tagra val hivatkozs tulajdonkppen a this-t hasznlja, hogy a megfelel objektum tagjt rje el. Az add_year fggvnyt pldul egyenrtk, m fradsgos mdon gy is megadhattuk volna:
Date& Date::add_year(int n) { if (this->d==29 && this->m==2 && !leapyear(this->y+n)) { this->d = 1; this->m = 3; } this->y += n; return *this; }

A this-t meghatrozott (explicit) mdon gyakran lncolt listk kezelsre hasznljuk (pldul 24.3.7.4).

10.2.7.1. Fizikai s logikai konstansok Esetenknt elfordulhat, hogy egy tagfggvny logikailag lland, mgis meg kell vltoztatnia egy tag rtkt. A felhasznl szmra a fggvny nem mdostja az objektum llapott, de valamilyen, a felhasznl ltal kzvetlenl nem lthat rszlet megvltozik. Az ilyen helyzetet gyakran hvjk logikai konstans mivoltnak. A Date osztlyt pldul egy fggvny visszatrsi rtke egy karakterlnccal brzolhatja, melyet a felhasznl a kimenetben felhasznlhat. Egy ilyen brzols felptse idignyes feladat, ezrt rdemes egy pldnyt trolni belle, amit az egymst kvet lekrdezsek mind felhasznlhatnak, amg a Date rtke meg nem vltozik. Ilyen bels gyorsttr (gyorstr, cache) inkbb bonyolultabb adatszerkezeteknl hasznlatos, de nzzk meg, hogyan mkdhetne ez a Date osztly esetben:
class Date { bool cache_valid; string cache; void compute_cache_value(); // ... public: // ... string string_rep() const; };

// gyorstr feltltse

// brzols karakterlnccal

Forrs: http://www.doksi.hu

10. Osztlyok

307

A felhasznl szemszgbl a string_rep fggvny nem vltoztatja meg az objektum llapott, ezrt vilgos, hogy konstans tagfggvnynek kell lennie. Msrszt a gyorsttrat fel kell tlteni a hasznlat eltt. Ezt elrhetjk tpusknyszerts alkalmazsval is:
string Date::string_rep() const { if (cache_valid == false) { Date* th = const_cast<Date*>(this); th->compute_cache_value(); th->cache_valid = true; } return cache; }

// konstans elvetse

Vagyis a const_cast opertort (15.4.2.1) hasznltuk, hogy egy Date* tpus mutatt kapjunk a this-re. Ez aligha elegns megolds, s nem biztos, hogy egy eredetileg is llandknt megadott objektum esetben is mkdik:
Date d1; const Date d2; string s1 = d1.string_rep(); string s2 = d2.string_rep();

// nem meghatrozott viselkeds

A d1 vltoz esetben a string_rep() egyszeren az eredeti tpusra alakt vissza, gy a dolog mkdik. m d2-t konstansknt adtuk meg s az adott nyelvi vltozat esetleg valamilyen memria-vdelmet alkalmaz az lland rtkek megrzsre. Ezrt a d2.string_rep() hvs nem biztos, hogy pontosan meghatrozhat, az adott nyelvi vltozattl fggetlen eredmnnyel fog jrni.

10.2.7.2. A mutable minst Az elbb lert tpusknyszerts (a const minst talaktsa) s a vele jr, megvalststl fgg viselkeds elkerlhet, ha a gyorsttrba kerl adatokat vltozkony-knt (mutable) adjuk meg:
class Date { mutable bool cache_valid; mutable string cache; void compute_cache_value() const; // ... public: // ... string string_rep() const; };

// (vltozkony) gyorstr feltltse

// brzols karakterlnccal

Forrs: http://www.doksi.hu

308

Absztrakcis mdszerek

A mutable trolsi minsts azt jelenti, hogy a tagot gy kell trolni, hogy akkor is mdosthat legyen, ha konstans objektum. Vagyis a mutable azt jelenti, hogy soha nem lland. Ezt felhasznlva egyszersthetnk a string_rep() meghatrozsn:
string Date::string_rep() const { if (!cache_valid) { compute_cache_value(); cache_valid = true; } return cache; }

Ezltal a string_rep()-et megfelelen hasznlatba vehetjk:


Date d3; const Date d4; string s3 = d3.string_rep(); string s4 = d4.string_rep();

// rendben!

A tagok vltozkonyknt val megadsa akkor alkalmas leginkbb, ha az brzolsnak csak egy rsze vltozhat. Ha az objektum logikailag vltozatlan marad, de a tagok tbbsge mdosulhat, jobb a vltoz adatrszt kln objektumba tenni s kzvetett ton elrni. Ezzel a mdszerrel a gyorsttrba helyezett karakterlncot tartalmaz program gy rhat meg:
struct cache { bool valid; string rep; }; class Date { cache* c; void compute_cache_value() const; // ... public: // ... string string_rep() const; }; string Date::string_rep() const { if (!c->valid) { compute_cache_value(); c->valid = true; } return c->rep; } // kezdeti rtkads a konstruktorban (10.4.6) // a gyorstr ltal mutatott elem feltltse

// brzols karakterlnccal

Forrs: http://www.doksi.hu

10. Osztlyok

309

A gyorsttrat tmogat eljrsok az n. lusta vagy takaros kirtkels (lazy evaluation) klnfle formira is tvihetk.

10.2.8. Struktrk s osztlyok


Definci szerint a struktra (struct), olyan osztly, melynek tagjai alaprtelmezs szerint nyilvnosak. Vagyis a
struct s { ...

egyszeren rvidtse az albbinak:


class s { public: ...

A private: elrhetsgi minsts annak jelzsre hasznlhat, hogy a kvetkez tagok privt elrsek, a public: pedig azt mondja, hogy a kvetkez tagok nyilvnosak. Attl eltekintve, hogy a nevek klnbznek, az albbi deklarcik egyenrtkek:
class Date1 { int d, m, y; public: Date1(int dd, int mm, int yy); }; void add_year(int n); // n v hozzadsa

struct Date2 { private: int d, m, y; public: Date2(int dd, int mm, int yy); }; void add_year(int n); // n v hozzadsa

A vlasztott stlust csak a krlmnyek s az egyni zls hatrozza meg. n ltalban azokat az osztlyokat adom meg struct-knt, amelyekben minden tag nyilvnos. Ezekre az osztlyokra gy gondolok, mint amik nem igazi tpusok, csak adatszerkezetek. A konstruktorok s lekrdez fggvnyek nagyon hasznosak lehetnek a struktrk szmra is, de inkbb csak jellsbeli knnyebbsget jelentenek, mintsem a tpus tulajdonsgait garantljk (mint az invarinsok, lsd 24.3.7.1).

Forrs: http://www.doksi.hu

310

Absztrakcis mdszerek

Az osztlyokban nem szksges elszr az adattagokat megadni, st, sokszor jobb azokat a deklarci vgre tenni, hogy kihangslyozzuk a nyilvnos felhasznli felletet alkot fggvnyeket:
class Date3 { public: Date3(int dd, int mm, int yy); void add_year(int n); private: int d, m, y; }; // n v hozzadsa

Valdi kdban, ahol ltalban mind a nyilvnos fellet, mind a tnyleges megvalsts terjedelmesebb, mint a tanknyvi pldkban, rendszerint a Date3 stlust rszestem elnyben. Az elrhetsgi minstseket az osztlydeklarcikon bell tbbszr is hasznlhatjuk:
class Date4 { public: Date4(int dd, int mm, int yy); private: int d, m, y; public: void add_year(int n); };

// n v hozzadsa

Ha azonban a deklarci tbb nyilvnos rszt is tartalmaz (mint a Date4 osztlynl), akkor a kd zavaross vlhat. Tbb privt rsz hasznlata szintn ezt eredmnyezi. Mindazonltal a szmtgp ltal elksztett kdok szmra kedvez, hogy az elrhetsgi minstsek ismtldhetnek.

10.2.9. Osztlyon belli fggvnydefincik


Az osztlyon bell definilt (nem csak deklarlt) fggvnyek helyben kifejtett (inline) tagfggvnynek szmtanak, azaz a fordtprogram a fggvny meghvsa helyett kzvetlenl beilleszti a kdot. Vagyis az osztly meghatrozsn belli kifejts kicsi, de gyakran hasznlt fggvnyek szmra hasznos. Ahhoz az osztly-definicihoz hasonlan, amelyben szerepel, az osztlyon bell kifejtett fggvny is szerepelhet tbb fordtsi egysgben (az #include utastssal beptve). Persze az osztlyhoz hasonlan jelentsnek minden felhasznlsakor azonosnak kell lennie (9.2.3).

Forrs: http://www.doksi.hu

10. Osztlyok

311

Az a stlus, mely szerint az adattagokat az osztly definicijnak vgre helyezzk, kisebb gondhoz vezet az adatbrzolst felhasznl nyilvnos inline fggvnyek tekintetben. Vegyk ezt a pldt:
class Date { public: int day() const { return d; } // ... private: int d, m, y; }; // zavar lehet // return Date::d

Ez szablyos C++-kd, mivel egy osztly egy tagfggvnye az osztly minden tagjra hivatkozhat, mintha az osztly definicija mr a tagfggvny-trzsek beolvassa eltt teljes lett volna. A kdot olvas programozt azonban ez megzavarhatja, ezrt n vagy elreveszem az adatokat, vagy az inline tagfggvnyeket az osztly utn fejtem ki:
class Date { public: int day() const; // ... private: int d, m, y; }; inline int Date::day() const { return d; }

10.3. Hatkony felhasznli tpusok


Az elz Date osztly pldjn bemutattuk az osztlyok meghatrozshoz szksges alapvet nyelvi elemeket. Most az egyszer s hatkony tervezsre helyezzk a hangslyt s azt mutatjuk be, hogy az egyes nyelvi elemek hogyan tmogatjk ezt. Szmos program hasznl egyszer, de srn elfordul elvont fogalmakat, konkrt tpusokkal brzolva: latin vagy knai karaktereket, lebegpontos szmokat, komplex szmokat, pontokat, mutatkat, koordintkat, (mutateltols (offset)) prokat, dtumokat, idpontokat, rtkkszleteket, kapcsolatokat, csompontokat, (rtkegysg) prokat, lemezcmeket, forrskd-helyeket, BCD karaktereket, pnznemeket, vonalakat, tglalapokat, rgztett pontos szmokat, trtrsszel br szmokat, karakterlncokat, vektorokat s tmbket. Gyakran elfordul, hogy egy program kzvetetten tmaszkodik ezen tpusok nmelyikre s mg tbbre kzvetlenl, knyvtrak kzvettsvel.

Forrs: http://www.doksi.hu

312

Absztrakcis mdszerek

A C++ ms programozsi nyelvekkel egyetemben kzvetlenl tmogat nhnyat a fenti tpusok kzl, m szmuk miatt nem lehetsges az sszeset kzvetlenl tmogatni. Egy ltalnos cl programozsi nyelv tervezje nem is lthatja elre az egyes alkalmazsok ignyeit. Teht szksg van olyan eljrsokra, melyekkel a felhasznl adott cl tpusokat adhat meg. Az ilyen tpusokat konkrt tpusoknak vagy konkrt osztlyoknak hvjuk, hogy megklnbztessk ket az absztrakt (elvont) osztlyoktl (12.3), illetve az osztlyhierarchik osztlyaitl (12.2.4 s 12.4). A C++ nyelv egyik kifejezett clja volt, hogy az ilyen felhasznli tpusok megadst s hatkony hasznlatt is tmogassa, mert ezek az elegns programozs alapkvei. Mint ltalban, itt is rvnyes, hogy az egyszer s fldhzragadt sokkal jelentsebb, mint a bonyolult s krmnfont. Ennek fnyben ksztsnk egy jobb dtumosztlyt:
class Date { public: // nyilvnos fellet enum Month { jan=1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec }; class Bad_date { }; Date(int dd =0, Month mm =Month(0), int yy =0); // fggvnyek a Date vizsglathoz int day() const; Month month() const; int year() const; string string_rep() const; void char_rep(char s[ ]) const; static void set_default(int, Month, int); // fggvnyek a Date mdostshoz Date& add_year(int n); Date& add_month(int n); Date& add_day(int n); private: int d, m, y; static Date default_date; }; // n v hozzadsa // n hnap hozzadsa // n nap hozzadsa // brzols // brzols karakterlnccal // brzols C stlus karakterlnccal // kivtelosztly // 0 jelentse "vedd az // alaprtelmezettet"

Forrs: http://www.doksi.hu

10. Osztlyok

313

A vgezhet mveletek ilyen halmaza meglehetsen jellemz a felhasznli adattpusokra. A kvetkezk szerepelnek benne: 1. Egy konstruktor, amely kezdrtket ad az objektumoknak s vltozknak 2. Lekrdez fggvnyek, melyekkel egy Date-et megvizsglhatunk. Ezek const minstse jelzi, hogy nem mdostjk annak az objektumnak vagy vltoznak az llapott, amelyre meghvtk ket. 3. A Date objektumokat s vltozkat kezel fggvnyek, melyek az brzols vagy a konkrt megvalsts ismerete, illetve az egyes elemek szerepvel val bajlds nlkl is meghvhatk. 4. Automatikusan definilt mveletek, melyek segtsgvel a Date-ek szabadon msolhatk. 5. A Bad_date osztly, mellyel a hibk mint kivtelek jelezhetk. A Month (hnap) tpust azrt vezettem be, hogy kezeljem azt a problmt, amit az okoz, hogy emlkeznnk kell r: vajon jnius 7-t amerikai stlusban Date(6,7)-nek vagy eurpai stlusban Date(7,6)-nak kell-e rnunk. Az alaprtelmezett paramter-rtkek kezelsre is gondoltam, ezzel kln eljrs foglalkozik. Gondolkodtam azon, hogy a napok s vek brzolsra a Day-t s a Year-t, mint nll tpusokat bevezessem, hogy a Date(1995,jul,27) s a Date(27,jul,1995) sszekeveredsnek veszlyt elkerljem. Ezek a tpusok azonban nem lennnek annyira hasznosak, mint a Month. Majdnem minden ilyen hiba amgy is kiderl futsi idben nemigen dolgozom olyan dtumokkal, mint a 27-ik v jlius 26-ika. Az 1800 eltti trtnelmi dtumok kezelse annyira bonyolult, hogy jobb trtnsz szakrtkre bzni. Ezenkvl pedig egy valahanyadikt nem lehet rendesen ellenrizni a hnap s az v ismerete nlkl. (Egy alkalmas Year tpus meghatrozsra nzve lsd: 11.7.1.) Az alaprtelmezett dtumot mint rvnyes Date objektumot definilni kell valahol:
Date Date::default_date(22,jan,1901);

A 10.2.7.1-ben emltett gyorsttras (cache) mdszer egy ilyen egyszer tpusnl felesleges, gy kihagytam. Ha mgis szksges, kiegszthetjk vele az osztlyt, mint a felhasznli felletet nem rint megvalstsi rszlettel.

Forrs: http://www.doksi.hu

314

Absztrakcis mdszerek

me egy kicsi elmleti plda arra, hogy lehet Date-eket hasznlni:


void f(Date& d) { Date lvb_day = Date(16,Date::dec,d.year()); if (d.day()==29 && d.month()==Date::feb) { // ... } if (midnight()) d.add_day(1); } cout << "A kvetkez nap:" << d+1 << '\n';

Felttelezzk, hogy a << kimeneti s a + sszead mvelet a Date-ekre definilt; (ezt a 10.3.3-ban valban meg is tesszk). Figyeljk meg a Date::feb jellst. Az f() nem tagfggvnye Date-nek, gy meg kell adni, hogy a Date-nek s nem valami msnak a feb-jrl van sz. Mirt ri meg egy kln tpust megadni egy olyan egyszer dolog szmra, mint egy dtum? Vgl is berhetnnk egy egyszer adatszerkezettel...
struct Date { int day, month, year; };

...s hagynnk, hogy a programozk dntsk el, mit csinlnak vele. De ha ezt tennnk, akkor minden felhasznlnak magnak kellene a Date-ek sszetevit kezelnie: vagy kzvetlenl, vagy kln fggvnyekben. Ez pedig azzal jrna, hogy a dtum fogalma sztszrdna, gy azt nehezebb lenne megrteni, dokumentlni s mdostani. Ha egy fogalmat egyszer adatszerkezetknt bocstunk a felhasznlk rendelkezsre, az szksgszeren kln munkt ignyel tlk. Ezenkvl br a Date tpus ltszlag egyszer, mgis gondot ignyel gy megrni, hogy helyesen mkdjk. Pldul egy Date objektum nvelshez szkvekkel kell trdni, azzal a tnnyel, hogy a hnapok klnbz hosszsgak s gy tovbb (lsd a 10.6[1]-es feladatot). Az v-hnap-nap adatbrzols radsul sok program szmra szegnyes. Ha viszont gy dntnk, hogy megvltoztatjuk, csak a kijellt fggvnyeket kell mdostanunk. Ha a Date-et pldul az 1970. janur elseje utni vagy eltti napok szmval akarnnk brzolni, csak a Date tagfggvnyeit kellene megvltoztatnunk (10.6.[2]).

Forrs: http://www.doksi.hu

10. Osztlyok

315

10.3.1. Tagfggvnyek
Termszetesen minden tagfggvnyt ki kell fejteni valahol. me a Date konstruktornak defincija:
Date::Date(int dd, Month mm, int yy) { if (yy == 0) yy = default_date.year(); if (mm == 0) mm = default_date.month(); if (dd == 0) dd = default_date.day(); int max; switch (mm) { case feb: max = 28+leapyear(yy); break; case apr: case jun: case sep: case nov: max = 30; break; case jan: case mar: case may: case jul: case aug: case oct: case dec: max = 31; break; default: throw Bad_date(); // valaki csalt } if (dd<1 || max<dd) throw Bad_date(); y = yy; m = mm; d = dd;

A konstruktor ellenrzi, hogy a kapott adatok rvnyes dtumot adnak-e. Ha nem, mint pldul a Date(30,Date::Feb,1994) esetben, kivtelt vlt ki (8.3, 14. fejezet), amely jelzi, hogy olyan jelleg hiba trtnt, amit nem lehet figyelmen kvl hagyni. Ha a kapott adatok elfogadhatak, a kezdeti rtkads megtrtnik. Ez meglehetsen jellemz eljrsmd. Msfell ha a Date objektum mr ltrejtt, akkor az tovbbi ellenrzs nlkl felhasznlhat s msolhat. Ms szval a konstruktor fellltja az osztlyra jellemz invarinst (ebben az esetben azt, hogy egy rvnyes dtumrl van sz). A tbbi tagfggvny szmthat erre az llapotra s ktelessge fenntartani azt. Ez a tervezsi mdszer risi mrtkben leegyszerstheti a kdot (lsd a 24.3.7.1-es pontot).

Forrs: http://www.doksi.hu

316

Absztrakcis mdszerek

A Month(0) rtket (amely nem jelent igazi hnapot) a vegyk az alaprtelmezett hnapot jelzsre hasznljuk. A Month felsorolsban megadhatnnk egy rtket kifejezetten ennek jelzsre, de jobb egy nyilvnvalan rvnytelen rtket hasznlni erre a clra, mint hogy olyan ltszatot keltsnk, hogy 13 hnap van egy vben. Vegyk szre, hogy a 0 rtket azrt hasznlhatjuk, mert az a Month felsorols biztostott garantlt rtktartomnyba esik (4.8). Gondolkodtam azon, hogy az adatellenrzst kln, egy is_date() fggvnybe teszem, de ez olyan kdhoz vezetne, amely bonyolultabb s kevsb hatkony, mint a kivtelek elkapsn alapul. Tegyk fel pldul, hogy a >> mvelet rtelmezett a Date osztlyra:
void fill(vector<Date>& aa) { while (cin) { Date d; try { cin >> d; } catch (Date::Bad_date) { // sajt hibakezel continue; } aa.push_back(d); } }

// lsd 3.7.3

Mint az ilyen egyszer konkrt osztlyok esetben szoksos, a tagfggvnyek meghatrozsa a trivilis s a nem tl bonyolult kztt mozog. Pldul:
inline int Date::day() const { return d; } Date& Date::add_month(int n) { if (n==0) return *this; if (n>0) { int delta_y = n/12; int mm = m+n%12; if (12 < mm) { delta_y++; mm -= 12; }

// megjegyzs: int(dec)==12

Forrs: http://www.doksi.hu

10. Osztlyok

317

// most azok az esetek jnnek, amikor Month(mm)-nek nincs d napja y += delta_y; m = Month(mm); return *this;

// negatv n kezelse } return *this;

10.3.2. Segdfggvnyek
Egy osztlyhoz ltalban szmos olyan fggvny tartozhat, melyeket nem szksges magban az osztlyban tagknt megadni, mert nincs szksgk a bels adatbrzols kzvetlen elrsre:
int diff(Date a, Date b); // napok szma az [a,b] vagy [b,a] tartomnyban bool leapyear(int y); Date next_weekday(Date d); Date next_saturday(Date d);

Ha ezeket a fggvnyeket magban az osztlyban fejtennk ki, az bonyolultabb tenn az osztly fellett s a bels adatbrzols esetleges mdostsakor tbb fggvnyt kellene ellenrizni. Hogyan kapcsoldnak az ilyen segdfggvnyek a Date osztlyhoz? Hagyomnyosan a deklarcijukat az osztly deklarcijval azonos fjlba tennnk, gy azon felhasznlk szmra, akiknek szksgk van a Date osztlyra, rgtn ezek is rendelkezsre llnnak a felletet ler fejllomny beptse utn (9.2.1):
#include "Date.h"

A Date.h fejllomny hasznlata mellett vagy helyett a segdfggvnyek s az osztly kapcsolatt gy tehetjk nyilvnvalv, hogy az osztlyt s segdfggvnyeit egy nvtrbe foglaljuk (8.2):
namespace Chrono { class Date { /* ... */}; int diff(Date a, Date b); bool leapyear(int y); // dtumkezel szolgltatsok

Forrs: http://www.doksi.hu

318

Absztrakcis mdszerek

Date next_weekday(Date d); Date next_saturday(Date d); // ...

A Chrono nvtr termszetesen a tbbi kapcsold osztlyt is tartalmazn, pldul a Time (Id) s Stopwatch (Stopper) osztlyokat s azok segdfggvnyeit is. Egy egyetlen osztlyt tartalmaz nvtr hasznlata ltalban csak tlbonyoltott, knyelmetlen kdhoz vezet.

10.3.3. Opertorok tlterhelse


Gyakran hasznos lehet olyan fggvnyeket felvenni, amelyek a hagyomnyos jellsmd hasznlatt biztostjk. Az operator== fggvny pldul lehetv teszi az == egyenlsgi opertor hasznlatt a Date objektumokra:
inline bool operator==(Date a, Date b) // egyenlsg { return a.day()==b.day() && a.month()==b.month() && a.year()==b.year(); }

Egyb kzenfekv jelltek:


bool operator!=(Date, Date); bool operator<(Date, Date); bool operator>(Date, Date); // ... Date& operator++(Date& d); Date& operator--(Date& d); Date& operator+=(Date& d, int n); Date& operator-=(Date& d, int n); Date operator+(Date d, int n); Date operator-(Date d, int n); ostream& operator<<(ostream&, Date d); istream& operator>>(istream&, Date& d); // egyenltlensg // kisebb // nagyobb // Date nvelse egy nappal // Date cskkentse egy nappal // n nap hozzadsa // n nap kivonsa // n nap hozzadsa // n nap kivonsa // d kirsa // beolvass d-be

A Date osztly szmra ezen opertorok hasznlhatsga pusztn knyelmi szempontnak tnik. m sok tpus pldul a komplex szmok (11.3), a vektorok (3.7.1) s a fggvnyszer objektumok (18.4) esetben ezek hasznlata annyira beidegzdtt a felhasznlknl, hogy szinte ktelez megadni ket. Az opertorok tlterhelsvel a 11. fejezet foglalkozik.

Forrs: http://www.doksi.hu

10. Osztlyok

319

10.3.4. A konkrt osztlyok jelentsge


Azrt hvjuk a Date s ms egyszer felhasznli tpusokat konkrt tpusoknak, hogy megklnbztessem azokat az absztrakt osztlyoktl (2.5.4) s az osztlyhierarchiktl (12.3), illetve hogy hangslyozzam az olyan beptett tpusokkal val hasonlsgukat, mint az int vagy a float. Ezeket rtktpusoknak (value types) is nevezik, hasznlatukat pedig rtkkzpont programozsnak (value-oriented programming). Hasznlati modelljk s mgtte lev filozfia nagyon klnbzik attl, amit gyakran objektum-orientlt programozsnak hvnak (2.6.2). A konkrt osztlyok dolga az, hogy egyetlen, viszonylag egyszer dolgot jl s hatkonyan csinljanak. ltalban nem cl, hogy a felhasznlnak eszkzt adjunk a kezbe egy konkrt osztly viselkedsnek megvltoztatsra. gy a konkrt osztlyokat nem sznjuk arra sem, hogy tbbalak (polimorf) viselkedst tanstsanak (2.5.5, 12.2.6). Ha nem tetszik egy konkrt tpus viselkedse, akkor rhatunk egy msikat, ami a kvnalmaknak megfelelen mkdik. Ez az adott tpus jrahasznostsval is elrhetjk; a tpust pontosan gy hasznlhatjuk fel az j tpus megvalstshoz, mint egy int-et:
class Date_and_time { private: Date d; Time t; public: Date_and_time(Date d, Time t); Date_and_time(int d, Date::Month m, int y, Time t); // ... };

A 12. fejezetben trgyalt rkldsi eljrst gy hasznlhatjuk fel egy j tpus meghatrozsra, hogy csak az eltrseket kell lernunk. A Vec osztlyt pldul a vector alapjn kszthetjk el (3.7.2). Egy valamireval fordtprogrammal egy, a Date-hez hasonl konkrt osztly hasznlata nem jr a szksges trolhely vagy a futsi id rejtett nvekedsvel. A konkrt osztlyok mrete fordtsi idben ismert, ezrt az objektumok szmra helyet foglalhatunk a futsi veremben is, azaz a szabad trat rint mveletek nlkl. A memriakioszts is ismert, gy a helyben fordts egyszer feladat. A memriakiosztsnak ms nyelvekkel, pldul a C-vel vagy a Fortrannal val sszeegyeztetse is hasonlan knnyen, kln erfeszts nlkl megoldhat.

Forrs: http://www.doksi.hu

320

Absztrakcis mdszerek

Az ilyen egyszer tpusok megfelel halmaza teljes programok alapjul szolglhat. Ha egy alkalmazsban nincsenek meg a megfelel kicsi, de hatkony tpusok, akkor a tl ltalnos s kltsges osztlyok hasznlata komoly futsi idbeli s trfelhasznls-beli pazarlshoz vezethet. A konkrt tpusok hinya msfell zavaros programokat eredmnyez, illetve azt, hogy minden programoz megrja az egyszer s srn hasznlt adatszerkezeteket kzvetlenl kezel kdot.

10.4. Objektumok
Objektumok tbbflekppen jhetnek ltre: lehetnek automatikus vagy globlis vltozk, osztlyok tagjai stb. Az albbiakban ezeket a lehetsgeket, a rjuk vonatkoz szablyokat, az objektumok kezdllapott bellt konstruktorokat s a hasznlatbl kikerl objektumok eltakartsra szolgl destruktorokat trgyaljuk.

10.4.1. Destruktorok
Az objektumok kezdllapott a konstruktorok lltjk be, vagyis a konstruktorok hozzk ltre azt a krnyezetet, amelyben a tagfggvnyek mkdnek. Esetenknt az ilyen krnyezet ltrehozsa valamilyen erforrs fjl, zr, memriaterlet lefoglalsval jr, amit a hasznlat utn fel kell szabadtani (14.4.7). Kvetkezskppen nmelyik osztlynak szksge van egy olyan fggvnyre, amely biztosan meghvdik, amikor egy objektum megsemmisl, hasonlan ahhoz, ahogy a konstruktor meghvsra is biztosan sor kerl, amikor egy objektum ltrejn: ezek a destruktor (megsemmist, destructor) fggvnyek. Feladatuk ltalban a rendbettel s az erforrsok felszabadtsa. A destruktorok automatikusan meghvdnak, amikor egy automatikus vltozt tartalmaz blokk lefut, egy dinamikusan ltrehozott objektumot trlnek s gy tovbb. Nagyon klnleges esetben van csak szksg arra, hogy a programoz kifejezetten meghvja a destruktort (10.4.11). A destruktor legjellemzbb feladata, hogy felszabadtsa a konstruktorban lefoglalt memriaterletet. Vegynk pldul egy valamilyen Name tpus elemek tblzatt tartalmaz Table osztlyt. A konstruktornak le kell foglalnia az elemek trolshoz szksges memrit. Ha a Table objektum brmilyen mdon trldik, a memrit fel kell szabadtani, hogy mshol fel lehessen majd hasznlni. Ezt gy rhetjk el, hogy megrjuk a konstruktort kiegszt fggvnyt:
class Name { const char* s; // ... };

Forrs: http://www.doksi.hu

10. Osztlyok

321

class Table { Name* p; size_t sz; public: Table(size_t s = 15) { p = new Name[sz = s]; } ~Table() { delete[ ] p; } Name* lookup(const char *); bool insert(Name*);

// konstruktor // destruktor

};

A destruktort jelent ~Table() jells a komplemenskpzst jell ~ szimblumot hasznlva utal a destruktornak a Table() konstruktorhoz val viszonyra. Az sszetartoz konstruktordestruktor pr meghatrozsa a C++-ban szoksos eljrs vltoz mret objektumok megvalstsra. A standard knyvtr troli, pldul a map, ennek a mdszernek valamelyik vltozatt hasznljk, hogy az elemeik szmra trolhelyet biztostsanak, ezrt a programoz a kvetkezkben lertakra tmaszkodik, amikor valamelyik standard knyvtrbeli trolt hasznlja.(gy viselkedik pldul a szabvnyos string osztly is.) A lertak alkalmazhatak a destruktor nlkli osztlyokra is. Ezekre gy tekinthetnk, mint amelyeknl egy olyan destruktorunk van, amely nem csinl semmit.

10.4.2. Alaprtelmezett konstruktorok


Hasonlkppen a legtbb tpust gy tekinthetjk, mint amelynek van alaprtelmezett konstruktora. Az alaprtelmezett konstruktor az, amelyiket paramter nlkl hvhatjuk meg. Minthogy a fenti pldban a 15 mint alaprtelmezett rtk adott, a Table::Table(size_t) fggvny alaprtelmezett konstruktor. Ha a programoz megadott alaprtelmezett konstruktort, akkor a fordtprogram azt fogja hasznlni, msklnben szksg esetn megprbl ltrehozni egyet. A fordtprogram ltal ltrehozott alaprtelmezett konstruktor automatikusan meghvja az osztly tpus tagok s a bzisosztlyok (12.2.2) alaprtelmezett konstruktort:
struct Tables { int i; int vi[10]; Table t1; Table vt[10]; }; Tables tt;

Forrs: http://www.doksi.hu

322

Absztrakcis mdszerek

Itt tt kezdrtkkel val feltltse fordts kzben ltrehozott alaprtelmezett konstruktor segtsgvel trtnik, amely a Table(15)-t hvja meg tt.t1-re s tt.vt minden egyes elemre. Msrszt tt.i s tt.vi elemei nem kapnak kezdrtket, mert ezek az objektumok nem osztly tpusak. Az osztlyok s a beptett tpusok egymstl eltr kezelsmdjnak a Cvel val egyeztets s a futsi id nvelstl val tartzkods az oka. Mivel a const-ok s a referencik ktelezen kezdrtket kell, hogy kapjanak (5.5, 5.4), az ilyeneket tartalmaz tagoknak nem lehet alaprtelmezett konstruktora, hacsak a programoz kifejezetten nem gondoskodik konstruktorrl (10.4.6.1):
struct X { const int a; const int& r; }; X x; // hiba: nincs alaprtelmezett konstruktor X szmra

Az alaprtelmezett konstruktorok kzvetlen mdon is hvhatk (10.4.10). A beptett tpusoknak szintn van alaprtelmezett konstruktoruk (6.2.8).

10.4.3. Ltrehozs s megsemmists


Tekintsk t a klnbz mdokat: hogyan hozhatunk ltre objektumot s ksbb az hogyan semmisl meg. Objektum a kvetkez mdokon hozhat ltre: 10.4.4 Nvvel elltott automatikus objektumknt, amely akkor keletkezik, amikor a program vgrehajtsa sorn deklarcija kirtkeldik, s akkor semmisl meg, amikor a program kilp abbl a blokkbl, amelyen bell a deklarci szerepelt. Szabad trbeli objektumknt, amely a new opertor hasznlatval jn ltre s a delete opertor hasznlatval semmisl meg. Nem statikus tagobjektumknt, amely egy msik osztly objektum tagjaknt jn ltre s azzal egytt keletkezik, illetve semmisl meg. Tmbelemknt, amely akkor keletkezik s semmisl meg, amikor a tmb, melynek eleme. Loklis statikus objektumknt, amely akkor jn ltre, amikor a program vgrehajtsa sorn elszr tallkozik a deklarcijval s egyszer semmisl meg: a program befejezsekor. Globlis, nvtrbeli vagy statikus osztly-objektumknt, amely egyszer, a program indulsakor jn ltre s a program befejezsekor semmisl meg.

10.4.5 10.4.6 10.4.7 10.4.8

10.4.9

Forrs: http://www.doksi.hu

10. Osztlyok

323

10.4.10 Ideiglenes objektumknt, amely egy kifejezs kirtkelsekor jn ltre s a teljes kifejezs vgn, melyben elfordult, semmisl meg. 10.4.11 Felhasznl ltal rt fggvnnyel vgzett, paramterekkel vezrelt lefoglalsi mvelet segtsgvel nyert, a memriba helyezett objektumknt. 10.4.12 Uni tagjaknt, amelynek nem lehet sem konstruktora, sem destruktora. Ez a felsorols nagyjbl a fontossg sorrendjben kszlt. A kvetkez alpontokban rszletesen elmagyarzzuk az objektumok ltrehozsnak ezen vltozatait s hasznlatukat.

10.4.4. Loklis vltozk


A loklis vltozk konstruktora minden alkalommal vgrehajtdik, valahnyszor a vezrls fonala keresztlhalad a vltoz deklarcijn, a destruktor vgrehajtsra pedig akkor kerl sor, amikor kilpnk a vltoz blokkjbl. A loklis vltozk destruktorai konstruktoraik sorrendjhez viszonytva fordtott sorrendben hajtdnak vgre:
void f(int i) { Table aa; Table bb; if (i>0) { Table cc; // ... } Table dd; // ... }

Itt aa, bb s dd ebben a sorrendben keletkeznek az f() meghvsakor s a dd, bb, aa sorrendben semmislnek meg, amikor a vezrls kilp az f()-bl. Ha egy hvsnl i>0, a cc a bb utn jn ltre, s dd ltrejtte eltt semmisl meg.

10.4.4.1. Objektumok msolsa Ha t1 s t2 a Table osztlyba tartoz objektumok, t2=t1 alaprtelmezs szerint t1-nek tagonknti tmsolst jelenti t2-be (10.2.5). Ha nem brljuk fell ezt az alaprtelmezett viselkedst, meglep (s rendszerint nemkvnatos) hats lphet fel, ha olyan osztly objektumaira alkalmazzuk, melynek mutat tagjai vannak. A tagonknti msols rendszerint nem megfelel olyan objektumok szmra, amelyek egy konstruktordestruktor pr ltal kezelt erforrsokat tartalmaznak:

Forrs: http://www.doksi.hu

324

Absztrakcis mdszerek

void h() { Table t1; Table t2 = t1; Table t3; } t3 = t2;

// kezdeti rtkads msolssal: problms // rtkads msolssal: problms

Itt a Table alaprtelmezett konstruktora ktszer hvdik meg: egyszer t1-re s egyszer t3-ra. A t2-re nem hvdik meg, mert ez a vltoz a t1-bl val msolssal kapott kezdrtket. A Table destruktor viszont hromszor hvdik meg: t1-re, t2-re s t3-ra is. Alaprtelmezs szerint az rtkads tagonknti msolst jelent, gy a h() fggvny vgn t1, t2 s t3 mindegyike arra a nvtmbre hivatkoz mutatt fogja tartalmazni, amely t1 ltrejttekor kapott helyet a szabad trban. A mutat, mely a t3 ltrejttekor kijellt nvtmbre mutat, nem marad meg, mert a t3=t2 rtkads kvetkeztben fellrdik, gy az ltala elfoglalt trterlet a program szmra rkre elvsz, hacsak nincs automatikus szemtgyjts (10.4.5). Msrszt a t1 rszre ltrehozott tmb t1-ben, t2-ben s t3-ban egyarnt megjelenik, teht hromszor is trldik. Ez nem meghatrozott s valsznleg katasztroflis eredmnyhez vezet. Az ilyen anomlik elkerlhetk, ha megadjuk, mit jelent egy Table objektum msolsa:
class Table { // ... Table(const Table&); Table& operator=(const Table&); };

// msol konstruktor // msol rtkads

A programoz brmilyen alkalmas jelentst meghatrozhat ezen msol mveletek szmra, de az ilyen tpus trolk esetben a msol mvelet hagyomnyos feladata az, hogy lemsolja a tartalmazott elemeket (vagy legalbbis a felhasznl szmra gy tesz, mintha ez a msols megtrtnt volna, lsd 11.12):
Table::Table(const Table& t) { p = new Name[sz=t.sz]; for (int i = 0; i<sz; i++) p[i] = t.p[i]; } Table& Table::operator=(const Table& t) { if (this != &t) { delete[ ] p; // msol konstruktor

// rtkads // vakodjunk az n-rtkadstl: t = t

Forrs: http://www.doksi.hu

10. Osztlyok

325

} return *this;

p = new Name[sz=t.sz]; for (int i = 0; i<sz; i++) p[i] = t.p[i];

Mint majdnem mindig, a msol konstruktor s az rtkad mvelet itt is jelentsen eltr. Ennek alapvet oka az, hogy a msol konstruktor le nem foglalt memrit kszt fel a felhasznlsra, mg az rtkad mveletnek egy mr ltrehozott objektumot kell helyesen kezelnie. Az rtkadst bizonyos esetekben optimalizlni lehet, de az rtkad opertor ltalnos clja egyszer: vdekezni kell a sajt magval val rtkads ellen, trlni kell a rgi elemeket, elkszteni s bemsolni az j elemeket. ltalban minden nem statikus tagot msolni kell (10.4.6.3.)

10.4.5. A szabad tr
A dinamikusan kezelt memriaterleten, a szabad trban ltrehozott objektumok konstruktort a new opertor hvja meg, s ezek az objektumok addig lteznek, amg a rjuk hivatkoz mutatra nem alkalmazzuk a delete opertort:
int main() { Table* p = new Table; Table* q = new Table; delete p; delete p; // valsznleg futsi idej hibt okoz

A Table::Table() konstruktort ktszer hvjuk meg, csakgy, mint a Table::~Table() destruktort. Sajnos azonban ebben a pldban a new-k s delete-ek nem felelnek meg egymsnak: a p ltal hivatkozott objektumot ktszer trltk, mg a q ltal mutatottat egyszer sem. Nyelvi szempontbl egy objektum nem trlse nem hiba, mindssze a memria pazarlsa, mindazonltal egy hosszan fut programnl az ilyen memriaszivrgs vagy memrialyuk (memory leak) slyos s nehezen felderthet hiba. Szerencsre lteznek az ilyesfajta memriaszivrgst keres eszkzk is. A p ltal mutatott objektum ktszeri trlse slyos hiba; a program viselkedse nem meghatrozott s nagy valsznsggel katasztroflis lesz.

Forrs: http://www.doksi.hu

326

Absztrakcis mdszerek

Bizonyos C++-vltozatok automatikusan jrahasznostjk az elrhetetlen objektumok ltal elfoglalt memrit (ezek a szemtgyjtst alkalmaz megvalstsok), de viselkedsk nem szabvnyostott. Ha van is szemtgyjts, a delete opertor ktszeri meghvsa egyben a destruktor (ha van ilyen) ktszeri meghvst fogja eredmnyezi, gy az objektum ktszer trldik, ami ilyenkor is slyos hiba. A legtbb esetben az objektumok ezen viselkedse csak aprbb knyelmetlensget jelent. Jelesl, ahol van szemtgyjts, ott is a csak memria-felszabadtst vgz destruktorokat lehet megtakartani. Ennek az egyszerstsnek a hordozhatsg elvesztse az ra, st bizonyos programoknl a futsi id nvekedse s a viselkeds megjsolhatatlansga is (C.9.1). Miutn egy objektumot a delete mvelettel trltnk, brmilyen hozzfrsi ksrlet az objektumhoz hibnak szmt. Sajnos az egyes nyelvi vltozatok nem kpesek megbzhat mdon jelezni az ilyen hibkat. A programoz megszabhatja, hogyan trtnjk a new hasznlata esetn a memria lefoglalsa, illetve annak a delete-tel val felszabadtsa (6.2.6.2 s 15.6). Lehetsges a lefoglals, a konstruktorok s a kivtelek egyttmkdsnek a megadsa is (14.4.5 s 19.4.5). A szabad trban lev tmbket a 10.4.7. trgyalja.

10.4.6. Osztly tpus tagok


Nzznk egy osztlyt, amely egy kisebb cgrl trolhat adatokat:
class Club { string name; Table members; Table officers; Date founded; // ... Club(const string& n, Date fd); };

A Club osztly konstruktornl paramterknt meg kell adni a nevet s az alapts dtumt. Az osztlytagok konstruktorainak paramtereit a tartalmaz osztly konstruktordefinicijnak tag-kezdrtk listjban (member initializer) adjuk meg:
Club::Club(const string& n, Date fd) : name(n), members(), officers(), founded(fd) { // ... }

Forrs: http://www.doksi.hu

10. Osztlyok

327

A tagok kezdrtk-listjt kettspont elzi meg s az egyes tagoknak kezdrtket ad kifejezseket vesszk vlasztjk el. A tagok konstruktorainak vgrehajtsa megelzi a tartalmaz osztly sajt konstruktora trzsnek vgrehajtst. A konstruktorok a tagoknak az osztly deklarcijban elfoglalt sorrendjben s nem a kezdrtket ad kifejezseknek a listban val felsorolsi sorrendjben hajtdnak vgre. Az esetleges zavarok elkerlse rdekben nyilvn clszer a tagokat a deklarciban elfoglalt sorrendjkben felvenni a kezdrtk-ad kifejezsek listjra. A tagok destruktorai a konstruktorok sorrendjvel ellenkez sorrendben hvdnak meg. Ha egy tag konstruktornak nincs szksge paramterre, nem szksges felvenni a listra, gy a kvetkez kdrszlet egyenrtk az elz pldabelivel:
Club::Club(const string& n, Date fd) : name(n), founded(fd) { // ... }

A Table::Table konstruktor a Club::officers tagot mindkt esetben a 15-tel, mint alaprtelmezett paramterrel hozza ltre. Ha egy osztlynak osztly tpus tagjai vannak, az osztly megsemmistsekor elszr sajt destruktor fggvnynek (ha van ilyen) trzse hvdik meg, majd a tagok destruktorai a deklarcival ellenttes sorrendben. A konstruktor alulrl felfel haladva (a tagokat elszr) pti fel a tagfggvnyek vgrehajtsi krnyezett, a destruktor pedig fellrl lefel (a tagokat utoljra) bontja le azt.

10.4.6.1. A tagok szksgszer kezdeti rtkadsa Azon tagok feltltse kezdrtkkel szksgszer, amelyeknl a kezdeti rtkads klnbzik az egyszer rtkadstl azaz az alaprtelmezett konstruktor nlkli osztlyba tartoz, a const s a referencia tpus tagok:
class X { const int i; Club c; Club& pc; // ... X(int ii, const string& n, Date d, Club& c) : i(ii), c(n,d), pc(c) { } };

Forrs: http://www.doksi.hu

328

Absztrakcis mdszerek

Ezen tagok kezdeti rtkadsra nincs egyb lehetsg, s hiba azt nem megtenni is. A legtbb tpus esetben azonban a programoz vlaszthat a kezdeti s a sima rtkads kzl. Ilyenkor n ltalban a tag-kezdrtk lists megoldst vlasztom, hogy egyrtelm legyen a kezdeti rtkads tnye. Ez a mdszer radsul hatkonyabb is:
class Person { string name; string address; // ... Person(const Person&); Person(const string& n, const string& a); }; Person::Person(const string& n, const string& a) : name(n) { address = a; }

Itt a name az n egy msolatval kap kezdrtket. Msfell az address elszr egy res karakterlnccal tltdik fel, majd rtkl az a egy msolatt kapja.

10.4.6.2. Konstans tagok Egy statikus, egsz tpus konstans tagot lehetsges a deklarciban egy kezdrtk-ad konstans kifejezssel is feltlteni:
class Curious { public: static const int c1 = 7; static int c2 = 11; const int c3 = 13; static const int c4 = f(17); static const float c5 = 7.0; // ... };

// rendben, de ne felejtsk el a meghatrozst // hiba: nem lland // hiba: nem statikus // hiba: a kezdrtk-ad nem lland // hiba: a kezdrtk-ad nem egsz rtk

Akkor s csak akkor, ha a kezdrtket kapott tagot memriban trolt objektumknt hasznljuk, szksges, hogy az ilyen tag (de csak egy helyen) definilt legyen, de ott nem szabad megismtelni a kezdrtk-ad kifejezst:
const int Curious::c1; // szksges, de a kezdrtk-ad nem szerepelhet itt mg egyszer

const int* p = &Curious::c1; // rendben: Curious::c1 meghatrozott

Forrs: http://www.doksi.hu

10. Osztlyok

329

Msik megoldsknt, jelkpes llandknt hasznlhatunk felsorol konstanst (4.8, 14.4.6, 15.3) is az osztly deklarcijn bell, ha szksges:
class X { enum { c1 = 7, c2 = 11, c3 = 13, c4 = 17 }; // ... };

gy a programoz nem fog ksrtsbe esni, hogy az osztlyban vltozknak, lebegpontos szmoknak stb. adjon kezdrtket.

10.4.6.3. Tagok msolsa Az alaprtelmezett msol konstruktor s az alaprtelmezett msol rtkads (10.4.4.1) az osztly sszes tagjt msolja. Ha ez nem lehetsges, az ilyen osztly objektum msolsi ksrlete hiba:
class Unique_handle { private: // a msol mveleteket privtt tesszk, megelzend az // alaprtelmezett msolst (11.2.2) Unique_handle(const Unique_handle&); Unique_handle& operator=(const Unique_handle&); public: // ... }; struct Y { // ... Unique_handle a; }; Y y1; Y y2 = y1;

// explicit kezdrtket ignyel

// hiba: Y::a nem msolhat

Ezenkvl az alaprtelmezett rtkads nem jhet ltre a fordtskor, ha az osztly egy nem statikus tagja: referencia, konstans, vagy olyan felhasznli tpus melynek nincsen msol rtkadsa.

Forrs: http://www.doksi.hu

330

Absztrakcis mdszerek

Jegyezzk meg, hogy a referencia tpus tagok ugyanarra az objektumra hivatkoznak az eredeti objektumban s a msolatban is. Ez gond lehet, ha a hivatkozott objektumot trlni kell. Ha msol konstruktort runk, gyeljnk arra, hogy minden tagot msoljunk, amelyet szksges. Alaprtelmezs szerint az elemek alaprtelmezett mdon kapnak kezdrtket, de sokszor nem erre van szksg egy msol konstruktorban:
Person::Person(const Person& a) : name(a.name) { } // vigyzat!

Itt elfelejtettem az address tagot msolni, gy az alaprtelmezs szerinti res karakterlncot kapja kezdrtkknt. Ha j taggal bvtnk egy osztlyt, ne felejtsk el ellenrizni, hogy vannak-e olyan felhasznl ltal megadott konstruktorok, amelyeket az j tagok kezdeti rtkadsra s msolsra val tekintettel meg kell vltoztatni.

10.4.7. Tmbk
Ha egy osztly egy tagjnak van alaprtelmezett, azaz paramter nlkl hvhat konstruktora, akkor ilyen osztly objektumok tmbjt is meghatrozhatjuk:
Table tbl[10];

A fenti egy 10 Table elembl ll tmbt hoz ltre s minden elemet a Table::Table() konstruktorral, a 15 rtk alaprtelmezett paramterrel tlt fel. A kezdrtk-lista (5.2.1, 18.6.7) alkalmazsn kvl nincs ms md egy tmb elemeinek konstruktorai szmra (nem alaprtelmezett) paramtereket megadni. Ha felttlenl szksges, hogy egy tmb tagjai klnbz kezdrtket kapjanak, rjunk olyan alaprtelmezett konstruktort, amely ellltja a kvnt rtkeket:
class Ibuffer { string buf; public: Ibuffer() { cin>>buf; } // ... }; void f() { Ibuffer words[100]; // ... }

// minden elem a cin-rl kap kezdrtket

Az ilyen trkkket azonban ltalban jobb elkerlni.

Forrs: http://www.doksi.hu

10. Osztlyok

331

Amikor egy tmb megsemmisl, az sszes elemre meghvdik a destruktor. Ha nem new mvelettel ltrehozott tmbrl van sz, akkor ez automatikusan trtnik. A C nyelvhez hasonlan a C++ sem klnbzteti meg az egyedi elemre s a tmb kezdelemre hivatkoz mutatt (5.3), ezrt a programoznak meg kell adnia, hogy egyedi elemet vagy tmbt kell-e trlni:
void f(int sz) { Table* t1 = new Table; Table* t2 = new Table[sz]; Table* t3 = new Table; Table* t4 = new Table[sz]; delete t1; delete[ ] t2; delete[ ] t3; delete t4; // helyes // helyes // helytelen; problma // helytelen; problma

A tmbk s egyedi elemek dinamikus trterleten val elhelyezse az adott nyelvi vltozattl fgg. Ezrt a klnbz vltozatok klnbzkppen fognak viselkedni, ha hibsan hasznljuk a delete s delete[ ] opertorokat. Egyszer s rdektelen esetekben, mint az elz plda, a fordt szreveheti a hibt, de ltalban futtatskor fog valami csnya dolog trtnni. A kifejezetten tmbk trlsre szolgl delete[ ] logikailag nem szksges. Elkpzelhet lenne, hogy a szabad trtl megkveteljk, hogy minden objektumrl tartsa nyilvn, hogy egyedi objektum avagy tmb. Ekkor a nyilvntarts terht levennnk a programoz vllrl, de ez a ktelezettsg egyes C++-vltozatokban jelents memria- s futsi id-tbbletet jelentene. Ha az olvas tl nehzkesnek tallja a C stlus tmbk hasznlatt, itt is hasznlhat helyettk olyan osztlyokat, mint a vector (3.7.1, 16.3):
void g() { vector<Table>* p1 = new vector<Table>(10); Table* p2 = new Table; delete p1; delete p2;

Forrs: http://www.doksi.hu

332

Absztrakcis mdszerek

10.4.8. Loklis statikus adatok


A loklis statikus objektumok (7.1.2) konstruktora akkor hajtdik vgre, amikor a vgrehajtsi szl elszr halad keresztl az objektum meghatrozsn:
void f(int i) { static Table tbl; // ... if (i) { static Table tbl2; // ... } } int main() { f(0); f(1); f(2); // ... }

Itt tbl konstruktora f() els meghvsakor hvdik meg. Mivel tbl-t statikusknt adtuk meg, gy nem semmisl meg, amikor f()-bl visszatr a vezrls s nem jn jra ltre f() msodik meghvsakor. Mivel a tbl2 vltoz deklarcijt tartalmaz blokk nem hajtdik vgre az f(0) meghvskor, tbl2 is csak f(1) vgrehajtsakor jn ltre, a blokk jbli vgrehajtsakor nem. A loklis statikus objektumok destruktorai akkor hvdnak meg, amikor a program lell (9.4.1.1). Hogy pontosan mikor, az nincs meghatrozva.

10.4.9. Nem loklis adatok


A fggvnyeken kvl meghatrozott (azaz globlis, nvtrbeli s osztlyhoz tartoz statikus) vltozk a main() fggvny meghvsa eltt jnnek ltre (s kapnak kezdrtket), s minden ltrehozott objektum destruktora a main() fggvnybl val kilps utn vgre fog hajtdni. A dinamikus knyvtrak hasznlata (dinamikus csatols) kiss bonyolultabb teszi ezt, hiszen ilyenkor a kezdeti rtkadsra akkor kerl sor, amikor a dinamikus kd a fut programhoz kapcsoldik.

Forrs: http://www.doksi.hu

10. Osztlyok

333

A fordtsi egysgeken bell a nem loklis objektumok konstruktorainak vgrehajtsa a definicijuk sorrendjben trtnik:
class X { // ... static Table memtbl; }; Table tbl; Table X::memtbl; namespace Z { Table tbl2; }

A konstruktorok vgrehajtsi sorrendje a kvetkez: tbl, X::memtbl, Z::tbl2. Vegyk szre, hogy a definci s nem a deklarci sorrendje szmt. A destruktorok a konstruktorokkal ellenttes sorrendben hajtdnak vgre: Z::tbl2, X::memtbl, tbl. Nincs nyelvi vltozattl fggetlen meghatrozsa annak, hogy az egyes fordtsi egysgek nem loklis objektumai milyen sorrendben jnnek ltre:
// file1.c: Table tbl1; // file2.c: Table tbl2;

Az, hogy tbl1 vagy tbl2 fog elbb ltrejnni, a C++ adott vltozattl fgg, de a sorrend azon bell is vltozhat. Dinamikus csatols hasznlata vagy akr a fordtsi folyamat kis mdostsa is megvltoztathatja a sorrendet. A destruktorok vgrehajtsi sorrendje is hasonlan vltozatfgg. Knyvtrak tervezsekor szksges vagy egyszeren knyelmes lehet egy olyan, konstruktorral s destruktorral br tpus elksztse, amely kizrlag a kezdeti rtkads s rendraks cljt szolglja. Ilyen tpus adatot csak arra clra fogunk hasznlni, hogy egy statikus objektum szmra memriaterletet foglaljunk le azrt, hogy lefusson a konstruktora s a destruktora:
class Zlib_init { Zlib_init(); ~Zlib_init(); }; // Zlib elksztse hasznlatra // Zlib utni takarts

Forrs: http://www.doksi.hu

334

Absztrakcis mdszerek

class Zlib { static Zlib_init x; // ... };

Sajnos egy tbb fordtsi egysgbl ll program esetben nincs garancia arra, hogy egy ilyen objektum kezdeti rtkadsa az els hasznlat eltt megtrtnik s a destruktor az utols hasznlat utn fut le. Egyes C++-vltozatok biztosthatjk ezt, de a legtbb nem. Programozi szinten azonban lehetsges azt a megoldst alkalmazni, amit a nyelvi vltozatok ltalban a loklis statikus objektumokra alkalmaznak: egy-egy, az els hasznlatot figyel kapcsolt:
class Zlib { static bool initialized; static void initialize() { /* kezdeti rtkads */ initialized = true; } public: // nincs konstruktor void f() { if (initialized == false) initialize(); // ... } // ...

};

Ha sok fggvnyben kell lekrdezni az els hasznlatot figyel kapcsolt, az fraszt feladat lehet, de megoldhat. Ez a mdszer azon alapul, hogy a konstruktor nlkli statikus objektumok 0 kezdrtket kapnak. A dolog akkor vlik igazn problematikuss, ha az objektum els hasznlata egy vgrehajtsi idre rzkeny fggvnyben trtnik, ahol az ellenrzs s szksg esetn a kezdeti rtkads tl sok idt vehet ignybe. Ilyenkor tovbbi trkkkre van szksg (21.5.2). Egy lehetsges msik megkzelts, hogy az egyes objektumokat fggvnyekkel helyettestjk (9.4.1):
int& obj() { static int x = 0; return x; } // kezdeti rtkads els hasznlatkor

Az els hasznlatot figyel kapcsolk nem kezelnek minden elkpzelhet helyzetet. Lehetsges pldul olyan objektumokat megadni, amelyek a kezdeti rtkads alatt egymsra hivatkoznak az ilyesmit jobb elkerlni. Ha mgis ilyen objektumokra van szksg, akkor vatosan, fokozatosan kell ltrehozni azokat. Egy msik problma, hogy az utols hasznlatot nem tudjuk egy jelzvel jelezni. Ehelyett lsd 9.4.1.1 s 21.5.2.

Forrs: http://www.doksi.hu

10. Osztlyok

335

10.4.10. Ideiglenes objektumok


Ideiglenes objektumok legtbbszr aritmetikai kifejezsekbl jnnek ltre. Pldul az x*y+z kifejezs kirtkelse sorn egy ponton az x*y rszeredmnyt valahol trolni kell. Hacsak nem a program gyorstsn dolgozik (11.6), a programoz ritkn kell, hogy az ideiglenes objektumokkal trdjk, habr ez is elfordul (11.6, 22.4.7). Egy ideiglenes objektum, hacsak nincs referencihoz ktve vagy nem egy nevestett objektumnak ad kezdrtket, trldik a tartalmaz teljes kifejezs kirtkelse vgn. A teljes kifejezs olyan kifejezs, amely nem rszkifejezse ms kifejezsnek. A szabvnyos string osztly c_str() nev tagfggvnye egy C stlus, nullkarakterrel lezrt karaktertmbt ad vissza (3.5.1, 20.4.1). A + opertor karakterlncok esetben sszefzst jell. Ezek nagyon hasznos dolgok a karakterlncok kezelsekor, de egyttes hasznlatuk furcsa problmkhoz vezethet:
void f(string& s1, string& s2, string& s3) { const char* cs = (s1+s2).c_str(); cout << cs; if (strlen(cs=(s2+s3).c_str())<8 && cs[0]=='a') { // cs hasznlata } }

Az olvas valsznleg azt mondja erre, hogy nem kell ilyet csinlni, s egyetrtek vele, de ilyen kdot szoktak rni, gy rdemes tudni, hogyan kell azt rtelmezni. Elszr egy ideiglenes, string osztly objektum jn ltre, amely az s1+s2 mvelet eredmnyt trolja. Ettl az objektumtl aztn elkrjk a C stlus karaktertmbt, majd a kifejezs vgn az ideiglenes objektum trldik. Vajon hol foglalt helyet a fordt a C stlus karaktertmb szmra? Valsznleg az s1+s2-t tartalmaz ideiglenes objektumban, s annak megsemmislse utn nem biztos, hogy nem semmisl meg az a terlet is, kvetkezskppen cs felszabadtott memriaterletre mutat. A cout << cs kimeneti mvelet mkdhet a vrt mdon, de ez puszta szerencse krdse. A fordtprogram esetleg feldertheti az ilyen problmt s figyelmeztethet r. Az if utastsos plda egy kicsit ravaszabb. Maga a felttel a vrakozsnak megfelelen fog mkdni, mert a teljes kifejezs, amelyben az s2+s3-at tartalmaz ideiglenes objektum ltrejn, maga az if felttele. Mindazonltal az ideiglenes objektum a felttelesen vgrehajtand utasts vgrehajtsnak megkezdse eltt megsemmisl, gy a cs vltoz brmifle ottani hasznlata nem biztos, hogy mkdik.

Forrs: http://www.doksi.hu

336

Absztrakcis mdszerek

Vegyk szre, hogy ebben az esetben, mint sok ms esetben is, az ideiglenes objektumokkal kapcsolatos problma abbl addik, hogy egy magasabb szint adatot alacsony szinten hasznltunk. Egy tisztbb programozsi stlus nem csak jobban olvashat programrszletet eredmnyezett volna, de az ideiglenes objektumokkal kapcsolatos problmkat is teljesen elkerlte volna:
void f(string& s1, string& s2, string& s3) { cout << s1+s2; string s = s2+s3; if (s.length()<8 && s[0]=='a') { // s hasznlata }

Ideiglenes vltozt hasznlhatunk konstans referencia vagy nevestett objektum kezdrtkeknt is:
void g(const string&, const string&); void h(string& s1, string& s2) { const string& s = s1+s2; string ss = s1+s2; } g(s,ss); // s s ss itt hasznlhat

Ez a kdrszlet jl mkdik. Az ideiglenes vltoz megsemmisl, amikor az hivatkozst vagy nevestett objektumt tartalmaz kdblokk lefut. Emlkezznk arra, hogy hiba egy loklis vltozra mutat referencit visszaadni egy fggvnybl (7.3) s hogy ideiglenes objektumot nem adhatunk egy nem konstans referencia kezdrtkl (5.5). Ideiglenes vltozt ltrehozhatunk kifejezett konstruktorhvssal is:
void f(Shape& s, int x, int y) { s.move(Point(x,y)); // Point ltrehozsa a Shape::move() szmra // ... }

Az ilyen mdon ltrehozott ideiglenes vltozk is ugyanolyan szablyok szerint semmislnek meg, mint az automatikusan ltrehozottak.

Forrs: http://www.doksi.hu

10. Osztlyok

337

10.4.11. Az objektumok elhelyezse


A new opertor alaprtelmezs szerint a szabad trban hozza ltre az objektumokat. Mit tegynk, ha mshol szeretnnk, hogy egy objektum ltrejjjn? Vegynk pldaknt egy egyszer osztlyt:
class X { public: X(int); // ... };

Az objektumokat tetszs szerinti helyre tehetjk, ha megadunk egy memria-lefoglal fggvnyt, amelynek tovbbi paramterei vannak, s a new opertor hasznlatakor megadjuk ezeket a paramtereket:
void* operator new(size_t, void* p) { return p; } void* buf = reinterpret_cast<void*>(0xF00F); X* p2 = new(buf)X; // explicit elhelyez opertor // fontos cm // X ltrehozsa a 'buf-ban', az operator // new(sizeof(X),buf) meghvsval

Ezen hasznlat miatt a new (buf) X utastsforma, amely az operator new-nak tovbbi paramtereket ad, elhelyez utastsknt (placement syntax) ismert. Jegyezzk meg, hogy minden new opertor a mretet vrja els paramterknt, s ezt, mint a ltrehozand objektum mrett, automatikusan megkapja (15.6). Hogy melyik opertort fogja egy adott hvs elrni, azt a szoksos paramter-egyeztetsi szablyok fogjk eldnteni (7.4); minden new() opertornak egy size_t tpus els paramtere van. Az elhelyez operator new() a legegyszerbb ilyen lefoglal fggvny, s definicija a <new> szabvnyos fejllomnyban szerepel. A reinterpret_cast a legdurvbb s a legnagyobb krokozsra kpes a tpuskonverzis opertorok kzl (6.2.7). Legtbbszr egyszeren a paramternek megfelel bitsorozat rtket, mint a kvnt tpust adja vissza, gy aztn a lnyegbl fakadan nyelvi vltozattl fgg, veszlyes s esetenknt felttlenl szksges egszek s mutatk kztti talaktsra hasznlhat. Az elhelyez new opertor felhasznlhat arra is, hogy egy bizonyos helyrl (Arena objektumtl) foglaljunk memrit:

Forrs: http://www.doksi.hu

338

Absztrakcis mdszerek

class Arena { public: virtual void* alloc(size_t) =0; virtual void free(void*) =0; // ... }; void* operator new(size_t sz, Arena* a) { return a->alloc(sz); }

A klnbz Arena objektumokban szksg szerint tetszleges tpus objektumokat hozhatunk ltre:
extern Arena* Persistent; extern Arena* Shared; void g(int i) { X* p = new(Persistent) X(i); X* q = new(Shared) X(i); // ... }

// X lland trterleten // X megosztott memriban

Ha egy objektumot olyan helyre helyeznk, amelyet nem (kzvetlenl) a szabvnyos szabadtr-kezel kezel, nmi vatossgra van szksg annak megsemmistsekor. Ennek alapvet mdja az, hogy kzvetlenl meghvjuk a destruktort:
void destroy(X* p, Arena* a) { p->~X(); // destruktor meghvsa a->free(p); // memria felszabadtsa }

Jegyezzk meg, hogy a destruktorok kzvetlen meghvst csakgy, mint az egyedi ignyeket kielgt globlis memria-lefoglalk hasznlatt inkbb kerljk el, ha lehet. Esetenknt mgis alapvet szksgnk van rjuk: pldul nehz lenne egy hatkonyan mkd ltalnos trolosztlyt kszteni a standard knyvtr vector (3.7.1, 16.3.8) tpusa nyomn, kzvetlen destruktorhvs nlkl. Mindazonltal egy kezd C++-programoz inkbb hromszor gondolja meg, mieltt kzvetlenl

Forrs: http://www.doksi.hu

10. Osztlyok

339

meghvna egy destruktort s akkor is inkbb krje eltte tapasztalt kollgjnak tancst. Az elhelyez opertor s a kivtelkezels kapcsolatrl lsd a 14.4.4-es pontot. A tmbknl nincs megfelelje az elhelyez opertornak, de nincs is szksg r, mert az elhelyez opertort tetszleges tpusokra alkalmazhatjuk. Tmbkre vonatkozan azonban megadhatunk pldul egyedi operator delete()-et (19.4.5).

10.4.12. Unik
Egy nevestett uni (union) olyan adatszerkezet (struct), amelyben minden tag cme azonos (lsd C.8.2). Egy uninak lehetnek tagfggvnyei, de nem lehetnek statikus tagjai. A fordtprogram ltalban nem tudhatja, hogy az uni melyik tagja van hasznlatban, vagyis nem ismert, hogy milyen tpus objektum van az uniban. Ezrt egy uninak nem lehet olyan tagja, amelynek konstruktorral vagy destruktorral rendelkezik, mert akkor nem lehetne a helyes memriakezelst biztostani, illetve azt, hogy az uni megsemmislsvel a megfelel destruktor hvdik meg. Az unik felhasznlsa leginkbb alacsony szinten vagy olyan osztlyok belsejben trtnik, amelyek nyilvntartjk, hogy mi van az uniban (10.6[20]).

10.5. Tancsok
[1] A fogalmakat osztlyokra kpezzk le. 10.1. [2] Csak akkor hasznljunk nyilvnos adatokat (struct-okat), amikor tnyleg csak adatok vannak s nincs rjuk nzve invarinst ignyl felttel. 10.2.8. [3] A konkrt tpusok a legegyszerbb osztlyok. Hacsak lehet, hasznljunk inkbb konkrt tpust, mint bonyolultabb osztlyokat vagy egyszer adatszerkezeteket. 10.3. [4] Egy fggvny csak akkor legyen tagfggvny, ha kzvetlenl kell hozzfrnie az osztly brzolshoz. 10.3.2. [5] Hasznljunk nvteret arra, hogy nyilvnvalv tegyk egy osztlynak s segdfggvnyeinek sszetartozst. 10.3.2. [6] Egy tagfggvny, ha nem vltozatja meg az objektumnak az rtkt, legyen const tagfggvny. 10.2.6. [7] Egy fggvny, amelynek hozz kell frnie az osztly brzolshoz, de nem

Forrs: http://www.doksi.hu

340

Absztrakcis mdszerek

[8] [9] [10] [11] [12]

[13] [14] [15] [16] [17] [18] [19]

szksges, hogy egy objektumon keresztl hvjuk meg, legyen statikus tagfggvny. 10.2.4. Az osztlyra llapotbiztostit (invarins) a konstruktorban lltsunk be. 10.3.1. Ha egy konstruktor lefoglal valamilyen erforrst, akkor legyen destruktora az osztlynak, amelyik felszabadtja azt. 10.4.1. Ha egy osztlynak van mutat tagja, akkor legyenek msol mveletei (msol konstruktora s msol rtkadsa). 10.4.4.1. Ha egy osztlynak van referencia tagja, valsznleg szksge lesz msol mveletekre (msol konstruktorra s msol rtkadsra) is. 10.4.6.3. Ha egy osztlynak szksge van msol mveletre vagy destruktorra, valsznleg szksge lesz konstruktorra, destruktorra, msol konstruktorra s msol rtkadsra is. 10.4.4.1. A msol rtkadsnl gyeljnk az nmagval val rtkadsra. 10.4.4.1. Msol konstruktor rsakor gyeljnk arra, hogy minden szksges elemet msoljunk (gyeljnk az alaprtelmezett kezdeti rtkadsra). 10.4.4.1. Ha j taggal bvtnk egy osztlyt, ellenrizzk, nincsenek-e felhasznli konstruktorok, amelyekben kezdrtket kell adni az j tagnak. 10.4.6.3. Hasznljunk felsorol konstansokat, ha egsz konstansokra van szksg egy osztly deklarcijban. 10.4.6.2. Globlis vagy nvtrhez tartoz objektumok hasznlatakor kerljk a vgrehajtsi sorrendtl val fggst. 10.4.9. Hasznljunk els hasznlatot jelz kapcsolkat, hogy a vgrehajtsi sorrendtl val fggst a lehet legkisebbre cskkentsk. 10.4.9. Gondoljunk arra, hogy az ideiglenes objektumok annak a teljes kifejezsnek a vgn megsemmislnek, amelyben ltrejttek. 10.4.10.

10.6. Gyakorlatok
1. (*1) Talljuk meg a hibt a 10.2.2-beli Date::add_year() fggvnyben. Aztn talljunk mg kt tovbbi hibt a 10.2.7-beli vltozatban. 2. (*2.5) Fejezzk be s prbljuk ki a Date osztlyt. rjuk jra gy, hogy az adatbrzolsra az 1970.01.01. ta eltelt napokat hasznljuk. 3. (*2) Keressnk egy kereskedelmi hasznlatban lev Date osztlyt. Elemezzk az ltala nyjtott szolgltatsokat. Ha lehetsges, vitassuk meg az osztlyt egy tnyleges felhasznlval. 4. (*1) Hogyan rjk el a Chrono nvtr Date osztlynak set_default fggvnyt (10.3.2)? Adjunk meg legalbb hrom vltozatot. 5. (*2) Hatrozzuk meg a Histogram osztlyt, amely a konstruktorban paramter-

Forrs: http://www.doksi.hu

10. Osztlyok

341

knt megadott idtartomnyokra vonatkoz gyakorisgokat tartja nyilvn. Biztostsunk mveletet a grafikon kiratsra s kezeljk az rtelmezsi tartomnyon kvl es rtkeket is. 6. (*2) Hatrozzunk meg osztlyokat, amelyek bizonyos (pldul egyenletes vagy exponencilis) eloszlsok szerinti vletlen szmokat adnak. Mindegyik osztlynak legyen egy konstruktora, amely az eloszlst megadja, s egy draw fggvnye, amely a kvetkez rtket adja vissza. 7. (*2.5) Ksztsk el a Table osztlyt, amely (nvrtk) prokat trol. Ezutn mdostsuk a szmolgp programot (6.1), hogy az a map helyett a Table osztlyt hasznlja. Hasonltsuk ssze a kt vltozatot. 8. (*2) rjuk jra a 7.10[7]-beli Tnode-ot, mint olyan osztlyt, amelynek konstruktorai, destruktorai stb. vannak. Adjuk meg a Tnode-ok egy fjt, mint osztlyt (konstruktorokkal s destruktorokkal). 9. (*3) Hatrozzuk meg, ksztsk el s ellenrizzk az Intset osztlyt, amely egszek halmazt brzolja. Legyen meg az uni, a metszet, s a szimmetrikus differencia mvelet is. 10. (*1.5) Mdostsuk az Intset osztlyt, hogy csompontok (Node objektumok) halmazt jelentse, ahol a Node egy meghatrozott adatszerkezet. 11. (*3) Hozzunk ltre egy olyan osztlyt, amely egsz konstansokbl s a +, -, * s / mveletekbl ll egyszer aritmetikai kifejezseket kpes elemezni, kirtkelni, trolni s kirni. A nyilvnos fellet ilyesmi legyen:
class Expr { // ... public: Expr(const char*); int eval(); void print(); };

Az Expr::Expr() konstruktor karakterlnc paramtere a kifejezs. Az Expr::eval() fggvny visszaadja a kifejezs rtkt, az Expr::print() pedig brzolja azt a cout-on. A program gy nzhet ki:
Expr x("123/4+123*4-3"); cout << "x = " << x.eval() << "\n"; x.print();

Hatrozzuk meg az Expr osztlyt ktflekppen: egyszer mint csompontok lncolt listjt, msszor egy karakterlnccal brzolva. Ksrletezznk a kifejezs klnbz kiratsaival: teljesen zrjelezve, a mveleti jelet uttagknt hasznlva, assembly kddal stb.

Forrs: http://www.doksi.hu

342

Absztrakcis mdszerek

12. (*2) Hatrozzuk meg a Char_queue osztlyt, hogy a nyilvnos fellet ne fggjn az brzolstl. Ksztsk el a Char_queue-t mint (a) lncolt listt, illetve (b) vektort. 13. (*3) Tervezznk egy szimblumtbla s egy szimblumtbla-elem osztlyt valamely nyelv szmra. Nzzk meg az adott nyelv egy fordtprogramjban, hogyan nznek ki ott az igazi szimblumtblk. 14. (*2) Mdostsuk a 10.6[11]-beli kifejezsosztlyt, hogy vltozkat is kezelni tudjon, valamint a = rtkad mveletet is. Hasznljuk a 10.6[131]-beli szimblumtbla osztlyt. 15. (*1) Adott a kvetkez program:
#include <iostream> int main() { std::cout << "Hell, vilg!\n"; }

Mdostsuk gy, hogy a kvetkez kimenetet adja:


Kezdeti rtkads Hell, vilg! Takarts

A main() fggvnyt semmilyen mdon nem vltoztathatjuk meg. 16. (*2) Hatrozzunk meg egy olyan Calculator osztlyt, amilyet a 6.1-beli fggvnyek nagyrszt megvalstanak. Hozzunk ltre Calculator objektumokat s alkalmazzuk azokat a cin-bl szrmaz bemenetre, a parancssori paramterekre s a programban trolt karakterlncokra. Tegyk lehetv a kimenetnek a bemenethez hasonl mdon tbbfle helyre val irnytst. 17. (*2) Hatrozzunk meg kt osztlyt, mindegyikben egy-egy statikus taggal, gy, hogy mindegyik ltrehozshoz a msikra hivatkozunk. Hol fordulhat el ilyesmi igazi kdban? Hogyan lehet mdostani az osztlyokat, hogy kikszbljk a vgrehajtsi sorrendtl val fggst? 18. (*2.5) Hasonltsuk ssze a Date osztlyt (10.3) az 5.9[13] s a 7.10[19] feladatra adott megoldssal. rtkeljk a megtallt hibkat s gondoljuk meg, milyen klnbsgekkel kell szmolni a kt osztly mdostsakor. 19. (*3) rjunk olyan fggvnyt, amely egy istream-bl s egy vector<string>-bl kiindulva elkszt egy map<string,vector<int> > objektumot, amely minden karakterlncot s azok elfordulsnak sorszmt tartalmazza. Futtassuk a programot egy olyan szvegfjllal, amely legalbb 1000 sort tartalmaz, s legalbb 10

Forrs: http://www.doksi.hu

11
Opertorok tlterhelse
Amikor n hasznlok egy szt, azt rtem alatta, amit n akarok se tbbet, se kevesebbet. (Humpty Dumpty) Jells Opertor fggvnyek Egy- s ktoperandus mveleti jelek Az opertorok elre meghatrozott jelentse Az opertorok felhasznli jelentse Opertorok s nvterek Komplex szm tpusok Tag s nem tag opertorok Vegyes md aritmetika Kezdeti rtkads Msols Konverzik Literlok Segdfggvnyek Konverzis opertorok A tbbrtelmsg feloldsa Bart fggvnyek s osztlyok Tagok s bart fggvnyek Nagy objektumok rtkads s kezdeti rtkads Indexels Fggvnyhvs Indirekci Nvels s cskkents Egy karakterlnc osztly Tancsok Gyakorlatok

Forrs: http://www.doksi.hu

344

Absztrakcis mdszerek

11.1. Bevezets
Minden mszaki szakterletnek s a legtbb nem mszakinak is kialakultak a maga megszokott rvidtsei, amelyek knyelmess teszik a gyakran hasznlt fogalmak kifejezst, trgyalst. Az albbi pldul
x+y*z

vilgosabb szmunkra, mint a


vegyk y-t z-szer s az eredmnyt adjuk x-hez

Nem lehet elgg megbecslni a szoksos mveletek tmr jellsnek fontossgt. A legtbb nyelvvel egytt a C++ is tmogat egy sor, a beptett tpusokra vonatkoz mveletet. A legtbb fogalomnak, amelyre mveleteket szoktak alkalmazni, azonban nincs megfelelje a beptett tpusok kztt, gy felhasznli tpussal kell azokat brzolni. Pldul ha komplex szmokkal akarunk szmolni, ha mtrix-mveletekre, logikai jellsekre vagy karakterlncokra van szksgnk a C++-ban, osztlyokat hasznlunk, hogy ezeket a fogalmakat brzoljuk. Ha ezekre az osztlyokra vonatkoz mveleteket definilunk, megszokottabb s knyelmesebb jells felhasznlsval kezelhetjk az objektumokat, mintha csak az alapvet fggvny-jellst hasznlnnk.
class complex { // nagyon leegyszerstett complex tpus double re, im; public: complex(double r, double i) : re(r), im(i) { } complex operator+(complex); complex operator*(complex); };

Itt pldul a komplex szm fogalmnak egy egyszer megvalstst lthatjuk. Egy complex rtket egy ktszeres pontossg lebegpontos szmpr brzol, melyet a + s a * mveletek kezelnek. A felhasznl adja meg a complex::operator+() s complex::operator*() opertorokat, hogy rtelmezze a + s * mveleteket. Ha pldul b s c complex tpusak, akkor a b+c a b.operator(c)-t jelenti. Ezek utn kzeltleg meghatrozhatjuk a complex szmokat tartalmaz kifejezsek megszokott jelentst:

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

345

void f() { complex a = complex(1, 3.1); complex b = complex(1.2, 2); complex c = b; a = b+c; b = b+c*a; c = a*b+complex(1,2);

A szoksos kirtkelsi szablyok rvnyesek, gy a msodik kifejezs azt jelenti, hogy b=b+(c*a), s nem azt, hogy b=(b+c)*a. Az opertorok tlterhelsnek legnyilvnvalbb alkalmazsai kzl sok konkrt tpusokra vonatkozik (10.3). Az opertorok tlterhelse azonban nemcsak konkrt tpusoknl hasznos. ltalnos s absztrakt felletek felptsnl pldul gyakran hasznlunk olyan opertorokat, mint a ->, a [ ] s a ().

11.2. Opertor fggvnyek


A kvetkez opertorok (6.2) jelentst meghatroz fggvnyeket megadhatjuk:
+ | -= << >= -> ~ *= >> && [] * ! /= >>= || () / = %= <<= ++ new % < ^= == -new[ ] ^ > &= != ->* delete & += |= <= , delete[ ]

A kvetkezknek viszont nem lehet felhasznli jelentst tulajdontani: :: (hatkr-felolds, 4.9.4, 10.2.4) . (tagkivlaszts, 5.7) .* (tagkivlaszts a tagra hivatkoz mutatn keresztl, 15.5) Ezek olyan opertorok (mveleti jelek), amelyek msodik operandusknt nem rtket, hanem nevet vrnak s a tagokra val hivatkozs alapvet mdjai. Ha tl lehetne terhelni ezeket azaz ha a felhasznl hatrozhatn meg jelentsket akkor ez rdekes mellkhatsokkal jrhatna [Stroustrup, 1994]. A hromparamter feltteles-kifezs opertor, a ?: (6.3.2) sem terhelhet tl, mint ahogy a sizeof (4.6) s a typeid (15.4.4) sem.

Forrs: http://www.doksi.hu

346

Absztrakcis mdszerek

j mveleti jeleket sem adhatunk meg; ehelyett a fggvnyhvsi jells hasznlhat, ha a rendelkezsre llkon kvl tovbbi opertorokra is szksg van. gy pldul ne **-ot hasznljunk, hanem azt, hogy pow(). Ezek a megszortsok tl szigornak tnhetnek, de rugalmasabb szablyok knnyen az egyrtelmsg elvesztshez vezetnnek. Els pillantsra nyilvnvalnak s egyszernek tnhet a ** opertort hasznlni a hatvnyozsra, de gondoljunk csak meg: a ** mveleti jel balrl kssn, mint a Fortranban, vagy jobbrl, mint az Algolban? Az a**p kifejezst hogyan rtelmezzk: mint a*(*p)-t vagy mint (a)**(p)-t? Az opertor fggvnyek neve az operator kulcsszbl s azt kveten magbl az opertorbl ll; pldul operator <<. Az opertor fggvnyeket ugyangy deklarlhatjuk s hvhatjuk meg, mint a tbbi fggvnyt. Az opertorral val jells csak az opertor fggvny kzvetlen meghvsnak rvidtse:
void f(complex a, complex b) { complex c = a + b; complex d = a.operator+(b); }

// rvid forma // explicit hvs

A complex elz definicijt adottnak vve a fenti kt kezdeti rtkads jelentse azonos.

11.2.1. Egy- s ktoperandus mveletek


Ktoperandus mveleti jelet egyparamter nem statikus tagfggvnyknt vagy ktparamter nem tag fggvnyknt definilhatunk. Ha @ ktoperandus mveletet jell, akkor aa@bb vagy aa.operator@(bb)-t, vagy operator@(aa,bb)-t jelli. Ha mindkett rtelmezett, a tlterhels-feloldsi szablyok (7.4) dntik el, melyik alkalmazhat, illetve hogy egyltaln brmelyik alkalmazhat-e:
class X { public: void operator+(int); X(int); }; void operator+(X,X); void operator+(X,double); void f(X a) { a+1; 1+a; a+1.0; }

// a.operator+(1) // ::operator+(X(1),a) // ::operator+(a,1.0)

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

347

Az egyoperandus (akr el-, akr uttagknt hasznlt) mveleti jelek paramter nlkli nem statikus tagfggvnyknt vagy egyparamter nem tag fggvnyknt definilhatk. Ha @ eltag s egyoperandus mveletet jell, akkor @aa vagy aa.operator@()-t, vagy operator@(aa)-t jelli. Ha mindkett rtelmezett, a tlterhels-feloldsi szablyok (7.4) dntik el, melyik alkalmazhat, illetve hogy egyltaln brmelyik alkalmazhat-e. Ha @ uttag s egyoperandus mveletet ad meg, akkor aa@ vagy aa.operator@(int)-et, vagy operator@(aa,int)-et jelli. (Ezt rszletesebben a 11.11 pont rja le.) Ha mindkett rtelmezett, ismt csak a tlterhels-feloldsi szablyok (7.4) dntik el, melyik alkalmazhat, illetve hogy egyltaln brmelyik alkalmazhat-e. Opertort csak a nyelvi szablyoknak megfelelen definilhatunk (A.5), gy nem lehet pldul egyoperandus % vagy hromoperandus + mveletnk:
class X { // tagok (a 'this' mutat automatikus): X* operator&(); X operator&(X); X operator++(int); X operator&(X,X); X operator/(); // eltagknt hasznlt egyoperandus & (cm) // ktoperandus & (s) // uttagknt hasznlt nvel opertor (lsd 11.1) // hiba: hromoperandus // hiba: egyoperandus /

};

// nem tag fggvnyek : X operator-(X); X operator-(X,X); X operator--(X&,int); X operator-(); X operator-(X,X,X); X operator%(X); // eltagknt hasznlt egyoperandus mnusz (mnusz eljel) // ktoperandus mnusz (kivons) // uttagknt hasznlt cskkent opertor // hiba: nincs operandus // hiba: hromoperandus // hiba: egyoperandus %

A [ ] opertort a 11.8, a () opertort a 11.9, a -> opertort a 11.10, a ++ s -- opertorokat a 11.11, a memriafoglal s felszabadt opertorokat a 6.2.6.2, a 10.4.11 s a 15.6 pontokban rjuk le.

11.2.2. Az opertorok elre meghatrozott jelentse


A felhasznli opertorok jelentsnek csak nhny elrsnak kell megfelelnik. Az operator=, operator[ ], operator() s az operator-> nem statikus tagfggvny kell, hogy legyen; ez biztostja, hogy els operandusuk balrtk (lvalue) lesz (4.9.6).

Forrs: http://www.doksi.hu

348

Absztrakcis mdszerek

Bizonyos beptett opertorok jelentse megegyezik ms opertoroknak ugyanazon paramterre sszetetten gyakorolt hatsval. Pldul ha a egy int, akkor ++a jelentse megegyezik a+=1-gyel, ami pedig azt jelenti, hogy a=a+1. Hacsak a felhasznl nem gondoskodik rla, ilyen sszefggsek nem llnak fenn a felhasznli opertorokra, gy a fordtprogram pldul nem fogja kitallni a Z::operator+=() mvelet jelentst pusztn abbl, hogy megadtuk a Z::operator+() s Z::operator=() mveleteket. Hagyomnyosan az = (rtkad), a & (cmkpz) s a , (vessz; 6.2.2) opertorok elre definiltak, ha osztlyba tartoz objektumra alkalmazzuk azokat. Ezeket az elre meghatrozott jelentseket az ltalnos felhasznl ell elrejthetjk, ha privtknt adjuk meg azokat:
class X { private: void operator=(const X&); void operator&(); void operator,(const X&); // ... }; void f(X a, X b) { a = b; &a; a,b; }

// hiba: az rtkad opertor privt // hiba: a cm opertor (&) privt // hiba: a vessz opertor (,) privt

Alkalmas mdon definilva azonban j jelents is tulajdonthat nekik.

11.2.3. Opertorok s felhasznli tpusok


Az opertoroknak tagfggvnynek kell lennik vagy paramtereik kztt legalbb egy felhasznli tpusnak kell szerepelnie (kivtelek ez all a new s delete opertorok jelentst fellbrl fggvnyek.) Ez a szably biztostja, hogy a programoz egy kifejezs rtelmt csak akkor mdosthassa, ha legalbb egy felhasznli tpus elfordul benne. Ebbl addan nem definilhat olyan opertor, amely kizrlag mutatkkal mkdik. A C++ teht bvthet, de nem vltoztathat meg, (az osztlyba tartoz objektumokra vonatkoz =, & s , opertorokat kivve). Az olyan opertorok, melyeket arra sznunk, hogy els paramterknt valamilyen alaptpust fogadjanak el, nem lehetnek tagfggvnyek. Vegyk pldul azt az esetet, amikor egy complex vltozt akarunk a 2 egszhez hozzadni: az aa+2 kifejezst alkalmas tagfggvny

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

349

meglte esetn rtelmezhetjk aa.operator+(2)-knt, de a 2+aa kifejezst nem, mert nincs int osztly, amelynek + olyan tagfggvnye lehetne, hogy a 2.operator(aa) eredmnyre jussunk. De ha lenne is, akkor is kt tagfggvny kellene ahhoz, hogy 2+aa-val s aa+2-vel is megbirkzzunk. Minthogy a fordtprogram nem ismeri a felhasznli + mvelet jelentst, nem ttelezheti fel rla a felcserlhetsget (kommutativitst), hogy annak alapjn 2+aa-t mint aa+2-t kezelje. Az ilyesmit rendszerint nem tag fggvnyekkel kezelhetjk (11.3.2, 11.5). A felsorolsok felhasznli tpusok, gy rjuk is rtelmezhetnk opertorokat:
enum Day { sun, mon, tue, wed, thu, fri, sat }; Day& operator++(Day& d) { return d = (sat==d) ? sun : Day(d+1); }

A fordtprogram minden kifejezst ellenriz, hogy nem lp-e fel tbbrtelmsg. Ha egy felhasznli opertor is biztost lehetsges rtelmezst, a kifejezs ellenrzse a 7.4 pontban lertak szerint trtnik.

11.2.4. Nvterek opertorai


Az opertor mindig valamilyen osztly tagja vagy valamilyen nvtrben (esetleg a globlisban) definilt. Vegyk pldul a standard knyvtr karakterlnc-kirsi mveletnek egyszerstett vltozatt:
namespace std { // egyszerstett std

class ostream { // ... ostream& operator<<(const char*); }; extern ostream cout; class string { // ... }; } ostream& operator<<(ostream&, const string&);

Forrs: http://www.doksi.hu

350

Absztrakcis mdszerek

int main() { char* p = "Hell"; std::string s = "vilg"; std::cout << p << ", " << s << "!\n"; }

Ez termszetesen azt rja ki, hogy Hell, vilg!. De mirt? Vegyk szre, hogy nem tettem mindent elrhetv az std nvtrbl azltal, hogy azt rtam volna:
using namespace std;

Ehelyett az std:: eltagot alkalmaztam a string s a cout eltt. Vagyis a legrendesebben viselkedve nem szennyeztem be a globlis nvteret s egyb mdon sem vezettem be szksgtelen fggseket. A C stlus karakterlncok (char*) kimeneti mvelete az std::ostream egy tagja, gy
std::cout << p

jelentse definci szerint:


std::cout.operator<<(p)

Mivel azonban az std::ostream-nek nincs olyan tagja, amelyet az std::string-re alkalmazhatnnk, gy


std::cout << s

jelentse:
operator<<(std::cout,s)

A nvtrben definilt opertorokat ugyangy operandusuk tpusa szerint tallhatjuk meg, mint ahogy a fggvnyeket paramtereik tpusa szerint (8.2.6). Minthogy a cout az std nvtrben van, gy az std is szba kerl, amikor a << szmra alkalmas defincit keresnk. gy aztn a fordtprogram megtallja s felhasznlja a kvetkez fggvnyt:
std::operator<<(std::ostream&, const std::string&)

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

351

Jelljn @ egy ktoperandus mveletet. Ha x az X tpusba, az y pedig az Y tpusba tartozik, akkor x@y feloldsa, azaz a paramterek tpusnak megfelel fggvny megkeresse a kvetkezkppen trtnik: Ha X egy osztly, keresnk egy operator@-t, amely az X osztlynak vagy valamelyik bzisosztlynak tagfggvnye. Keresnk egy operator@ deklarcit az x@y kifejezst krlvev krnyezetben. Ha X az N nvtr tagja, az operator@-t az N nvtrben keressk. Ha Y az M nvtr tagja, az operator@-t az M nvtrben keressk. Ha az operator@ tbbfle deklarcijt is megtalltuk, a feloldsi szablyokat kell alkalmazni (7.4), hogy a legjobb egyezst megtalljuk, ha egyltaln van ilyen. Ez a keressi eljrs csak akkor alkalmazand, ha legalbb egy felhasznli tpus szerepel az operandusok kztt, ami azt is jelenti, hogy a felhasznli konverzikat (11.3.2, 11.4) is figyelembe vesszk. (A typedef-fel megadott nevek csak szinonimk, nem felhasznli tpusok (4.9.7).) Az egyoperandus mveletek feloldsa hasonlan trtnik. Jegyezzk meg, hogy az opertorok feloldsban a tagfggvnyek nem lveznek elnyt a nem tag fggvnyekkel szemben. Ez eltr a nvvel megadott fggvnyek keresstl (8.2.6). Az opertorok el nem rejtse biztostja, hogy a beptett opertorok nem vlnak elrhetetlenn, s hogy a felhasznl a meglev osztlydeklarcik mdostsa nlkl adhat meg j jelentseket. A szabvnyos iostream knyvtr pldul definilja a << tagfggvnyeket a beptett tpusokra, a felhasznl viszont a felhasznli tpusoknak a << mvelettel val kimenetre kldst az ostream osztly (21.2.1) mdostsa nlkl definilhatja.

11.3. Komplex szm tpusok


A komplex szmoknak a bevezetben emltett megvalstsa tl keveset nyjt ahhoz, hogy brkinek is tessk. Egy matematika tanknyvet olvasva azt vrnnk, hogy a kvetkez fggvny mkdik:
void f() { complex a = complex(1,2); complex b = 3; complex c = a+2.3; complex d = 2+b; complex e = -b-c; b = c*2*c; }

Forrs: http://www.doksi.hu

352

Absztrakcis mdszerek

Radsul elvrnnk, hogy ltezzk nhny tovbbi mvelet is, pldul a == az sszehasonltsra s a << a kimenetre, s mg a matematikai fggvnyek (mint a sin() s a sqrt()) megfelel kszlett is ignyelnnk. A complex osztly egy konkrt tpus, gy felptse megfelel a 10.3-beli elveknek. Radsul a komplex aritmetika felhasznli olyan nagy mrtkben ptenek az opertorokra, hogy a complex osztly definilsa az opertor-tlterhelsre vonatkoz szinte valamennyi szably alkalmazst ignyli.

11.3.1. Tag s nem tag opertorok


Elnys lenne, ha minl kevesebb fggvny frne hozz kzvetlenl egy adott objektum bels adatbrzolshoz. Ezt gy rhetjk el, ha csak azokat az opertorokat adjuk meg magban az osztlyban, amelyek rtelmknl fogva mdostjk els paramterket, mint pldul a +=. Azokat az opertorokat, amelyek csak egy j rtket lltanak el paramtereik alapjn, mint pldul a +, az osztlyon kvl definilom s az alapvet opertorok segtsgvel valstom meg:
class complex { double re, im; public: complex& operator+=(complex a); // ... };

// hozz kell frni az brzolshoz

complex operator+(complex a, complex b) { complex r = a; return r += b; // az brzols elrse a += opertoron keresztl }

Ezen deklarcik alapjn mr lerhatjuk a kvetkezt:


void f(complex x, complex y, complex z) { complex r1 = x+y+z; complex r2 = x; r2 += y; r2 += z; }

// r1 = operator+(operator+(x,y),z) // r2 = x // r2.operator+=(y) // r2.operator+=(z)

Esetleges hatkonysgi klnbsgektl eltekintve r1 s r2 kiszmtsa egyenrtk.

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

353

Az sszetett rtkad opertorokat, pldul a += -t s a *= -t ltalban knnyebb definilni, mint egyszer megfeleliket, a + s * opertorokat. Ez tbbnyire meglepst kelt, pedig pusztn abbl kvetkezik, hogy az sszeadsnl 3 objektum jtszik szerepet (a kt sszeadand s az eredmny), mg a += opertornl csak kett. Az utbbi esetben hatkonyabb a megvalsts, ha nem hasznlunk ideiglenes vltozkat:
inline complex& complex::operator+=(complex a) { re += a.re; im += a.im; return *this; }

A fenti megoldsnl nincs szksg ideiglenes vltozra az eredmny trolsra s a fordtprogram szmra is knnyebb feladat a teljes helyben kifejts. Egy j fordtprogram az optimlishoz kzeli kdot kszt a sima + opertor hasznlata esetn is. De nincs mindig j optimalizlnk s nem minden tpus olyan egyszer, mint a complex, ezrt a 11.5 pont trgyalja, hogyan adhatunk meg olyan opertorokat, amelyek hozzfrhetnek az osztly brzolshoz.

11.3.2. Vegyes md aritmetika


Ahhoz, hogy a
complex d = 2+b;

kdot kezelni tudjuk, olyan + opertorra van szksgnk, amely klnbz tpus paramtereket is elfogad. A Fortran kifejezsvel lve teht vegyes md aritmetikra (mixedmode arithmetic) van szksg. Ezt knnyen megvalsthatjuk, ha megadjuk az opertor megfelel vltozatait:
class complex { double re, im; public: complex& operator+=(complex a) { re += a.re; im += a.im; return *this; }

Forrs: http://www.doksi.hu

354

Absztrakcis mdszerek

complex& operator+=(double a) { re += a; return *this; } }; // ...

complex operator+(complex a, complex b) { complex r = a; return r += b; // complex::operator+=(complex)-et hvja meg } complex operator+(complex a, double b) { complex r = a; return r += b; // complex::operator+=(double)-t hvja meg } complex operator+(double a, complex b) { complex r = b; return r += a; // complex::operator+=(double)-t hvja meg }

Egy double hozzadsa egy komplex szmhoz egyszerbb mvelet, mint egy komplex szm hozzadsa; ezt tkrzik a fenti defincik is. A double operandust kezel mveletek nem rintik a komplex szm kpzetes rszt, gy hatkonyabbak lesznek. A fenti deklarcik mellett most mr lerhatjuk a kvetkezt:
void f(complex x, complex y) { complex r1 = x+y; // operator+(complex,complex)-et hvja meg complex r2 = x+2; // operator+(complex,double)-t hvja meg complex r3 = 2+x; // operator+(double,complex)-et hvja meg }

11.3.3. Kezdeti rtkads


A complex vltozknak skalrokkal val kezdeti s egyszer rtkads kezelshez szksgnk van a skalrok (egsz vagy lebegpontos (vals) rtkek) complex-sz talaktsra:
complex b = 3; // b.re=3, b.im=0-t kell jelentenie

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

355

Az olyan konstruktor, amely egyetlen paramtert vr, konverzit jelent a paramter tpusrl a konstruktor tpusra:
class complex { double re, im; public: complex(double r) : re(r), im(0) { } // ... };

Ez a konstruktor a vals szmegyenesnek a komplex skba val szoksos begyazst jelenti. Egy konstruktor mindig azt rja el, hogyan hozhatunk ltre egy adott tpus rtket. Ha egy adott tpus rtket kell ltrehozni egy (kezdeti vagy egyszer) rtkad kifejezs rtkbl s ebbl egy konstruktor ltre tudja hozni a kvnt tpus rtket, akkor konstruktort alkalmazunk. Ezrt az egyparamter konstruktorokat nem kell explicit meghvnunk:
complex b = 3;

A fenti egyenrtk a kvetkezvel:


complex b = complex(3);

Felhasznli konverzira csak akkor kerl sor automatikusan, ha az egyrtelm (7.4). Arra nzve, hogyan adhatunk meg csak explicite meghvhat konstruktorokat, lsd a 11.7.1 pontot. Termszetesen szksgnk lesz egy olyan konstruktorra is, amelynek kt double tpus paramtere van, s a (0,0) kezdrtket ad alaprtelmezett konstruktor is hasznos:
class complex { double re, im; public: complex() : re(0), im(0) { } complex(double r) : re(r), im(0) { } complex(double r, double i) : re(r), im(i) { } // ... };

Forrs: http://www.doksi.hu

356

Absztrakcis mdszerek

Alaprtelmezett paramter-rtkeket hasznlva gy rvidthetnk:


class complex { double re, im; public: complex(double r =0, double i =0) : re(r), im(i) { } // ... };

Ha egy tpusnak van konstruktora, a kezdeti rtkadsra nem hasznlhatunk kezdrtklistt (5.7, 4.9.5):
complex z1 = { 3 }; complex z2 = { 3, 4 }; // hiba: complex rendelkezik konstruktorral // hiba: complex rendelkezik konstruktorral

11.3.4. Msols
A megadott konstruktorokon kvl a complex osztlynak lesz egy alaprtelmezett msol konstruktora (10.2.5) is. Az alaprtelmezett msol konstruktor egyszeren lemsolja a tagokat. A mkdst pontosan gy hatrozhatnnk meg:
class complex { double re, im; public: complex(const complex& c) : re(c.re), im(c.im) { } // ... };

n elnyben rszestem az alaprtelmezett msol konstruktort azon osztlyok esetben, amelyeknl ez megfelel. Rvidebb lesz a kd, mintha brmi mst rnk, s a kd olvasjrl felttelezem, hogy ismeri az alaprtelmezett mkdst. A fordtprogram is ismeri s azt is, hogyan lehet azt optimalizlni. Ezenkvl pedig sok tag esetn fraszt dolog kzzel kirni a tagonknti msolst s knny kzben hibzni (10.4.6.3). A msol konstruktor paramtereknt referencit kell hasznlnom. A msol konstruktor hatrozza meg a msols jelentst belertve a paramter msolst is gy a
complex::complex(complex c) : re(c.re), im(c.im) { } // hiba

hibs, mert a fggvny meghvsa vgtelen rekurzihoz vezet.

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

357

Ms, complex paramter fggvnyek esetben n rtk s nem referencia szerinti paramter-tadst hasznlok. Mindig az osztly ksztje dnt. A felhasznl szemszgbl nzve nincs sok klnbsg egy complex s egy const complex& paramtert kap fggvny kztt. Errl bvebben r a 11.6 pont. Elvileg a msol konstruktort az ilyen egyszer kezdeti rtkadsoknl hasznljuk:
complex x = 2; complex y = complex(2,0); // complex(2) ltrehozsa; ezzel adunk kezdrtket x-nek // complex(2,0) ltrehozsa; ezzel adunk kezdrtket y-nak

A fordtprogram azonban optimalizl s elhagyja a msol konstruktor meghvst. rhattuk volna gy is:
complex x(2); complex y(2,0); // x kezdrtke 2 // y kezdrtke (2,0)

A complex-hez hasonl aritmetikai tpusok esetben jobban kedvelem az = jel hasznlatt. Ha a msol konstruktort privtt tesszk (11.2.2) vagy ha egy konstruktort explicit-knt adunk meg (11.7.1), az = stlus rtkads ltal elfogadhat rtkek krt a () stlus rtkads ltal elfogadotthoz kpest korltozhatjuk. A kezdeti rtkadshoz hasonlan a kt azonos osztlyba tartoz objektum kztti rtkads alaprtelmezs szerint tagonknti rtkadst jelent (10.2.5). A complex osztlynl erre a clra megadhatnnk kifejezetten a complex::operator= mveletet, de ilyen egyszer osztly esetben erre nincs ok, mert az alaprtelmezett mkds pont megfelel. A msol konstruktor akr a fordtprogram hozta ltre, akr a programoz rta nemcsak a vltozk kezdrtknek belltsra hasznlatos, hanem paramter-tadskor, rtk visszaadsakor s a kivtelkezelskor is (lsd 11.7). Ezek szerept a nyelv a kezdeti rtkadsval azonosknt hatrozza meg (7.1, 7.3, 14.2.1).

11.3.5. Konstruktorok s konverzik


A ngy alapvet aritmetikai mveletnek eddig hrom-hrom vltozatt hatroztuk meg:
complex operator+(complex,complex); complex operator+(complex,double); complex operator+(double,complex); // ...

Forrs: http://www.doksi.hu

358

Absztrakcis mdszerek

Ez frasztv vlhat, s ami fraszt, ott knnyen elfordulhatnak hibk. Mi lenne, ha minden paramter hromfle tpus lehetne? Minden egyparamter mveletbl hrom vltozat kellene, a ktparamterekbl kilenc, a hromparamterekbl huszonht s gy tovbb. Ezek a vltozatok gyakran nagyon hasonlak. Valjban majdnem mindegyik gy mkdik, hogy a paramtereket egy kzs tpusra alaktja, majd egy szabvnyos algoritmust hajt vgre. Ahelyett, hogy a paramterek minden lehetsges prostsra megadnnk egy fggvnyt, tpuskonverzikra hagyatkozhatunk. Tegyk fel, hogy complex osztlyunknak van egy olyan konstruktora, amely egy double rtket alakt complex-sz, gy a complex osztly szmra elg egyetlen egyenlsg-vizsgl mveletet megadnunk:
bool operator==(complex,complex); void f(complex x, complex y) { x==y; // jelentse operator==(x,y) x==3; // jelentse operator==(x,complex(3)) 3==y; // jelentse operator==(complex(3),y) }

Lehetnek azonban okok, melyek miatt jobb kln fggvnyeket megadni. Egyes esetekben pldul a konverzi tl bonyolult mvelet lehet, mskor bizonyos paramtertpusokra egyszerbb algoritmusok alkalmazhatk. Ahol ilyen okok nem lpnek fel jelents mrtkben, ott a fggvny legltalnosabb formjt megadva (esetleg nhny kritikus vltozattal kiegsztve) s a konverzikra hagyatkozva elkerlhetjk, hogy a vegyes md aritmetikbl addan nagyon sokfle fggvnyt kelljen megrnunk. Ha egy fggvny vagy opertor tbb vltozattal rendelkezik, a fordtprogram feladata a legalkalmasabb vltozat kivlasztsa, a paramtertpusok s a lehetsges (szabvnyos vagy felhasznli) konverzik alapjn. Ha nincs legjobb vltozat, a kifejezs tbbrtelm s hibs (lsd 7.4). Az olyan objektumok, melyeket a konstruktor kzvetlen meghvsa vagy automatikus hasznlata hozott ltre, ideiglenes vltoznak szmtanak s amint lehetsges, megsemmislnek (lsd 10.4.10). A . s -> opertorok bal oldaln nem trtnik automatikus felhasznli konverzi. Ez akkor is gy van, ha maga a . implicit (a kifejezsbe belertett):
void g(complex z) { 3+z; 3.operator+=(z); 3+=z; }

// rendben: complex(3)+z // hiba: 3 nem egy osztly objektuma // hiba: 3 nem egy osztly objektuma

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

359

Ezt kihasznlva egy mveletet tagfggvnny tve kifejezhetjk, hogy a mvelet bal oldali operandusknt balrtket vr.

11.3.6. Literlok
Osztly tpus literlokat nem definilhatunk abban az rtelemben, ahogyan 1.2 s 1.2e3 double tpus literlok. Az alapvet tpusokba tartoz literlokat viszont gyakran hasznlhatjuk, ha a tagfggvnyek fel vannak ksztve a kezelskre. Az egyparamter konstruktorok ltalnos eljrst biztostanak erre a clra. Egyszer s helyben kifejtett (inline) konstruktorok esetben sszer a literl paramter konstruktorhvsokra mint literlokra gondolni. A complex(3) kifejezst pldul n gy tekintem, mint egy complex rtk literlt, noha a sz technikai rtelmben vve nem az.

11.3.7. Kiegszt tagfggvnyek


Eddig csak konstruktorokat s aritmetikai mveleteket adtunk a complex osztlyhoz. A tnyleges hasznlathoz ez kevs. A vals s a kpzetes rsz lekrdezse pldul srn hasznlatos:
class complex { double re, im; public: double real() const { return re; } double imag() const { return im; } // ... };

A complex osztly tbbi tagfggvnyvel ellenttben a real() s az imag() nem vltoztatja meg egy complex objektum rtkt, gy const-knt adhat meg. A real() s imag() fggvnyek alapjn egy sor hasznos fggvnyt definilhatunk anlkl, hogy azoknak hozzfrst kellene adnunk a complex osztly adatbrzolshoz:
inline bool operator==(complex a, complex b) { return a.real()==b.real() && a.imag()==b.imag(); }

Vegyk szre, hogy a vals s a kpzetes rszt elg olvasnunk, rnunk sokkal ritkbban kell.

Forrs: http://www.doksi.hu

360

Absztrakcis mdszerek

Ha rszleges frisstsre van szksgnk, a kvetkezt rhatjuk:


void f(complex& z, double d) { // ... z = complex(z.real(),d); }

// d hozzrendelse z.im-hez

Egy jl optimalizl fordt ebbl egyetlen rtkadst kszt.

11.3.8. Segdfggvnyek
Ha mindent sszerakunk, complex osztlyunk gy alakul:
class complex { double re, im; public: complex(double r =0, double i =0) : re(r), im(i) { } double real() const { return re; } double imag() const { return im; } complex& operator+=(complex); complex& operator+=(double); // -=, *=, s /=

};

Kiegsztsknt egy sor segdfggvnyt kell biztostanunk:


complex operator+(complex,complex); complex operator+(complex,double); complex operator+(double,complex); // -, *, s / complex operator-(complex); complex operator+(complex); bool operator==(complex,complex); bool operator!=(complex,complex); istream& operator>>(istream&,complex&); ostream& operator<<(ostream&,complex); // bemenet // kimenet // egyoperandus mnusz // egyoperandus plusz

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

361

Vegyk szre, hogy a real() s imag() fggvnyek szerepe alapvet az sszehasonlt fggvnyek definilsban. A kvetkez segdfggvnyek is nagyrszt ezekre ptenek. Megadhatnnk olyan fggvnyeket is, amelyek a polr-koordints jellst tmogatjk:
complex polar(double rho, double theta); complex conj(complex); double abs(complex); double arg(complex); double norm(complex); double real(complex); double imag(complex); // a knyelmesebb jellsrt // a knyelmesebb jellsrt

Vgl szksgnk lesz a tovbbi alapvet matematikai fggvnyekre:


complex acos(complex); complex asin(complex); complex atan(complex); // ...

Felhasznli szemszgbl nzve az itt bemutatott complex osztly szinte azonos a complex<double>-lal (lsd a standard knyvtrbeli <complex>-et, 22.5).

11.4. Konverzis opertorok


Konstruktorok hasznlata tpuskonverzi cljra knyelmes lehet, de nemkvnatos kvetkezmnyei vannak. Egy konstruktor nem tud 1. automatikus talaktst megadni felhasznli adattpusrl beptett adattpusra (mert a beptett adattpusok nem osztlyok) 2. talaktst megadni egy jabban megadott osztlyrl egy rgebbire, a rgebbi osztly deklarcijnak megvltoztatsa nlkl. Ezeket a feladatokat az talaktand osztly konverzis (talakt) opertornak definilsval oldhatjuk meg. Ha T egy tpus neve, akkor az X::operator T() fggvny hat-

Forrs: http://www.doksi.hu

362

Absztrakcis mdszerek

rozza meg az X tpus T-re val konverzijt. Definilhatunk pldul a 6 bites, nem negatv egszeket brzol Tiny osztlyt, melynek objektumait aritmetikai kifejezsekben szabadon keverhetjk egszekkel:
class Tiny { char v; void assign(int i) { if (i&~077) throw Bad_range(); v=i; } public: class Bad_range { }; Tiny(int i) { assign(i); } Tiny& operator=(int i) { assign(i); return *this; } }; operator int() const { return v; } // konverzi int tpusra

Amikor egy Tiny egy egsztl kap rtket vagy kezdrtket, ellenrizzk, hogy az rtk a megengedett tartomnyba esik-e. Minthogy egy Tiny msolsakor nincs szksg az rtkellenrzsre, az alaprtelmezett msol konstruktor s rtkads ppen megfelel. Ahhoz, hogy a Tiny vltozkra is lehetv tegyk az egszeknl szoksos mveleteket, hatrozzuk meg a Tiny-rl int-re val automatikus konverzit, a Tiny::operator int()-et. Jegyezzk meg, hogy a konverzi cltpusa az opertor nevnek rsze s nem szabad kirni, mint a konverzis fggvny visszatrsi rtkt:
Tiny::operator int() const { return v; } int Tiny::operator int() const { return v; } // helyes // hiba

Ilyen tekintetben a konverzis opertor a konstruktorra hasonlt. Ha egy int helyn egy Tiny szerepel, akkor arra a helyre a megfelel int rtk fog kerlni:
int main() { Tiny c1 = 2; Tiny c2 = 62; Tiny c3 = c2-c1; Tiny c4 = c3; int i = c1+c2; c1 = c1+c2; i = c3-64; c2 = c3-64; c3 = c4;

// c3 = 60 // nincs tartomnyellenrzs (nem szksges) // i = 64 // tartomnyhiba: c1 nem lehet 64 // i = -4 // tartomnyhiba: c2 nem lehet -4 // nincs tartomnyellenrzs (nem szksges)

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

363

A konverzis fggvnyek klnsen hasznosak olyan adatszerkezetek kezelsekor, amelyeknl az adatoknak (a konverzis opertor ltal definilt) kiolvassa egyszer feladat, ellenttben az rtkadssal s a kezdrtk-adssal. Az istream s ostream tpusok egy konverzi segtsgvel tmogatjk az albbihoz hasonl vezrlsi szerkezeteket:
while (cin>>x) cout<<x;

A cin>>x bemeneti mvelet egy istream& referencit ad vissza, amely automatikusan a cin objektum llapott tkrz rtkre alaktdik. Ezt azutn a while utasts ellenrzi (21.3.3.). ltalban azonban nem j tlet adatvesztssel jr automatikus konverzit meghatrozni kt tpus kztt. Clszer takarkoskodni a konverzis opertorok bevezetsvel. Ha tl sok van bellk, az a kifejezsek tbbrtelmsghez vezethet. A tbbrtelmsget mint hibt jelzi ugyan a fordtprogram, de kikszblni fradsgos lehet. Taln a legjobb eljrs az, ha kezdetben nevestett fggvnyekkel vgeztetjk az talaktst (pldul X::make_int()). Ha ksbb valamelyik ilyen fggvny annyira npszer lesz, hogy alkalmazsa nem elegns tbb, akkor kicserlhetjk az X::operator int() konverzis opertorra. Ha vannak felhasznli konverzik s felhasznli opertorok is, lehetsges, hogy tbbrtelmsg lp fel a felhasznli s a beptett opertorok kztt:
int operator+(Tiny,Tiny); void f(Tiny t, int i) { t+i; // hiba, tbbrtelm: operator+(t,Tiny(i)) vagy int(t)+i ? }

Ezrt vagy felhasznli konverzikra ptsnk, vagy felhasznli opertorokra, de ne mindkettre.

11.4.1. Tbbrtelmsg
Egy X osztly objektum rtkadsa egy V tpus rtkkel akkor megengedett, ha van olyan X::operator=(Z) rtkad opertor, amely szerint V tpus egyben Z is, vagy ha van egy egyedi konverzi V-rl Z-re. A kezdeti rtkadsnl hasonl a helyzet.

Forrs: http://www.doksi.hu

364

Absztrakcis mdszerek

Bizonyos esetekben a kvnt tpus rtket konstruktorok s konverzis opertorok ismtelt alkalmazsval llthatjuk el. Ezt a helyzetet kzvetlen konverzival kell megoldani; az automatikus felhasznli konverziknak csak egy szintje megengedett. Nha a kvnt tpus rtk tbbflekppen is ltrehozhat, ez pedig hiba:
class X { /* ... */ X(int); X(char*); }; class Y { /* ... */ Y(int); }; class Z { /* ... */ Z(X); }; X f(X); Y f(Y); Z g(Z); void k1() { f(1); f(X(1)); f(Y(1)); g("Mack"); g(X("Doc")); g(Z("Suzy"));

// hiba: tbbrtelm f(X(1)) vagy f(Y(1))? // rendben // rendben // hiba: kt felhasznli konverzi szksges; g(Z(X("Mack")))-et // nem prbltuk // rendben: g(Z(X("Doc"))) // rendben: g(Z(X("Suzy")))

A felhasznli konverzikat a fordtprogram csak akkor veszi figyelembe, ha szksgesek egy hvs feloldshoz:
class XX { /* ... */ XX(int); }; void h(double); void h(XX); void k2() { h(1); }

// h(double(1)) vagy h(XX(1))? h(double(1))!

A h(1) hvs a h(double(1)) hvst jelenti, mert ehhez csak egy szabvnyos (s nem felhasznli) konverzira van szksg (7.4). A konverzis szablyok se nem a legegyszerbben megvalsthat, se nem a legegyszerbben lerhat, de nem is az elkpzelhet legltalnosabb szablyok. Viszont viszonylag biztonsgosak s alkalmazsukkal kevsb fordulnak el meglep eredmnyek. A programoznak sokkal knnyebb egy tbbrtelmsget feloldani, mint megtallni egy hibt, amit egy nem sejtett konverzi alkalmazsa okoz.

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

365

Az elemzs sorn alkalmazott szigoran alulrl felfel val halads elvbl az is kvetkezik, hogy a visszatrsi rtket nem vesszk figyelembe a tlterhelsek feloldsakor:
class Quad { public: Quad(double); // ... }; Quad operator+(Quad,Quad); void f(double a1, double a2) { Quad r1 = a1+a2; Quad r2 = Quad(a1)+a2; }

// ktszeres pontossg sszeads // Quad aritmetika kiknyszertse

Ezen tervezsi md vlasztsnak egyik oka, hogy a szigor alulrl felfel val halads elve rthetbb, a msik pedig az, hogy nem a fordtprogram dolga eldnteni, milyen fok pontossgot akar a programoz egy sszeadsnl. Ha egy kezdeti vagy egyszer rtkads mindkt oldalnak eldlt a tpusa, akkor az rtkads feloldsa ezen tpusok figyelembe vtelvel trtnik:
class Real { public: operator double(); operator int(); // ... }; void g(Real a) { double d = a; int i = a; d = a; i = a;

// d = a.double(); // i = a.int(); // d = a.double(); // i = a.int();

Az elemzs itt is alulrl felfel trtnik, egyszerre csak egy opertornak s paramtereinek figyelembe vtelvel.

Forrs: http://www.doksi.hu

366

Absztrakcis mdszerek

11.5. Bart fggvnyek


Amikor egy fggvnyt egy osztly tagjaknt adunk meg, hrom, logikailag klnbz dolgot jelznk: 1. A fggvny hozzfrhet az osztly deklarcijnak privt rszeihez. 2. A fggvny az osztly hatkrbe tartozik. 3. A fggvnyt az osztly egy objektumra kell meghvni (egy this mutat ll a rendelkezsre). Ha egy tagfggvnyt static-knt hatrozunk meg (10.2.4), akkor ez csak az els kt tulajdonsgot jelenti; ha friend-knt (bartknt), csak az elst. Definiljunk pldul egy Matrix-ot egy Vector-ral szorz opertort. Termszetesen mind a Matrix, mind a Vector osztly alkalmazza az adatrejts elvt, s csak tagfggvnyeiken keresztl kezelhetjk ket. A szorzst megvalst fggvny azonban nem lehet mindkt osztly tagja. Nem is akarunk ltalnos, alacsonyszint hozzfrst megengedni, hogy minden felhasznl rhassa s olvashassa a Matrix s Vector osztlyok teljes adatbrzolst. Ahhoz, hogy ezt elkerljk, a * opertort mindkt osztlyban friend (bart) fggvnyknt hatrozzuk meg:
class Matrix; class Vector { float v[4]; // ... friend Vector operator*(const Matrix&, const Vector&); }; class Matrix { Vector v[4]; // ... friend Vector operator*(const Matrix&, const Vector&); }; Vector operator*(const Matrix& m, const Vector& v) { Vector r; for (int i = 0; i<4; i++) { // r[i] = m[i] * v; r.v[i] = 0; for (int j = 0; j<4; j++) r.v[i] += m.v[i].v[j] * v.v[j]; } return r; }

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

367

A friend deklarcit az osztly privt s nyilvnos rszbe is tehetjk. A tagfggvnyekhez hasonlan a bart fggvnyeket is az osztly deklarcijban adjuk meg, gy ugyanolyan mrtkben hozztartoznak az osztly fellethez, mint a tagfggvnyek. Egy osztly tagfggvnye lehet egy msik osztly bart fggvnye:
class List_iterator { // ... int* next(); }; class List { friend int* List_iterator::next(); // ... };

Nem szokatlan helyzet, hogy egy osztly sszes tagfggvnye egy msik osztly bartja. Ennek jelzsre egy rvidts szolgl:
class List { friend class List_iterator; // ... };

E deklarci hatsra a List_iterator osztly sszes tagfggvnye a List osztly bart fggvnye lesz. Vilgos, hogy friend osztlyokat csak szorosan sszetartoz fogalmak kifejezsre szabad hasznlnunk. Szmos esetben viszont vlaszthatunk, hogy egy osztlyt tag (begyazott osztly) vagy nem tag bartknt adunk meg (24.4).

11.5.1. A bart fggvnyek elrse


A tagfggvnyek deklarcijhoz hasonlan a friend deklarcik sem vezetnek be j nevet a tartalmaz hatkrbe:
class Matrix { friend class Xform; friend Matrix invert(const Matrix&); // ... }; Xform x; Matrix (*p)(const Matrix&) = &invert; // hiba: a hatkrben nincs Xform // hiba: a hatkrben nincs invert()

Forrs: http://www.doksi.hu

368

Absztrakcis mdszerek

Nagy programok s osztlyok esetben elnys, ha egy osztly nem ad hozz titokban j neveket a tartalmaz hatkrhz, azoknl a sablon osztlyoknl pedig, amelyek tbb klnbz krnyezetben pldnyosthatk (13. fejezet), ez kifejezetten fontos. A bart (friend) osztlyt elzleg meg kell adnunk a tartalmaz hatkrben vagy ki kell fejtennk az osztlyt kzvetlenl tartalmaz nem osztly tpus hatkrben. A kzvetlenl tartalmaz nvtr hatkrn kvli neveket nem vesznk figyelembe:
class AE { /* ... */ }; // nem "bartja" Y-nak

namespace N { class X { /* ... */ }; // Y "bartja" class Y { friend class X; friend class Z; friend class AE; }; class Z { /* ... */ }; // Y "bartja" }

A bart fggvnyeket ugyangy megadhatjuk pontosan, mint a bart osztlyokat, de elrhetjk paramtereik alapjn is (8.2.6), mg akkor is, ha nem a kzvetlenl tartalmaz hatkrben adtuk meg:
void f(Matrix& m) { invert(m); // a Matrix "bart" invert()-je }

Ebbl kvetkezik, hogy egy bart fggvnyt vagy egy tartalmaz hatkrben kell kzvetlenl megadnunk, vagy az osztlynak megfelel paramterrel kell rendelkeznie, msklnben nem hvhatjuk meg:
// a hatkrben nincs f() class X { friend void f(); friend void h(const X&); }; void g(const X& x) { f(); h(x); // X h() "bartja" } // rtelmetlen // paramtere alapjn megtallhat

// a hatkrben nincs f()

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

369

11.5.2. Bartok s tagfggvnyek


Mikor hasznljunk bart fggvnyt s mikor jobb vlaszts egy tagfggvny egy mvelet szmra? Az els szempont egy osztlynl az, hogy minl kevesebb fggvny rje el kzvetlenl az adatbrzolst s hogy az adatlekrdezst a segdfggvnyek megfelel krvel tmogassuk. Ezrt az elsdleges krds nem az, hogy ez a fggvny tag legyen, statikus tag vagy bart?, hanem az, hogy tnyleg szksge van-e az brzols elrsre? ltalban kevesebb fggvnynek van erre tnylegesen szksge, mint els rnzsre gondolnnk. Bizonyos mveleteknek tagoknak kell lennik: pldul a konstruktoroknak, destruktoroknak s a virtulis fggvnyeknek (12.2.6). Sokszor azonban van mrlegelsi lehetsg. Mivel egy tagfggvny neve az osztlyra nzve loklisnak szmt, a fggvnyt inkbb tagfggvnyknt adjuk meg, hacsak nem szl valamilyen rv amellett, hogy nem tag fggvny legyen. Vegynk egy X osztlyt, amely egy mvelet klnfle mdozatait jelenti meg:
class X { // ... X(int); int m1(); int m2() const; friend int f1(X&); friend int f2(const X&); friend int f3(X);

};

A tagfggvnyeket csak az adott osztly objektumaira alkalmazhatjuk; felhasznli talaktst a fordt nem vgez:
void g() { 99.m1(); 99.m2(); }

// hiba: nem prbltuk X(99).m1()-et // hiba: nem prbltuk X(99).m2()-t

Az X(int) talaktst a fordt nem alkalmazza, hogy a 99-bl X tpus objektumot csinljon. Az f1() globlis fggvny hasonl tulajdonsggal rendelkezik, mert nem const referencia paramterekre a fordt nem alkalmaz felhasznli talaktst (5.5, 11.3.5). Az f2() s f3() paramtereire azonban alkalmazhat ilyen:

Forrs: http://www.doksi.hu

370

Absztrakcis mdszerek

void h() { f1(99); // hiba: nem prbltuk f1(X(99))-et f2(99); // rendben: f2(X(99)); f3(99); // rendben: f3(X(99)); }

Ezrt egy, az objektum llapott megvltoztat mvelet vagy tag legyen, vagy pedig nem const referencia (vagy nem const mutat) paramter globlis fggvny. Olyan mveletet, amelynek balrtkre van szksge, ha alapvet adattpusra alkalmazzuk (=, *=, ++ stb.), a legtermszetesebb mdon felhasznli tpus tagfggvnyeknt definilhatunk. Megfordtva: ha egy mvelet sszes operandusa automatikusan konvertlhat, akkor a megvalst fggvny csak olyan nem tag fggvny lehet, amely paramterknt const referencia vagy nem referencia tpust vr. Ez gyakori eset olyan mveleteket megvalst fggvnyeknl, melyeknek nincs szksgk balrtkre, ha alapvet adattpusra alkalmazzuk azokat (+, -, || stb.). Az ilyen mveleteknek gyakran az operandus-osztly brzolsnak elrsre van szksgk, ezrt aztn a ktoperandus opertorok a friend fggvnyek leggyakoribb forrsai. Ha nincs tpuskonverzi, akkor nincs knyszert ok arra sem, hogy vlasszunk a tagfggvny s a referencia paramtert vr bart fggvny kzl. Ilyenkor a programoz aszerint dnthet, hogy melyik formt rszesti elnyben. A legtbb embernek pldul jobban tetszik az inv(m) jells, ha egy m Matrix inverzrl van sz, mint a msik lehetsges m.inv() jells. Ha azonban az inv() azt a Matrix-ot invertlja, amelyikre alkalmaztuk s nem egy j Matrix-knt adja vissza az inverzt, akkor persze csak tagfggvny lehet. Ha ms szempontok nem jtszanak kzre, vlasszunk tagfggvnyt. Nem tudhatjuk, hogy valaki nem ad-e majd meg valamikor egy konverzis opertort, s azt sem lthatjuk elre, hogy egy jvbeli mdosts nem vltoztatja-e meg az objektum llapott. A tagfggvnyhvsi forma vilgoss teszi a felhasznl szmra, hogy az objektum llapota megvltozhat; referencia paramter hasznlata esetn ez sokkal kevsb nyilvnval. Tovbb sokkal rvidebbek a kifejezsek egy tagfggvny trzsben, mint a kls fggvnybeli megfelelik; egy nem tag fggvnynek meghatrozott paramterre van szksge, mg a tagfggvny automatikusan hasznlhatja a this mutatt. Ezenkvl, mivel a tagfggvnyek neve az osztlyra nzve loklisnak szmt, a kls fggvnyek neve hosszabb szokott lenni.

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

371

11.6. Nagy objektumok


A complex osztly mveleteinek paramtereit complex tpusknt hatroztuk meg. Ez azt jelenti, hogy a paramterek minden mveletnl lemsoldnak. Kt double msolsa kltsges mvelet lehet ugyan, de valsznleg olcsbb, mint egy pr mutat. Nem minden osztlynak van azonban knyelmesen kicsi brzolsa. A nagymrv msolsokat elkerlend, megadhatunk referencia tpus paramtereket kezel fggvnyeket:
class Matrix { double m[4][4]; public: Matrix(); friend Matrix operator+(const Matrix&, const Matrix&); friend Matrix operator*(const Matrix&, const Matrix&); };

A referencik alkalmazsa nagy objektumokra is lehetv teszi a szoksos aritmetikai mveletek hasznlatt, nagymrv msolsok nlkl is. Mutatkat nem hasznlhatunk, mert a mutatra alkalmazott opertorok jelentst nem vltoztathatjuk meg. Az sszeadst gy definilhatnnk:
Matrix operator+(const Matrix& arg1, const Matrix& arg2) { Matrix sum; for (int i=0; i<4; i++) for (int j=0; j<4; j++) sum.m[i][j] = arg1.m[i][j] + arg2.m[i][j]; return sum; }

Ez az operator+() az operandusokat referencikon keresztl ri el, de objektum-rtket ad vissza. Referencit visszaadni hatkonyabbnak tnhet:
class Matrix { // ... friend Matrix& operator+(const Matrix&, const Matrix&); friend Matrix& operator*(const Matrix&, const Matrix&); };

Ez szablyos kd, de egy memria-lefoglalsi problmt okoz. Minthogy a fggvnybl az eredmnyre vonatkoz referencit adjuk vissza, az eredmny maga nem lehet automatikus vltoz (7.3). Mivel egy mveletet tbbszr is alkalmazhatunk egy kifejezsen bell, az

Forrs: http://www.doksi.hu

372

Absztrakcis mdszerek

eredmny nem lehet loklis statikus vltoz sem. Ezrt aztn az eredmnynek jellemzen a szabad trban foglalnnk helyet. A visszatrsi rtk msolsa (vgrehajtsi idben, kds adatmretben mrve) gyakran olcsbb, mint az objektum szabad trba helyezse s onnan eltvoltsa, s programozni is sokkal egyszerbb. Az eredmny msolsnak elkerlsre vannak mdszerek. A legegyszerbb ezek kzl egy statikus objektumokbl ll tmeneti tr hasznlata:
const max_matrix_temp = 7; Matrix& get_matrix_temp() { static int nbuf = 0; static Matrix buf[max_matrix_temp]; if (nbuf == max_matrix_temp) nbuf = 0; return buf[nbuf++];

Matrix& operator+(const Matrix& arg1, const Matrix& arg2) { Matrix& res = get_matrix_temp(); // ... return res; }

gy egy Matrix msolsa csak egy kifejezs rtkn alapul rtkadskor trtnik meg. De az g legyen irgalmas, ha olyan kifejezst tallnnk rni, amelyhez max_matrix_temp-nl tbb ideiglenes rtk kell! Hibkra kevesebb lehetsget ad mdszer, ha a mtrix tpust csak a tnyleges adatot trol tpus lerjaknt (handle, 25.7) hatrozzuk meg. gy aztn a mtrixlerk gy kpviselhetik az objektumokat, hogy kzben a lehet legkevesebb helyfoglals s msols trtnik (11.12 s 11.14[18]). Ez az eljrs azonban a visszatrsi rtkknt objektumot s nem referencit vagy mutatt hasznl opertorokon alapul. Egy msik mdszer hromvltozs mveletek meghatrozsra s azok olyankor automatikusan trtn meghvsra tmaszkodik, amikor olyan kifejezsek kirtkelse trtnik, mint a=b+c vagy a+b*i (21.4.6.3 s 22.4.7).

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

373

11.7. Alapvet opertorok


ltalnossgban, ha X egy tpus, akkor az X(const X&) msol konstruktor kezeli azt az esetet, amikor egy X tpus objektumnak egy ugyanilyen objektumot adunk kezdrtkl. Nem lehet elgg hangslyozni, hogy a kezdeti s az egyszer rtkads klnbz mveletek (10.4.4.1). Ez klnsen fontos akkor, amikor a destruktorral is szmolnunk kell. Ha az X osztlynak van valamilyen nem magtl rtetd feladatot pldul a szabad trban lefoglalt memria felszabadtst vgz destruktora, akkor az osztlynak valsznleg szksge lesz az objektum ltrehozst, megsemmistst s msolst vgz sszes fggvnyre:
class X { // ... X(Sometype); X(const X&); X& operator=(const X&); ~X(); };

// konstruktor: objektumok ltrehozsa // msol konstruktor // msol rtkads: takarts s msols // destruktor: takarts

Ezenkvl mg hromfle helyzetben msoldik egy objektum: tadott fggvnyparamterknt, fggvny visszatrsi rtkeknt, illetve kivtelknt. Ha paramterknt kerl tadsra, egy addig kezdrtk nlkli vltoz, a formlis paramter kap kezdrtket. Ennek szerepe azonos az egyb kezdeti rtkadsokval. Ugyanez igaz a visszatrsi rtkre s a kivtelre is, mg ha kevsb nyilvnval is. Ilyen esetben a msol konstruktor vgzi a munkt:
string g(string arg) { return arg; } int main () { string s = "Newton"; s = g(s); } // string rtk szerint tadva (msol konstruktor hasznlatval) // string visszaadsa (msol konstruktor hasznlatval)

// string kezdrtket kap (msol konstruktor hasznlatval)

Vilgos, hogy az s vltoz rtknek "Newton"-nak kell lennie a g() meghvsa utn. Nem nehz feladat az s rtknek egy msolatt az arg formlis paramterbe msolni; a string osztly msol konstruktornak hvsa ezt megteszi. Amikor g() visszaadja a visszatrsi rtket, a string(const string&) jabb hvsa kvetkezik, amikor egy olyan ideiglenes

Forrs: http://www.doksi.hu

374

Absztrakcis mdszerek

vltoz kap rtket, amely aztn az s-nek ad rtket. Hatkonysgi okokbl az egyik (de csak az egyik) msolst gyakran elhagyhatjuk. Az ideiglenes vltozk aztn persze a string::~string() destruktor segtsgvel megsemmislnek (10.4.10). Ha a programoz nem ad meg msol konstruktort vagy msol rtkadst egy osztly szmra, a fordtprogram hozza ltre a hinyz fggvnyt vagy fggvnyeket (10.2.5). Ez egyben azt is jelenti, hogy a msol mveletek nem rkldnek (12.2.3).

11.7.1. Explicit konstruktorok


Alaprtelmezs szerint az egyparamter konstruktor egyben automatikus konverzit is jelent. Bizonyos tpusok szmra ez idelis:
complex z = 2; // z kezdeti rtkadsa complex(2)-vel

Mskor viszont nem kvnatos s hibk forrsa lehet:


string s = 'a'; // s karakterlnc, int('a') szm elemmel

Nagyon valszntlen, hogy az s-et megad programoz ezt akarta volna. Az automatikus konverzikat az explicit kulcssz alkalmazsval akadlyozhatjuk meg. Vagyis egy explicit-knt megadott konstruktort csak kzvetlen mdon lehet meghvni. gy ahol elvileg egy msol konstruktorra van szksg (11.3.4), ott az explicit konstruktor nem hvdik meg automatikusan:
class String { // ... explicit String(int n); // n bjt lefoglalsa String(const char* p);// a kezdrtk (p) egy C stlus karakterlnc }; String s1 = 'a'; String s2(10); String s3 = String(10); String s4 = "Brian"; String s5("Fawlty"); void f(String); String g() { // hiba: nincs automatikus char->String talakts // rendben: String 10 karakternyi hellyel // rendben: String 10 karakternyi hellyel // rendben: s4 = String("Brian")

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

375

f(10); f(String(10)); f("Arthur"); f(s1);

// hiba: nincs automatikus int ->String talakts // rendben: f(String("Arthur"))

String* p1 = new String("Eric"); String* p2 = new String(10); } return 10; // hiba: nincs automatikus int ->String talakts

A klnbsg akztt, hogy


String s1 = 'a'; // hiba: nincs automatikus char ->String talakts

s akztt, hogy
String s2(10); // rendben: karakterlnc 10 karakternyi hellyel

cseklynek tnhet, de igazi kdban kevsb az, mint kitallt pldkban. A Date osztlyban egy sima int-et hasznltunk az v brzolsra (10.3). Ha a Date osztly ltfontossg szerepet jtszott volna, akkor bevezethettk volna a Year osztlyt, hogy fordtsi idben szigorbb ellenrzsek trtnjenek:
class Year { int y; public: explicit Year(int i) : y(i) { } operator int() const { return y; } }; class Date { public: Date(int d, Month m, Year y); // ... }; Date d3(1978,feb,21); Date d4(21,feb,Year(1978)); // hiba: a 21 nem Year tpus // rendben

// Year ltrehozsa int-bl // talakts Year-rl int-re

A Year egy egyszer csomagol (beburkol, wrapper) osztly az int krl. Az operator int()-nek ksznheten a Year automatikusan mindenhol int-t alakul, ahol szksges. Azltal, hogy a konstruktort explicit-knt adtuk meg, biztostottuk, hogy az int-nek Year-r va-

Forrs: http://www.doksi.hu

376

Absztrakcis mdszerek

l alaktsa csak ott trtnik meg, ahol ezt krjk s a vletlen rtkadsok a fordtskor kiderlnek. Minthogy a Year tagfggvnyeit knny helyben kifejtve (inline) fordtani, a futsi id s a szksges trhely nvekedstl sem kell tartanunk. Hasonl mdszer tartomny (intervallum) tpusokra (25.6.1) is alkalmazhat.

11.8. Indexels
Osztly tpus objektumoknak az operator [ ] (subscripting) fggvny segtsgvel adhatunk sorszmot (indexet). Az operator[ ] msodik paramtere (az index) brmilyen tpus lehet, gy aztn vektorokat, asszociatv tmbket stb. is definilhatunk. Pldaknt rjuk most jra a 5.5-beli pldt, amelyben egy asszociatv tmb segtsgvel rtunk egy fjlban a szavak elfordulst megszmol kis programot. Akkor egy fggvnyt hasznltunk, most egy asszociatv tmb tpust:
class Assoc { struct Pair { string name; double val; Pair(string n ="", double v =0) :name(n), val(v) { } }; vector<Pair> vec; Assoc(const Assoc&); Assoc& operator=(const Assoc&); public: Assoc() {} const double& operator[ ](const string&); double& operator[ ](string&); void print_all() const; }; // a msolst megakadlyozand privt // a msolst megakadlyozand privt

Az Assoc tpus objektumok Pair-ek vektort tartalmazzk. A megvalstsban ugyanazt az egyszer s nem tl hatkony keressi mdszert hasznljuk, mint az 5.5 pontban:
double& Assoc::operator[ ](string& s) // megkeressk s-t; ha megtalltuk, visszaadjuk az rtkt; ha nem, j Pair-t hozunk // ltre s az alaprtelmezett 0 rtket adjuk vissza {

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

377

for (vector<Pair>::iterator p = vec.begin(); p!=vec.end(); ++p) if (s == p->name) return p->val; vec.push_back(Pair(s,0)); } return vec.back().val; // kezdrtk: 0 // az utols elem visszaadsa (16.3.3)

Minthogy az Assoc objektum brzolsa kvlrl nem rhet el, szksg van egy kimeneti fggvnyre:
void Assoc::print_all() const { for (vector<Pair>::const_iterator p = vec.begin(); p!=vec.end(); ++p) cout << p->name << ": " << p->val << '\n'; }

Vgl megrhatjuk a fprogram egyszer vltozatt:


int main() // szavak elfordulsnak megszmllsa a bemeneten { string buf; Assoc vec; while (cin>>buf) vec[buf]++; vec.print_all(); }

Az asszociatv tmb tlett tovbbfejleszti a 17.4.1 pont. Az operator[ ]() fggvnyeknek tagfggvnynek kell lennik.

11.9. Fggvnyhvs
A fggvnyhvs (function call), vagyis a kifejezs(kifejezs-lista) jells gy tekinthet, mint egy ktoperandus mvelet, ahol a kifejezs a bal oldali, a kifejezs-lista pedig a jobb oldali operandus. A () hv opertor a tbbi opertorhoz hasonl mdon tlterhelhet. Az operator()() paramterlistjnak kirtkelse s ellenrzse a szoksos paramter-tadsi szablyok szerint trtnik. A fggvnyhv opertor tlterhelse elssorban olyan tpusok ltrehozsakor hasznos, amelyeknek csak egy mveletk van vagy ltalban csak egy mveletk hasznlatos.

Forrs: http://www.doksi.hu

378

Absztrakcis mdszerek

A ( ) hv mvelet legnyilvnvalbb s taln legfontosabb alkalmazsa az, hogy a valamikppen fggvnyknt viselked objektumokat fggvnyknt hvhassuk meg. Egy fggvnyknt viselked objektumot fggvnyszer vagy egyszeren fggvnyobjektumnak hvunk (18.4). Az ilyen fggvnyobjektumok fontosak, mert lehetv teszik, hogy olyan kdot rjunk, amelyben valamilyen nem magtl rtetd mveletet paramterknt adunk t. A standard knyvtrban pldul sok olyan algoritmus tallhat, melyek egy fggvnyt hvnak meg egy trol minden elemre. Vegyk az albbi pldt:
void negate(complex& c) { c = -c; } void f(vector<complex>& aa, list<complex>& ll) { for_each(aa.begin(),aa.end(),negate); for_each(ll.begin(),ll.end(),negate); }

// a vektor sszes elemnek neglsa // a lista sszes elemnek neglsa

Ez a vektor s a lista minden elemt neglja. Mi lenne, ha a lista minden elemhez complex(2,3)-at akarnnk hozzadni? Ezt knnyen megtehetjk:
void add23(complex& c) { c += complex(2,3); } void g(vector<complex>& aa, list<complex>& ll) { for_each(aa.begin(),aa.end(),add23); for_each(ll.begin(),ll.end(),add23); }

Hogyan tudnnk egy olyan fggvnyt rni, melyet tbbszr meghvva egy-egy tetszleges rtket adhatunk az elemekhez? Olyasmire van szksgnk, aminek megadhatjuk a kvnt rtket s utna ezt az rtket hasznlja fel minden hvsnl. Ez a fggvnyeknek nem termszetes tulajdonsga. Jellemz megoldsknt valahova a fggvnyt krlvev krnyezetbe helyezve adjuk t az rtket, ami nem tiszta megolds. Viszont rhatunk egy osztlyt, amely a megfelel mdon mkdik:
class Add { complex val; public: Add(complex c) { val = c; } // az rtk mentse Add(double r, double i) { val = complex(r,i); } }; void operator()(complex& c) const { c += val; } // a paramter nvelse az rtkkel

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

379

Egy Add osztly objektum kezdrtknek egy komplex szmot adunk, majd a ( ) mveletet vgrehajtatva ezt a szmot hozzadjuk a paramterhez:
void h(vector<complex>& aa, list<complex>& ll, complex z) { for_each(aa.begin(),aa.end(),Add(2,3)); for_each(ll.begin(),ll.end(),Add(z)); }

Ez a tmb minden elemhez complex(2,3)-at fog adni, a lista elemeihez pedig z-t. Vegyk szre, hogy Add(z) egy olyan objektumot hoz ltre, amelyet aztn a for_each ismtelten felhasznl. Nem egyszeren egy egyszer vagy tbbszr meghvott fggvnyrl van sz. A tbbszr meghvott fggvny az Add(z) operator()() fggvnye. Mindez azrt mkdik, mert a for_each egy sablon (template), amely a ( ) mveletet alkalmazza a harmadik paramterre, anlkl, hogy trdne vele, mi is igazbl a harmadik paramter:
template<class Iter, class Fct> Fct for_each(Iter b, Iter e, Fct f) { while (b != e) f(*b++); return f; }

Els pillantsra ez a mdszer furcsnak tnhet, de egyszer, hatkony, s nagyon hasznos (lsd 3.8.5, 18.4). Az operator()() tovbbi npszer alkalmazsai a rszlncok kpzsre vagy tbb dimenzis tmbk indexelsre (22.4.5) val hasznlat. Az operator()()-nak tagfggvnynek kell lennie.

11.10. Indirekci
A -> indirekci (hivatkozstalants, dereferencing) opertort egyparamter, uttagknt hasznlt opertorknt definilhatjuk. Legyen adott egy osztly:
class Ptr { // ... X* operator->(); };

Forrs: http://www.doksi.hu

380

Absztrakcis mdszerek

Ekkor a Ptr osztly objektumokat az X osztly tagjainak elrsre hasznlhatjuk, a mutatkhoz nagyon hasonl mdon:
void f(Ptr p) { p->m = 7; }

// (p.operator->())->m = 7

A p objektumnak a p.operator->() mutatv val talaktsa nem fgg attl, hogy milyen m tagra mutat. Az operator->() ebben az rtelemben egyoperandus uttag-opertor, formai kvetelmnyei viszont nem jak, gy a tagnevet ki kell rni utna:
void g(Ptr p) { X* q1 = p->; X* q2 = p.operator->(); }

// szintaktikus hiba // rendben

A ->() opertor tlterhelsnek f alkalmazsa az okos vagy intelligens mutat (smart pointer) tpusok ltrehozsa, azaz olyan objektumok, amelyek mutatknt viselkednek, de radsul valamilyen tennivalt vgeznek, valahnyszor egy objektumot rnek el rajtuk keresztl. Pldul ltrehozhatunk egy Rec_ptr osztlyt, amellyel a lemezen trolt Rec osztly objektumok rhetek el. A Rec_ptr konstruktora egy nevet vr, melynek segtsgvel a keresett objektum a lemezen megkereshet, a Rec_ptr::operator->() fggvny a memriba tlti az objektumot, amikor azt a Rec_ptr-en keresztl el akarjuk rni, a Rec_ptr destruktora pedig szksg esetn a megvltozott objektumot a lemezre rja:
class Rec_ptr { const char* identifier; Rec* in_core_address; // ... public: Rec_ptr(const char* p) : identifier(p), in_core_address(0) { } ~Rec_ptr() { write_to_disk(in_core_address,identifier); } Rec* operator->(); }; Rec* Rec_ptr::operator->() { if (in_core_address == 0) in_core_address = read_from_disk(identifier); return in_core_address; }

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

381

A Rec_ptr-t gy hasznlhatjuk:
struct Rec { string name; // ... }; void update(const char* s) { Rec_ptr p(s); p->name = "Roscoe"; // ... // a Rec tpus, amire Rec_ptr mutat

// Rec_ptr ellltsa s-bl // s mdostsa; ha szksges, elszr beolvassa a lemezrl

Termszetesen az igazi Rec_ptr egy sablon lenne, a Rec tpus pedig paramter. Egy valsgos program hibakezelst is tartalmazna s kevsb nav mdon kezeln a lemezt. Kznsges mutatk esetben a -> hasznlata egyenrtk az egyvltozs * s [ ] hasznlatval. Ha adott egy tpus:
Y* p;

akkor teljesl a kvetkez:


p->m == (*p).m == p[0].m

Ahogy mr megszokhattuk, a felhasznli opertorokra nzve ez nem biztostott. Szksg esetn persze gondoskodhatunk errl:
class Ptr_to_Y { Y* p; public: Y* operator->() { return p; } Y& operator*() { return *p; } Y& operator[ ](int i) { return p[i]; } };

Ha egy osztlyban tbb ilyen opertort hatrozunk meg, akkor tancsos lehet ezt gy tenni, hogy a fenti egyenrtksg teljesljn, ugyangy, mint ahogy ++x s x+=1 is j, ha x=x+1-gyel azonos hatssal jr, ha x egy olyan osztly vltoz, amelyben a ++, +=, + mveletek rtelmezettek.

Forrs: http://www.doksi.hu

382

Absztrakcis mdszerek

A -> opertor tlterhelhetsge nem csak kis klnlegessg, hanem rdekes programok egy osztlya szmra fontos is, azon oknl fogva, hogy az indirekci (dereferencing) kulcsfogalom, a -> opertor tlterhelse pedig tiszta, kzvetlen s hatkony mdja annak egy programban val megjelentsre. A bejrk (itertorok) (19. fejezet) jellemz s lnyegi pldt adnak erre. A -> opertor msik haszna, hogy korltozott, de hasznos mdon lehetv teszi a C++ nyelvben a delegcit (24.2.4). Az operator-> tagfggvny kell, hogy legyen. Csak gy hasznlhat, ha mutatt vagy olyan tpust ad vissza, amelyre a -> alkalmazhat. Ha egy sablon osztly szmra adjuk meg, sokszor elfordul, hogy nem is kerl tnyleges felhasznlsra, ezrt sszer e megszorts ellenrzst a tnyleges hasznlatig elhalasztani.

11.11. Nvels s cskkents


Amint a programoz kitall egy intelligens mutatt, sokszor dnt gy, hogy ehhez a ++ nvel (increment) s -- cskkent (decrement) mvelet is hozztartozik, a beptett tpusokra rtelmezett nvels s cskkents mintjra. Ez klnsen nyilvnval s szksges olyankor, amikor a cl egy kznsges mutatnak egy okosra val kicserlse, amely azonos jelents mellett csak nmi futsi idej hiba-ellenrzssel van kiegsztve. Vegyk pldul az albbi egybknt problematikus hagyomnyos programot:
void f1(T a) { T v[200]; T* p = &v[0]; p--; *p = a; ++p; *p = a; } // hagyomnyos hasznlat

// hopp: 'p' tartomnyon kvli s nem kaptuk el // rendben

A p mutatt ki szeretnnk cserlni valamilyen Ptr_to_T osztly objektumra, amelyre csak akkor tudjuk alkalmazni az indirekci opertort, ha tnyleg egy objektumra mutat. Azt is el szeretnnk rni, hogy p-t csak gy lehessen nvelni vagy cskkenteni, ha tmbn belli objektumra mutat, mg a nvels vagy cskkents hatsra is. Valami ilyesmit szeretnnk teht:
class Ptr_to_T { // ... };

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

383

void f2(T a) // ellenrztt { T v[200]; Ptr_to_T p(&v[0],v,200); p--; *p = a; // futsi idej hiba: 'p' tartomnyon kvli ++p; *p = a; // rendben }

A nvel s cskkent opertorok az egyetlenek a C++ nyelv opertorai kztt, amelyek eltagknt (prefix) s uttagknt (postfix) egyarnt hasznlhatk. Ezrt a Ptr_to_T tpus szmra mindkt fajta nvel s cskkent opertort definilnunk kell:
class Ptr_to_T { T* p; T* array; int size; public: Ptr_to_T(T* p, T* v, int s); // csatols s mret v tmbhz, a kezdrtk p Ptr_to_T(T* p); // csatols nll objektumhoz, a kezdrtk p Ptr_to_T& operator++(); Ptr_to_T operator++(int); Ptr_to_T& operator--(); Ptr_to_T operator--(int); }; T& operator*(); // eltag // uttag // eltag // uttag // eltag

Az int paramterrel jelezzk a ++ uttagknt val alkalmazst. Magt az int-et nem hasznljuk, csak l-paramter, amely az el- s uttagknt val hasznlat kztt tesz klnbsget. Knnyen megjegyezhetjk, melyik melyik, ha arra gondolunk, hogy a tbbi (aritmetikai s logikai) egy paramter opertorhoz hasonlan az l-paramter nlkli ++ s -- az eltagknt, a paramteres vltozat a furcsa uttagknt val mkdshez kell. A Prt_to_T osztlyt hasznlva a plda egyenrtk az albbival:
void f3(T a) // ellenrztt { T v[200]; Ptr_to_T p(&v[0],v,200); p.operator--(0);

Forrs: http://www.doksi.hu

384

Absztrakcis mdszerek

p.operator*() = a; p.operator++(); p.operator*() = a;

// futsi idej hiba: 'p' tartomnyon kvli // rendben

A Prt_to_T osztly kiegsztse gyakorlatnak marad (11.14[19]). tdolgozsa olyan sablonn, amely kivteleket is hasznl a futsi idben fellp hibk jelzsre, egy msik gyakorlat (14.12[12]). A 13.6.3 egy mutatsablont mutat be, amely rklds hasznlata mellett is jl mkdik.

11.12. Egy karakterlnc osztly


me a String osztly egy valsgosabb vltozata, amely a cljainknak mg ppen megfelel. Ez a karakterlnc-osztly tmogatja az rtk szerinti mkdst (value semantics, rtk-szemantika), a karakterr s -olvas mveleteket, az ellenrztt s ellenrizetlen elrst, az adatfolyam ki- s bemenetet, a karakterliterlokat, az egyenlsgvizsgl s sszefz mveleteket. A karakterlncokat C stlus, nullval lezrt karaktertmbknt trolja, a msolsok szmnak cskkentsre pedig hivatkozsszmllt hasznl. Egy tbbet tud s/vagy tbb szolgltatst nyjt string osztly rsa j gyakorlat (11.14[7-12]). Ha megvagyunk vele, eldobhatjuk a gyakorlatainkat s hasznlhatjuk a standard knyvtrbeli string-et (20. fejezet). Az n majdnem valsgos String osztlyom hrom segdosztlyt hasznl: az Srep-et, hogy tbb azonos rtk String is hasznlhassa ugyanazt az eltrolt adatot, ha azonos az rtkk; a Range-et az rtktartomny-megsrtsi hibkat jelz kivtelek kivltshoz; s a Cref-et, hogy egy rs s olvass kztt klnbsget tev index-opertort tmogasson:
class String { struct Srep; Srep *rep; public: class Cref; class Range { }; }; // ... // adatbrzols // referencia char-ra // kivtelkezelshez

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

385

A tbbi taghoz hasonlan a tagosztlyokat (member class, amit gyakran hvnak begyazott osztlynak, nested class-nak is) deklarlhatjuk az osztlyban, majd ksbb kifejthetjk:
struct String::Srep { char* s; int sz; int n; // mutat az elemekre // karakterek szma // hivatkozsszmll

Srep(int nsz, const char* p) { n = 1; sz = nsz; s = new char[sz+1]; strcpy(s,p); } ~Srep() { delete[ ] s; } Srep* get_own_copy() { if (n==1) return this; n--; return new Srep(sz,s); } void assign(int nsz, const char* p) { if (sz != nsz) { delete[ ] s; sz = nsz; s = new char[sz+1]; } strcpy(s,p); } private: Srep(const Srep&); Srep& operator=(const Srep&); };

// hely a lezr nulla szmra is

// msols, ha szksges

// a msols megakadlyozsa

A String osztlynak megvannak a szoksos konstruktorai, destruktora s rtkad mveletei is:


class String { // ...

Forrs: http://www.doksi.hu

386

Absztrakcis mdszerek

String(); String(const char*); String(const String&); String& operator=(const char *); String& operator=(const String&); ~String(); }; // ...

// x = "" // x = "abc" // x = msik_karakterlnc

A String osztly rtk szerint mkdik, azaz egy s1=s2 rtkads utn s1 s s2 kt teljesen klnbz karakterlnc lesz, vagyis ha ksbb az egyiket mdostjuk, akkor annak nem lesz hatsa a msikra. A msik megolds az lenne, ha a String osztly mutatkkal dolgozna. Ekkor az s1=s2 rtkads utn s2 megvltoztatsa s1-et is rinten. Ha egy osztlynak megvannak a hagyomnyos aritmetikai mveletei, mint a komplex szmokkal, vektorokkal, mtrixokkal, karakterlncokkal vgzettek, n elnyben rszestem az rtk szerinti mkdst. Ahhoz viszont, hogy ennek tmogatsa ne kerljn tl sokba, a String-et lerknt brzolom, amely az adatbrzolsra mutat, amit csak szksg esetn kell msolni:
String::String() // az alaprtelmezett rtk egy res karakterlnc { rep = new Srep(0,""); } String::String(const String& x) { x.rep->n++; rep = x.rep; } String::~String() { if (--rep->n == 0) delete rep; } String& String::operator=(const String& x) { x.rep->n++; if (--rep->n == 0) delete rep; rep = x.rep; return *this; } // msol rtkads // vdelem az "st = st" ellen // az brzols megosztsa // msol konstruktor // az brzols megosztsa

A const char* paramter l-msol mveletek bevezetsvel a karakterliterlokat is megengedjk:

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

387

String::String(const char* s) { rep = new Srep(strlen(s),s); } String& String::operator=(const char* s) { if (rep->n == 1) rep->assign(strlen(s),s); else { rep->n--; rep = new Srep(strlen(s),s); } return *this; }

// Srep jrahasznostsa // j Srep hasznlata

Az egyes karakterlncokat elr opertorok megtervezse nehz, mert az idelis megolds az lenne, ha ezek a szoksos jellst (azaz a [ ]-t) hasznlnk, a lehet leghatkonyabbak lennnek s a paramter rtkt is ellenriznk. Sajnos, ez a hrom kvetelmny nem teljesthet egyszerre. n gy ksztettem el az osztlyt, hogy hatkony ellenrizetlen mveleteket adtam meg (egy kicsit knyelmetlenebb jellssel), illetve kevsb hatkony ellenrztt eljrsokat (a hagyomnyos jellssel):
class String { // ... void check(int i) const { if (i<0 || rep->sz<=i) throw Range(); } char read(int i) const { return rep->s[i]; } void write(int i, char c) { rep=rep->get_own_copy(); rep->s[i]=c; } Cref operator[ ](int i) { check(i); return Cref(*this,i); } char operator[ ](int i) const { check(i); return rep->s[i]; } int size() const { return rep->sz; } }; // ...

Az tlet az, hogy a hagyomnyos [ ] jellssel az ellenrztt elrs legyen biztostott a kznsges felhasznls szmra, de a felhasznlnak legyen mdja egyszerre vgignzni a teljes tartomnyt s a gyorsabb, ellenrizetlen elrst hasznlni:

Forrs: http://www.doksi.hu

388

Absztrakcis mdszerek

int hash(const String& s) { int h = s.read(0); const int max = s.size(); for (int i = 1; i<max; i++) h ^= s.read(i)>>1; return h; }

// ellenrzs nlkli hozzfrs s-hez

Nehz dolog egy opertort, pldul a [ ]-t gy meghatrozni, hogy az az r s az olvas jelleg hozzfrst is tmogassa, ha nem fogadhat el az a megolds, hogy egyszeren egy referencit adunk vissza, amit aztn a felhasznl kedve szerint felhasznlhat. Itt pldul ez nem lehetsges, mert a String-et gy hatroztam meg, hogy az egyes rtkadssal, paramter-tadssal stb. megadott rtk String-ek ugyanazt a bels brzolst hasznljk, mg az egyik String-et tnylegesen nem rjk: az rtk msolsa csak ekkor trtnik meg. Ezt a mdszert ltalban rskori msolsnak vagy msols rskor-nak (copy-on-write) hvjk. A tnyleges msolst a String::get_own_copy() vgzi. Abbl a clbl, hogy az elr fggvnyeket helyben kifejtve (inline) lehessen fordttatni, olyan helyre kell elhelyezni definicijukat, ahonnan az Srep osztly elrhet. Teht vagy az Srep-et kell a String osztlyon bell megadni, vagy pedig az elr fggvnyeket kell inline-knt meghatrozni a String-en kvl s az String::Srep utn (11.14[2]). Megklnbztetend az rst s az olvasst, a String::operator[ ]() egy Cref-et ad vissza, ha nem const objektumra hvtk meg. A Cref gy viselkedik, mint a char&, azzal a klnbsggel, hogy rsakor meghvja a String::Sref::get_own_copy()-t:
class String::Cref { friend class String; String& s; int i; Cref(String& ss, int ii) : s(ss), i(ii) { } public: operator char() const { return s.read(i); } void operator=(char c) { s.write(i,c); } }; // hivatkozs s[i]-re

// rtk kijellse // rtk mdostsa

Pldul:
void f(String s, const String& r) { char c1 = s[1]; // c1 = s.operator[ ](1).operator char() s[1] = 'c'; // s.operator[ ](1).operator=('c')

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

389

char c2 = r[1]; r[1] = 'd';

// c2 = r.operator[ ](1) // hiba: char rtkads, r.operator[ ](1) = 'd'

Vegyk szre, hogy egy nem const objektumra az s.operator[ ](1) rtke Cref(s,1) lesz. Ahhoz, hogy teljess tegyk a String osztlyt, meghatrozunk mg egy sor hasznos fggvnyt:
class String { // ... String& operator+=(const String&); String& operator+=(const char*); friend ostream& operator<<(ostream&, const String&); friend istream& operator>>(istream&, String&); friend bool operator==(const String& x, const char* s) { return strcmp(x.rep->s, s) == 0; } friend bool operator==(const String& x, const String& y) { return strcmp(x.rep->s, y.rep->s) == 0; } friend bool operator!=(const String& x, const char* s) { return strcmp(x.rep->s, s) != 0; } friend bool operator!=(const String& x, const String& y) { return strcmp(x.rep->s, y.rep->s) != 0; }

};

String operator+(const String&, const String&); String operator+(const String&, const char*);

Hely-megtakarts cljbl a ki- s bemeneti opertorokat, illetve az sszefzst meghagytam gyakorlatnak. A fprogram pusztn egy kiss megdolgoztatja a String opertorokat:
String f(String a, String b) { a[2] = 'x'; char c = b[3]; cout << "Az f-ben: " << a << ' ' << b << ' ' << c << '\n'; return b; }

Forrs: http://www.doksi.hu

390

Absztrakcis mdszerek

int main() { String x, y; cout << "Adjon meg kt karakterlncot!\n"; cin >> x >> y; cout << "Bemenet: " << x << ' ' << y << '\n'; String z = x; y = f(x,y); if (x != z) cout << "Az x srlt!\n"; x[0] = '!'; if (x == z) cout << "Az rs nem sikerlt!\n"; cout << "Kilps: " << x << ' ' << y << ' ' << z << '\n'; }

Ebbl a String osztlybl mg hinyoznak fontosnak vagy akr alapvetnek tekinthet dolgok, pldul nincs az rtket C stlus adatknt visszaad mvelet (11.14[10], 20. fejezet).

11.13. Tancsok
[1] Opertorokat elssorban azrt adjunk meg, hogy a szoksos hasznlati mdot tmogassuk. 11.1. [2] Nagy operandusok esetben hasznljunk const referencia paramtereket. 11.6. [3] Nagy rtkeket ad eredmnyeknl fontoljuk meg az eredmny-visszaads optimalizlst. 11.6. [4] Hagyatkozzunk az alaprtelmezett msol mveletekre, ha azok megfelelek az osztlyunk szmra. 11.3.4. [5] Brljuk fell vagy tiltsuk meg az alaprtelmezett msolst, ha az egy adott tpus szmra nem megfelel. 11.2.2. [6] Ha egy fggvnynek szksge van az adatbrzols elrsre, inkbb tagfggvny legyen. 11.5.2. [7] Ha egy fggvnynek nincs szksge az adatbrzols elrsre, inkbb ne legyen tagfggvny. 11.5.2. [8] Hasznljunk nvteret, hogy a segdfggvnyeket osztlyukhoz rendeljk. 11.2.4. [9] Szimmetrikus mveletekre hasznljunk nem tag fggvnyeket. 11.3.2. [10] Tbbdimenzis tmb indexelsre hasznljunk ()-t. 11.9. [11] Az egyetlen mret paramter konstruktorok legyenek explicit-ek. 11.7.1.

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

391

[12] ltalnos clra hasznljuk a szabvnyos string-et (20. fejezet), ne a gyakorlatok megoldsa rvn kapott sajt vltozatot. 11.12. [13] Legynk vatosak az automatikus konverzik bevezetsekor. 11.4. [14] Bal oldali operandusknt balrtket vr mveleteket tagfggvnyekkel valstsunk meg. 11.3.5.

11.14. Gyakorlatok
1. (*2) Milyen konverzikat hasznlunk az albbi program egyes kifejezseiben:
struct X { int i; X X(int); operator+(int); }; struct Y { int i; Y(X); Y operator+(X); operator int(); }; extern X operator*(X, Y); extern int f(X); X x = 1; Y y = x; int i = 2; int main() { i + 10; y + 10; y + 10 * y; x + y + i; x * x + i; f(7); f(y); y + y; 106 + y; }

Mdostsuk a programot gy, hogy futs kzben kirjon minden megengedett kifejezst. 2. (*2) Fejezzk be s teszteljk a 11.12-beli String osztlyt.

Forrs: http://www.doksi.hu

392

Absztrakcis mdszerek

3. (*2) Definiljuk az INT osztlyt, amely pontosan gy viselkedik, mint egy int. (Segtsg: definiljuk az INT::operator int()-et). 4. (*1) Definiljuk a RINT osztlyt, amely pontosan gy viselkedik, mint egy int, azzal a klnbsggel, hogy csak a kvetkez mveletek engedlyezettek: (egy s kt paramter) +, (egy s kt paramter) -, *, / s %. (Segtsg: ne definiljuk a RINT::operator int()-et). 5. (*3) Definiljuk a LINT osztlyt, amely pontosan gy viselkedik, mint a RINT, de legalbb 64 bites pontossg. 6. (*4) Definiljunk egy tetszleges pontossg aritmetikt tmogat osztlyt. Teszteljk az 1000 faktorilisnak kiszmttatsval. (Segtsg: a String osztlyhoz hasonl trszervezsre lesz szksg.) 7. (*2) Hatrozzunk meg a String osztly szmra egy kls bejrt (itertort):
class String_iter { // hivatkozs karakterlncra s annak elemre public: String_iter(String& s); // bejr s szmra char& next(); // hivatkozs a kvetkez elemre }; // igny szerint tovbbi mveletek

Hasonltsuk ezt ssze hasznossg, stlus s hatkonysg szempontjbl a String egy bels bejrjval. (Vagyis adott egy kurrens elemnk a String-ben, s a vele kapcsolatos mveletek.) 8. (*1.5) A () opertor tlterhelsvel hatrozzunk meg egy rszlnc-mveletet egy karakterlnc-osztly szmra. Milyen ms mveletet szeretnnk egy karakterlncon vgezni? 9. (*3) Tervezzk meg gy a String osztlyt, hogy a rszlnc-opertort egy rtkads bal oldaln is fel lehessen hasznlni. Elszr egy olyan vltozatot rjunk, amelyiknl egy rszlncot egy azonos hosszsg teljes karakterlncra lehet cserlni, aztn egy olyat, amelyiknl eltrre. 10. (*2) Definiljuk a String osztly szmra az rtket C stlus adatknt visszaad mveletet. Vitassuk meg annak elnyeit s htrnyait, hogy ez egy konverzis opertor. Vitassuk meg a C stlus adat szmra szksges memria lefoglalsnak klnfle mdjait. 11. (*2.5) Tervezznk s ksztsnk egy egyszer regulris kifejezs illeszt eszkzt a String osztly szmra. 12. (*1.5) Mdostsuk gy a 11.14[11]-beli eszkzt, hogy az mkdjn a standard knyvtrbeli string-gel. A string defincijt nem vltoztathatjuk meg.

Forrs: http://www.doksi.hu

11. Opertorok tlterhelse

393

13. (*2) rjunk egy programot, amit opertor-tlterhelssel s a makrk hasznlatval olvashatatlann tesznk. Egy tlet: hatrozzuk meg + -t gy az INT-ekre, hogy - -t jelentsen s fordtva. Ezutn egy makrval hatrozzuk meg gy az intet, hogy INT-et jelentsen. Brljuk fell a npszer fggvnyeket referencia tpus paramtereket hasznl fggvnyekknt. Nhny flrevezet megjegyzssel is nagy zavart lehet kelteni. 14. (*3) Cserljk ki egy bartunkkal a 11.14[13] feladatra adott megoldsunkat, s prbljuk meg futtatsa nlkl kiderteni, mit csinl. A gyakorlat vgre meg fogjuk tanulni, hogy mit ne tegynk tbb. 15. (*2) Definiljuk a Vec4 tpust, mint ngy float-bl ll vektort. Definiljuk a [ ] mveletet. Adjuk meg a +, -, *, /, =, +, +=, -=, *= s /= mveleteket a vektorok s float-ok egyttes hasznlatra. 16. (*3) Definiljuk a Mat4 tpust, mint ngy Vec4-bl ll vektort. Definiljuk a [ ] mveletet, mint ami Vec4-et ad vissza, ha a Mat4-re alkalmazzuk. Adjuk meg a szoksos mtrix-mveleteket. Ksztsnk fggvnyt, amely a Gauss-fle kikszbls mdszert alkalmazza egy Mat4-re. 17. (*2) Definiljunk egy Vector tpust, amely a Vec4-hez hasonlt, de Vector::Vector(int) konstruktornak paramterknt megadhatjuk a mretet. 18. (*3) Definiljunk egy Matrix tpust, amely a Mat4-hez hasonlt, de Matrix::Matrix(int,int) konstruktornak paramterknt megadhatjuk a dimenzikat. 19. (*2) Fejezzk be a 11.11 pont Ptr_to_T osztlyt s ellenrizzk. Legyenek meg legalbb a *, ->, =, ++ s -- mveletek. Ne okozzunk futsi idej hibt, csak ha egy hibs mutatt tnylegesen felhasznlnak. 20. (*1) Adott kt struktra:
struct S { int x, y; }; struct T { char* p; char* q; };

rjunk egy C osztlyt, melynek segtsgvel majdnem gy hasznlhatjuk valamely S s T x-t s p-jt, mint ha x s p C tagja lenne. 21. (*1.5) Definiljuk az Index osztlyt a mypow(double,Index) hatvnyoz fggvny kitevje szmra. Talljunk mdot arra, hogy 2**I meghvja mypow(2,I)-t. 22. (*2) Definiljuk az Imaginary osztlyt kpzetes szmok brzolsra. Definiljuk a Complex osztlyt ennek alapjn. Ksztsk el az alapvet aritmetikai mveleteket.

Forrs: http://www.doksi.hu

12
Szrmaztatott osztlyok
Ne szaportsuk az objektumokat, ha nem szksges. (W. Occam) Fogalmak s osztlyok Szrmaztatott osztlyok Tagfggvnyek Ltrehozs s megsemmists Osztlyhierarchik Tpusmezk Virtulis fggvnyek Absztrakt osztlyok Hagyomnyos osztlyhierarchik Absztrakt osztlyok mint felletek Az objektumok ltrehozsnak adott helyre korltozsa Absztrakt osztlyok s osztlyhierarchik Tancsok Gyakorlatok

12.1. Bevezets
A C++ a Simula nyelvtl klcsnzte az osztly, mint felhasznli tpus, illetve az osztlyhierarchia fogalmt, valamint a rendszertervezs azon elvt, hogy a programban hasznlt fogalmak modellezsre osztlyokat hasznljon. A C++ nyjtotta nyelvi szerkezetek kzvetlenl tmogatjk ezeket a tervezsi elveket. Megfordtva is igaz: akkor hasznljuk a C++ nyelvet hatkonyan, ha a nyelvi lehetsgeket a tervezsi elvek tmogatsra hasznljuk. Aki a nyelvi elemeket csak a hagyomnyosabb programozs jellsbeli altmasztsra hasznlja, a C++ valdi erssgnek hasznlatrl mond le.

Forrs: http://www.doksi.hu

396

Absztrakcis mdszerek

Egy fogalom sohasem nmagban ltezik, hanem ms fogalmakkal egytt s erejnek egy rszt is a rokon fogalmakkal val kapcsolatbl merti. Prbljuk csak megmagyarzni, mi az, hogy aut. Hamarosan bevezetjk a kvetkez fogalmakat: kerk, motor, vezet, gyalogos, teheraut, mentk, utak, olaj, gyorshajts, brsg, motel stb. Minthogy a fogalmakat osztlyokknt brzoljuk, felmerl a krds: hogyan brzoljuk a fogalmak kztti kapcsolatokat? Persze tetszleges kapcsolatot egy programozsi nyelvben nem tudunk kzvetlenl kifejezni. De ha tudnnk, akkor sem akarnnk, hiszen osztlyainkat a mindennapi letben hasznlt fogalmaknl szkebben s preczebben akarjuk meghatrozni. A szrmaztatott osztly fogalma s a vele kapcsolatos nyelvi eljrsok clja a viszonyok kifejezse, azaz hogy kifejezzk, mi a kzs az osztlyokban. A kr s a hromszg fogalmban pldul kzs, hogy mindkett skgrbe-alakzat, gy a Circle (Kr) s Triangle (Hromszg) osztlyokat gy rhatjuk le, hogy pontosan meghatrozott (explicit) mdon megmondjuk, hogy a Shape (Alakzat) osztly a kzs bennk. Ha egy programban gy szerepeltetnk krket s hromszgeket, hogy nem vonjuk be a skidom fogalmt, akkor valami lnyegeset mulasztunk el. Ez a fejezet azt feszegeti, mi kvetkezik ebbl az egyszer elvbl ami valjban az ltalban objektumorientltnak nevezett programozsi elv alapja. A nyelvi lehetsgek s mdszerek bemutatsa az egyszertl s konkrttl a bonyolultabb, kifinomultabb, elvontabb fel halad. Sokak szmra ez a megszokottl a kevsb ismert fel val haladst is fogja jelenti. De ez nem csupn egyszer utazs a rgi, rossz mdszerektl az egyedli igaz t fel. Amikor rmutatok egy megkzelts korltaira, hogy a programozt az j fel tereljem, mindig adott problmk kapcsn teszem; ms problmk kapcsn vagy ms sszefggsben lehetsges, hogy a korbbi mdszer alkalmazsa a jobb vlaszts. Hasznlhat programokat az itt trgyalt mdszerek mindegyiknek felhasznlsval rtak mr. A cl az, hogy az olvas megismerje az sszes eljrst, hogy aztn okos s kiegyenslyozott mdon tudjon majd vlasztani kzlk, amikor igazi feladatokat kell megoldania. A fejezetben elszr az objektumorientlt programozst tmogat alapvet nyelvi eszkzket mutatom be, majd egy hosszabb plda kapcsn azt trgyalom, hogyan lehet ezek alkalmazsval jl szerkesztett programot rni. Az objektumorientlt programozst tmogat tovbbi nyelvi eszkzket, pldul a tbbszrs rkldst vagy a futsi idej tpusazonostst a 15. fejezet trgyalja.

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

397

12.2. Szrmaztatott osztlyok


Vegynk egy programot, amely egy cg dolgozit kezeli. Egy effle programban lehet egy ilyen adatszerkezet:
struct Employee { // alkalmazott string first_name, family_name; char middle_initial; Date hiring_date; short department; // ... };

Ezutn meghatrozhatunk egy fnkt is:


struct Manager { Employee emp; set<Employee*> group; short level; // ... }; // fnk // a fnk mint alkalmazott // beosztottak

A fnk egyben alkalmazott is, ezrt az Employee (Alkalmazott) adatokat a Manager (Fnk, vezet) objektum emp tagjban troljuk. Ez nyilvnval lehet a programoz (klnsen egy figyelmes programoz) szmra, de a fordtprogram s az egyb eszkzk sehonnan nem fogjk tudni, hogy a Manager egyben Employee is. Egy Manager* nem Employee* is egyben, gy a kett nem cserlhet fel. Egy Employee-ket tartalmaz listra nem vehetnk fel egy Manager-t a megfelel kd megrsa nlkl. Vagy tpusknyszertst kellene alkalmaznunk a Manager*-ra, vagy az emp tag cmt kellene az alkalmazottak listjra tennnk. Egyik megolds sem elegns s zavar is lehet. A helyes megkzelts az, hogy kifejezetten megmondjuk, a Manager-ek egyben Employee-k is, csak tovbbi adatokat is tartalmaznak:
struct Manager : public Employee { set<Employee*> group; short level; // ... };

A Manager az Employee-bl szrmazik, s fordtva, az Employee a Manager bzisosztlya. A Manager osztlynak megvannak azok a tagjai, amelyek az Employee-nek is (first_name, department stb.) s ezekhez jnnek hozz a sajt tagok (group, level stb.).

Forrs: http://www.doksi.hu

398

Absztrakcis mdszerek

A szrmaztatst gyakran gy brzoljk grafikusan, hogy a szrmaztatott osztlybl egy nyilat rajzolnak a bzisosztly fel, jelezve, hogy a szrmaztatott osztly a bzisosztlyra hivatkozik (s nem fordtva): Employee

Manager

ltalban gy mondjk, a szrmaztatott osztly tulajdonsgokat rkl a bzisosztlytl. Ennek alapjn ezt a kapcsolatot rkldsnek (rkls, inheritance) is hvjk. Az angol kifejezsek a bzisposztlyra s a szrmaztatott osztlyra: base class (vagy superclass), illetve derived class (subclass). Az utbbi szhasznlat (superclass fosztly, subclass alosztly) azonban zavar lehet, hiszen a szrmaztatott osztly bizonyos rtelemben szlesebb a bzisosztlynl, mivel annl tbb adatot trol s tbb fggvnyt biztost. A szrmaztats npszer s hatkony megvalstsa a szrmaztatott osztly objektumot a bzisosztly olyan objektumaknt brzolja, amely kiegszl a szrmaztatott osztlyra egyedileg jellemz adatokkal is:

Emplyee: first_name family_name ...

Manager: first_name family_name ... group level ...

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

399

A Manager osztlynak az Employee-bl ilyen mdon val szrmaztatsa a Manager tpust az Employee altpusv teszi, gy teht mindenhol, ahol Employee objektum hasznlhat, egy Manager is megfelel. Most mr kszthetnk egy listt az alkalmazottakrl (Employee), akiknek egy rsze vezet beoszts (Manager):
void f(Manager m1, Employee e1) { list<Employee*> elist; elist.push_front(&m1); elist.push_front(&e1); // ...

Ne feledjk, egy Manager egyben Employee is, gy egy Manager*-ot hasznlhatunk Employee*-knt is. Az Employee viszont nem felttlenl Manager, gy Employee*-ot nem hasznlhatunk Manager*-knt. ltalban, ha egy Derived (szrmaztatott) osztlynak egy Base (bzis) osztly nyilvnos bzisosztlya (15.3), akkor egy Base* vltoz tpusknyszerts nlkl kaphat Derived* tpus rtket. A msik irnyban (Base*-rl Derived*-ra) explicit konverzi szksges:
void g(Manager mm, Employee ee) { Employee* pe = &mm; Manager* pm = &ee; pm->level = 2;

// rendben: minden Manager egyben Employee is // hiba: nem minden Employee fnk // katasztrfa: ee nem rendelkezik 'level' taggal

pm = static_cast<Manager*>(pe); // "nyers ervel": mkdik, mert pe // a Manager tpus mm-re mutat pm->level = 2; } // ez is j: pm a Manager tpus mm-re mutat, // amelynek van 'level' tagja

Vagyis mutatk s referencik hasznlatakor a szrmaztatott osztly objektumait gy kezelhetjk, mint a bzisosztly objektumait. A msik irnyban ez nem ll. A static_cast s a dynamic_cast hasznlatt a 15.4.2 pont rja le. Egy osztly bzisosztlyknt val hasznlata egyenrtk az osztly egy (nvtelen) objektumnak deklarlsval. Kvetkezskppen egy osztlyt csak akkor hasznlhatunk bzisosztlyknt, ha definiltuk (5.7):

Forrs: http://www.doksi.hu

400

Absztrakcis mdszerek

class Employee; class Manager : public Employee { // ... };

// csak deklarci, nem definci // hiba: Employee nem definilt

12.2.1. Tagfggvnyek
Az egyszer adatszerkezetek mint a Manager s az Employee nem tl rdekesek s sokszor nem is klnsebben hasznosak. Az informcit megfelel tpusknt kell megadnunk, melyhez az elvgezhet mveletek is hozztartoznak, s ezt gy kell megtennnk, hogy kzben nem ktdnk az adott brzolshoz:
class Employee { string first_name, family_name; char middle_initial; // ... public: void print() const; string full_name() const { return first_name + ' ' + middle_initial + ' ' + family_name; } // ... }; class Manager : public Employee { // ... public: void print() const; // ... };

A szrmaztatott osztlyok tagfggvnyei ugyangy elrhetik a bzisosztly nyilvnos (public) s vdett (protected, 15.3) tagjait, mintha maguk vezettk volna be azokat:
void Manager::print() const { cout << "A keresett nv " << full_name() << '\n'; // ... }

A szrmaztatott osztly azonban nem ri el a bzisosztly privt (private) tagjait:


void Manager::print() const { cout << "A keresett nv " << family_name << '\n'; // ... }

// hiba!

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

401

A Manager::print() msodik vltozatt a fordt nem fogja lefordtani. A szrmaztatott osztlynak nincs klnleges engedlye a bzisosztly privt tagjainak elrsre, gy a Manager::print() szmra a family_name nem rhet el. Nmelyek meglepdnek ezen, de gondoljuk el, mi lenne fordtott esetben: ha a szrmaztatott osztly tagfggvnye elrhetn a bzisosztly privt tagjait. A privt tag fogalma rtelmetlenn vlna azltal, hogy a programoz hozzfrhetne az osztly privt rszhez egy osztlyt szrmaztatva belle. Tovbb nem lehetne tbb egy privt tag hasznlatt a tags bart (friend) fggvnyek tnzsvel megkeresni. Az egsz program minden forrsllomnyt t kne nzni: a szrmaztatott osztlyokat s azok fggvnyeit keresni, majd a szrmaztatott osztlyokbl szrmaztatott tovbbi osztlyokat s azok fggvnyeit s gy tovbb. Ez legjobb esetben is fraszt s sokszor kivitelezhetetlen is. Ott, ahol ez elfogadhat, inkbb vdett (protected) s ne privt (private) tagokat hasznljunk. Egy vdett tag a szrmaztatott osztlyok szmra olyan, mint egy nyilvnos (public), a tbbiek szmra azonban privtnak minsl (15.3). A legtisztbb megolds ltalban az, ha a szrmaztatott osztly a bzisosztlynak csak a nyilvnos tagjait hasznlja:
void Manager::print() const { Employee::print(); // alkalmazottak adatainak kirsa cout << level; // ... // a fnkkre vonatkoz adatok kirsa

Vegyk szre, hogy a :: hatkr opertort kellett hasznlni, mert a print() fggvnyt a Manager osztly jradefinilja. A fggvnynevek ilyen mdon val jrafelhasznlsa igen ltalnos. Ha vatlanul ilyet runk:
void Manager::print() const { print(); // hopp! } // a fnkkre vonatkoz adatok kirsa

a program vratlan mdon jra s jra meg fogja hvni nmagt.

Forrs: http://www.doksi.hu

402

Absztrakcis mdszerek

12.2.2. Konstruktorok s destruktorok


Egyes szrmaztatott osztlyoknak konstruktorokra van szksgk. Ha a bzisosztlynak vannak ilyen fggvnyei, akkor az egyiket meg is kell hvni. Az alaprtelmezett konstruktorokat automatikusan is meghvhatjuk, de ha a bzisosztly minden konstruktora paramtert ignyel, akkor a megfelel konstruktort csak explicit mdon lehet meghvni. Vegyk a kvetkez pldt:
class Employee { string first_name, family_name; short department; // ... public: Employee(const string& n, int d); // ... }; class Manager : public Employee { set<Employee*> group; // beosztottak short level; // ... public: Manager(const string& n, int d, int lvl); // ... };

A bzisosztly konstruktora a szrmaztatott osztly konstruktornak definicijban kap paramtereket. Ebbl a szempontbl a bzisosztly konstruktora gy viselkedik, mintha a szrmaztatott osztly tagja lenne (10.4.6):
Employee::Employee(const string& n, int d) : family_name(n), department(d) { // ... } // tagok kezdeti rtkadsa

Manager::Manager(const string& n, int d, int lvl) : Employee(n,d), // a bzisosztly kezdeti rtkadsa level(lvl) // tagok kezdeti rtkadsa { // ... }

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

403

Egy szrmaztatott osztly konstruktora csak a sajt tagjai s a bzisosztly konstruktora szmra adhat meg kezdrtket; a bzisosztly tagjainak nem:
Manager::Manager(const string& n, int d, int lvl) : family_name(n), // hiba: family_name nem deklarlt a Manager osztlyban department(d), // hiba: department nem deklarlt a Manager osztlyban level(lvl) { // ... }

A fenti definci hrom hibt tartalmaz: nem hvja meg az Employee konstruktort, s kt zben is megprbl kzvetlenl kezdrtket adni az Employee tagjainak. Az osztlyba tartoz objektumok alulrl felfel plnek fel; elszr a bzisosztly, aztn a tagok, vgl maga a szrmaztatott osztly. A megsemmists fordtott sorrendben trtnik: elszr a szrmaztatott osztly, aztn a tagok, vgl a bzisosztly. A tagok a deklarci sorrendjben jnnek ltre s fordtott sorrendben semmislnek meg (10.4.6 s 15.2.4.1).

12.2.3. Msols
Egy osztlyba tartoz objektum msolst a msol konstruktor s az rtkadsok hatrozzk meg (10.4.4.1):
class Employee { // ... Employee& operator=(const Employee&); Employee(const Employee&); }; void f(const Manager& m) { Employee e = m; // e ltrehozsa m Employee rszbl e = m; // m Employee rsznek msolsa e-be }

Minthogy az Employee osztly msol fggvnyei nem tudnak a Manager osztlyrl, a Manager objektumnak csak az Employee rsze fog lemsoldni. Az objektumnak ez a felszeleteldse (slicing), azaz a tny, hogy ekkor az objektumnak csak egy szelete msoldik le, meglep lehet s hibkhoz vezethet. A felszeletelds megakadlyozsa az egyik oka annak, hogy osztlyhierarchiba tartoz objektumok esetben clszerbb mutatkat s referencikat hasznlnunk. A hatkonysgi megfontolsok mellett tovbbi ok, hogy megrizzk a tbbalak (polimorfikus) viselkedst (2.5.4 s 12.2.6).

Forrs: http://www.doksi.hu

404

Absztrakcis mdszerek

Jegyezzk meg, hogy ha nem hatrozunk meg msol rtkad opertort, akkor a fordtprogram fog ltrehozni egyet. Ebbl kvetkezik, hogy az rtkad opertorok nem rkldnek. (A konstruktorok soha.)

12.2.4. Osztlyhierarchik
Egy szrmaztatott osztly lehet maga is bzisosztly:
class Employee { /* ... */ }; class Manager : public Employee { /* ... */ }; class Director : public Manager { /* ... */ };

Az egymssal kapcsolatban ll osztlyok ilyen halmazt hagyomnyosan osztlyhierarchinak hvjuk. A hierarchia leggyakrabban fa szokott lenni, de lehet ennl ltalnosabb grf is.:
class Temporary { /* ... */ }; class Secretary : public Employee { /* ... */ }; class Tsec : public Temporary, public Secretary { /* ... */ }; class Consultant : public Temporary, public Manager { /* ... */ };

brval: Employee

Temporary

Secretary Tsec

Manager

Consultant

Director

Vagyis ahogy a 15.2 pont rszletesen elmagyarzza, a C++ nyelv kpes az osztlyoknak egy irnytott krmentes grfjt kifejezni.

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

405

12.2.5. Tpusmezk
Ha a deklarcikban alkalmazott knyelmes rvidtsnl tbbre akarjuk hasznlni az osztlyok szrmaztatst, meg kell vlaszolnunk a kvetkez krdst: ha adott egy Base* mutat, akkor milyen szrmaztatott osztlyba tartozik az objektum, amelyre mutat? A problmnak ngy alapvet megoldsa van: 1. 2. 3. 4. rjk el, hogy csak egyfle objektum jhessen szba (2.7, 13. fejezet). Helyezznk egy tpusmezt a bzisosztlyba s a fggvnyek ezt krdezzk le. Hasznljuk a dynamic_cast-ot (dinamikus tpuskonverzi, 15.4.2, 15.4.5). Hasznljunk virtulis fggvnyeket (2.5.5, 12.2.6).

Bzisosztlyra hivatkoz mutatkat gyakran hasznlunk olyan trol osztlyokban (container class), mint a halmaz (set), a vektor (vector) s a lista (list). Ekkor az els megolds homogn listkat, azaz azonos tpus objektumokat eredmnyez. A tbbi megolds lehetv tesz heterogn listkat is, azaz olyanokat, ahol klnbz tpus objektumok (vagy ilyenekre hivatkoz mutatk) lehetnek. A 3. megolds a 2. megoldsnak a nyelv ltal tmogatott vltozata, a 4. pedig a 2.-nak egyedi, tpusbiztos talaktsa. Az 1. s a 4. megolds prostsai klnsen rdekesek s ersek; majdnem mindig vilgosabb kdot eredmnyeznek, mint a 2. vagy a 3. megolds. Nzznk meg elszr egy tpusmezs megoldst, hogy lssuk, legtbbszr mirt kerlend. A fnk alkalmazott pldt gy rhatnnk t:
struct Employee { enum Empl_type { M, E }; Empl_type type; Employee() : type(E) { } string first_name, family_name; char middle_initial; Date hiring_date; short department; // ...

}; struct Manager : public Employee { Manager() { type = M; } set<Employee*> group; short level; // ... // beosztottak

};

Forrs: http://www.doksi.hu

406

Absztrakcis mdszerek

Most mr rhatunk egy fggvnyt, amely minden Employee-rl ki tudja rni az informcit:
void print_employee(const Employee* e) { switch (e->type) { case Employee::E: cout << e->family_name << '\t' << e->department << '\n'; // ... break; case Employee::M: { cout << e->family_name << '\t' << e->department << '\n'; // ... const Manager* p = static_cast<const Manager*>(e); cout << " szint " << p->level << '\n'; // ... break; } } }

Az alkalmazottak listja gy rhat ki:


void print_list(const list<Employee*>& elist) { for (list<Employee*>::const_iterator p = elist.begin(); p!=elist.end(); ++p) print_employee(*p); }

Ez jl mkdik, klnsen az egyetlen programoz ltal fenntartott kis programokban. Alapvet gyengje, hogy az a felttele, hogy a programoz a megfelel (s a fordtprogram ltal nem ellenrizhet mdon) kell, hogy kezelje a tpusokat. A problmt ltalban slyosbtja, hogy az olyan fggvnyek, mint a print_employee, a szba jhet osztlyok kzs vonsait hasznljk ki:
void print_employee(const Employee* e) { cout << e->family_name << '\t' << e->department << '\n'; // ... if (e->type == Employee::M) { const Manager* p = static_cast<const Manager*>(e); cout << " szint " << p->level << '\n'; // ... } }

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

407

Egy nagy fggvnyben, ahol sok szrmaztatott tpust kell kezelni, nehz lehet az sszes ilyen tpusmez-ellenrzst megtallni. De ha megtalltuk is, nehz lehet megrteni, hogy mi is trtnik. Tovbb, ha a rendszer j Employee-vel bvl, az sszes fontos fggvnyt mdostani kell vagyis az sszes olyat, amelyik ellenrzi a tpusmezt. Minden olyan fggvnyt meg kell vizsglni, amelyiknek egy vltoztats utn szksge lehet a tpusmez ellenrzsre. Ez azt jelenti, hogy hozz kell frni a kritikus forrskdhoz s kln munkt jelent a vltoztats utni teszt is. A tpusknyszerts rulkod jele annak, hogy javtani lehetne a kdon. Vagyis a tpusmezs megolds hibkra ad lehetsget s nehezen mdosthat kdot eredmnyez. Ahogy a rendszer mrete n, a problma slyosbodik, mert a tpusmez alkalmazsa ellentmond a modularits s az adatrejts elvnek. Minden tpusmezt hasznl fggvnynek ismernie kell az sszes olyan osztly brzolst (s megvalstsuk egyb rszleteit), amely a tpusmezs osztly leszrmazottja. Ezenkvl ha van egy olyan adat (pldul egy tpusmez), amely minden szrmaztatott osztlybl elrhet, akkor a programoz hajlamos arra, hogy tovbbi ilyen mezket hozzon ltre. A kzs bzisosztly gy mindenfle hasznos informcik gyjtemnyv vlik. Ez aztn a bzisosztly s a szrmaztatott osztlyok megvalstsnak legkevsb kvnatos sszefondshoz vezet. A tisztbb felpts s knnyebb mdosthatsg kedvrt a klnll dolgokat kezeljk kln, a klcsns fggseket pedig kerljk el.

12.2.6. Virtulis fggvnyek


A virtulis fggvnyek azltal kerlik el a tpusmezs megolds problmit, hogy segtsgkkel a programoz olyan fggvnyeket deklarlhat a bzisosztlyban, amelyeket a szrmaztatott osztlyok fellbrlhatnak. A fordt- s betltprogram gondoskodik az objektumtpusok s az alkalmazott fggvnyek sszhangjrl:
class Employee { string first_name, family_name; short department; // ... public: Employee(const string& name, int dept); virtual void print() const; // ... };

Forrs: http://www.doksi.hu

408

Absztrakcis mdszerek

A virtual kulcssz azt jelenti, hogy a print() fggvny felletknt szolgl az ebben az osztlyban definilt print() fggvnyhez, illetve a szrmaztatott osztlyokban definilt print() fggvnyekhez. Ha a szrmaztatott osztlyokban szerepelnek ilyenek, a fordtprogram mindig az adott Employee objektumnak megfelel fggvnyt fogja meghvni. Egy virtulis fggvny akkor szolglhat felletknt a szrmaztatott osztlyokban definilt fggvnyekhez, ha azoknak ugyanolyan tpus paramtereik vannak, mint a bzisosztlybelinek, s a visszatrsi rtk is csak nagyon csekly mrtkben klnbzik (15.6.2). A virtulis tagfggvnyeket nha metdusoknak (method) is hvjk. A virtulis fggvnyt mindig definilnunk kell abban az osztlyban, amelyben elszr deklarltuk, hacsak nem tisztn virtulis (pure virtual) fggvnyknt adtuk meg (12.3):
void Employee::print() const { cout << family_name << '\t' << department << '\n'; // ... }

Virtulis fggvnyt akkor is hasznlhatunk, ha osztlybl nem is szrmaztatunk tovbbi osztlyt; ha pedig szrmaztatunk, annak a fggvnybl nem kell felttlenl sajt vltozat. Osztly szrmaztatsakor csak akkor rjunk egy megfelel vltozatot a fggvnybl, ha valban szksges:
class Manager : public Employee { set<Employee*> group; short level; // ... public: Manager(const string& name, int dept, int lvl); void print() const; // ... }; void Manager::print() const { Employee::print(); cout << "\tszint " << level << '\n'; // ... }

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

409

A szrmaztatott osztly azonos nev s azonos tpus paramterekkel br fggvnye fellrja vagy fellbrlja (override) a virtulis fggvny bzisosztlybeli vltozatt. Hacsak kzvetlen mdon meg nem mondjuk, hogy a virtulis fggvny melyik vltozatt akarjuk hasznlni mint az Employee::print() hvsnl , az objektumhoz leginkbb ill fellr fggvny lesz meghvva. A globlis print_employee() fggvny (12.2.5) szksgtelen, mert a helybe a print() tagfggvnyek lptek. Az alkalmazottak (Employee) listjt gy rathatjuk ki:
void print_list(const list<Employee*>& s) { for (list<Employee*>::const_iterator p = s.begin(); p!=s.end(); ++p) (*p)->print(); }

// lsd 2.7.2

De akr gy is:
void print_list(const list<Employee*>& s) { for_each(s.begin(),s.end(),mem_fun(&Employee::print)); }

// lsd 3.8.5

Minden Employee a tpusnak megfelelen rdik ki. A


int main() { Employee e("Brown",1234); Manager m("Smith",1234,2); list<Employee*> empl; empl.push_front(&e); empl.push_front(&m); print_list(empl); }

// 2.5.4

pldul az albbi kimenetet eredmnyezi:


Smith 1234 szint 2 Brown 1234

Ez akkor is mkdik, ha a print_list() fggvnyt azeltt rtuk meg s fordtottuk le, mieltt a Manager osztlyt egyltaln kitalltuk volna! Ez az osztlyoknak egy kulcsfontossg tulajdonsga. Ha helyesen alkalmazzuk, az objektumorientlt tervezs sarokkve lesz s a programok fejlesztsnl bizonyos fok stabilitst ad.

Forrs: http://www.doksi.hu

410

Absztrakcis mdszerek

Azt, hogy az Employee fggvnyei attl fggetlenl helyesen viselkednek, hogy pontosan milyen fajta Employee-re hvtuk meg azokat, tbbalaksgnak (polimorfizmus, polymorphism) nevezzk. A virtulis fggvnyekkel br tpus neve tbbalak tpus (polimorfikus tpus). A C++ nyelvben a tbbalak viselkedst virtulis fggvnyek hasznlatval vagy az objektumoknak mutatkon vagy referencikon t val kezelsvel rhetjk el. Ha kzvetlenl kezelnk egy objektumot s nem mutat vagy referencia segtsgvel, a fordtprogram felismeri annak pontos tpust, gy a futsi idej tbbalaksgra nincs szksg. Vilgos, hogy a tbbalaksg tmogatsa rdekben a fordtprogramnak minden Employee tpus objektumban valamilyen, a tpusra vonatkoz informcit (tpusinformcit) kell nyilvntartania, melynek segtsgvel kpes a megfelel print() fggvnyt meghvni. Ehhez rendszerint egyetlen mutatnyi hely is elg, s erre is csak azon osztlyokban van szksg, amelyeknek van virtulis fggvnyk; teht nem minden osztlyban s mg csak nem is minden szrmaztatott osztlyban. A tpusmezs megolds vlasztsa esetn ehhez kpest jelents mennyisg trterletet kellett volna a tpusmez szmra biztostanunk. Ha egy fggvnyt (miknt a Manager::print()-et is) a :: hatkr-felold opertor segtsgvel hvunk meg, akkor ezltal kikapcsoljuk a virtualitst. Msklnben a Manager::print() vgtelen rekurzit idzne el. A minstett nv hasznlatnak van mg egy elnye: ha egy virtulis fggvny inline (ami el szokott fordulni), akkor a fordtprogram a :: minstvel jelzett hvsokat kpes helyben kifejteni. Ennek segtsgvel a programoz hatkonyan kpes azokat az egyedi eseteket kezelni, amikor mondjuk egy virtulis fggvny ugyanarra az objektumra egy msik fggvnyt is meghv. A Manager::print() fggvny ennek pldja. Minthogy a Manager::print() meghvsakor meghatrozzuk az objektum tpust, az Employee::print() ezt kvet meghvsakor a tpusrl mr nem kell jra dnteni. rdemes megemlteni, hogy a virtulis fggvnyhvs hagyomnyos s nyilvnval megvalstsa az egyszer kzvetett fggvnyhvs (indirekci) (2.5.5), gy a hatkonysg elvesztstl val flelem ne riasszon vissza senkit a virtulis fggvnyek hasznlattl ott, ahol egy kznsges fggvnyhvs elfogadhatan hatkony.

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

411

12.3. Absztrakt osztlyok


Sok osztly hasonlt az Employee osztlyra annyiban, hogy nmagban s szrmaztatott osztlyok bzisosztlyaknt is hasznos. Az ilyen osztlyok szmra elegendek az elz pontban bemutatott mdszerek. De nem minden osztly ilyen. Bizonyos osztlyok, pldul a Shape (Alakzat), olyan elvont fogalmakat jelentenek meg, amelyekhez nem ltezhetnek objektumok. A Shape-nek csak mint bzisosztlynak van rtelme. Ez abbl is lthat, hogy nem tudunk hozz virtulis fggvnyeket rtelmesen definilni:
class Shape { public: virtual void rotate(int) { error("Shape::rotate"); } virtual void draw() { error("Shape::draw"); } // ... };

// nem "elegns"

Egy ilyen meghatrozatlan alakzatot meg tudunk ugyan adni (a nyelv megengedi), de nem sok rtelme van:
Shape s; // butasg: "alak nlkli alakzat"

A dolog azrt rtelmetlen, mert az s minden mvelete hibt fog eredmnyezni. Jobb megolds, ha a Shape osztly virtulis fggvnyeit tisztn virtulis (pure virtual) fggvnyknt deklarljuk. A virtulis fggvnyek az =0 kezdeti rtkadstl lesznek tisztn virtulisak:
class Shape { public: virtual void rotate(int) = 0; virtual void draw() = 0; virtual bool is_closed() = 0; // ... }; // absztrakt osztly // tisztn virtulis fggvny // tisztn virtulis fggvny // tisztn virtulis fggvny

Ha egy osztly legalbb egy tisztn virtulis fggvnnyel rendelkezik, akkor absztrakt osztlynak (elvont osztly, abstract class) hvjuk, ilyen osztlyba tartoz objektumot pedig nem hozhatunk ltre:
Shape s; // hiba: s az absztrakt Shape osztly vltozja lenne

Forrs: http://www.doksi.hu

412

Absztrakcis mdszerek

Az absztrakt osztlyokat csak felletknt (interface), illetve ms osztlyok bzisosztlyaknt hasznlhatjuk:


class Point { /* ... */ }; class Circle : public Shape { public: void rotate(int) { } void draw(); bool is_closed() { return true; } Circle(Point p, int r); private: Point center; int radius; };

// a Shape::rotate fellrsa // a Shape::draw fellrsa // a Shape::is_closed fellrsa

Ha egy tisztn virtulis fggvnyt a szrmaztatott osztlyban nem definilunk, akkor az tisztn virtulis fggvny marad, st, a szrmaztatott osztly is absztrakt osztly lesz. Ez a megvalsts lpcszetes felptst teszi lehetv:
class Polygon : public Shape { public: bool is_closed() { return true; } // ... a draw s a rotate nincs fellrva ... }; // absztrakt osztly // a Shape::is_closed fellrsa

Polygon b; // hiba: a Polygon osztlynak nem lehet objektuma class Irregular_polygon : public Polygon { list<Point> lp; public: void draw(); void rotate(int); // ... }; Irregular_polygon poly(some_points);

// a Shape::draw fellrsa // a Shape::rotate fellrsa

// j (megfelel konstrukort felttelezve)

Az absztrakt osztlyok fontos kpessge, hogy segtsgkkel a megvalsts egyb rszeinek elrhetv ttele nlkl biztosthatunk felletet. Egy opercis rendszer pldul egy absztrakt osztly mg rejtheti eszkzmeghajtinak tulajdonsgait:
class Character_device { public: virtual int open(int opt) = 0; virtual int close(int opt) = 0;

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

413

};

virtual int read(char* p, int n) = 0; virtual int write(const char* p, int n) = 0; virtual int ioctl(int ...) = 0; virtual ~Character_device() { } // virtulis destruktor

Az egyes eszkzmeghajtkat a Character_device-bl szrmaztatott osztlyknt definilhatjuk, s sokfle eszkzmeghajtt kezelhetnk ezen felleten keresztl. A virtulis destruktorok fontossgt a 12.4.2 pont magyarzza el. Az absztrakt osztlyok bevezetsvel immr minden eszkz a keznkben van arra, hogy modulris mdon, ptkvekknt osztlyokat hasznlva egy teljes programot rjunk.

12.4. Osztlyhierarchik tervezse


Vegyk a kvetkez egyszer tervezsi problmt: hogyan lehet egy program szmra lehetv tenni egy egsz rtk bekrst a felhasznli felletrl? Zavarbaejten sokfle mdon. Ahhoz, hogy elszigeteljk programunkat ettl a sokflesgtl s felderthessk a klnbz tervezsi mdokat, kezdjk a munkt ezen egyszer adatbeviteli mvelet modelljnek fellltsval. A tnyleges felhasznli fellet elksztsnek rszleteit ksbbre halasztjuk. Az alaptlet az, hogy lesz egy Ival_box (rtkmez) osztlyunk, amely tudja, hogy milyen rtkeket fogadhat el. A program elkrheti egy Ival_box objektum rtkt s felszlthatja arra is, hogy krje be ezt az rtket a felhasznltl, ha mg nem ll rendelkezsre. Azt is megkrdezheti, hogy az rtk megvltozott-e a legutbbi krs ta. Minthogy ez az alaptlet sokflekppen megvalsthat, abbl kell kiindulnunk, hogy sokfle klnbz Ival_box lesz: csszkk, szveges adatbeviteli mezk, ahov a felhasznl berhatja az rtket, szmtrcsk, hanggal vezrelhet eszkzk. Azt az ltalnos megkzeltst alkalmazzuk, hogy egy virtulis felhasznli felletet bocstunk az alkalmazs rendelkezsre, amely a ltez felhasznli felletek szolgltatsainak egy rszt biztostja. E fellet szmos rendszeren elkszthet, gy kdja hordozhat lesz. Termszetesen vannak ms mdok is arra, hogy egy alkalmazst elvlasszunk a felhasznli fellettl. Azrt vlasztottam ezt, mert ltalnos, mert a kapcsn egy sor eljrst s tervezsi szempontot lehet bemutatni, mert ezeket a mdszereket alkalmazzk a valdi felhasz-

Forrs: http://www.doksi.hu

414

Absztrakcis mdszerek

nli felleteteket kezel rendszerekben, s vgl a leglnyegesebb ok , mert ezek a mdszerek a felhasznli felletetek szk tartomnynl jval szlesebb krben is alkalmazhatk.

12.4.1. Hagyomnyos osztlyhierarchia


Els megoldsunk egy hagyomnyos osztlyhierarchia; ilyennel a Simula, Smalltalk s rgebbi C++-programokban tallkozhatunk. Az Ival_box osztly az sszes Ival_box ltal hasznlatos felletet rja le s egy olyan alaprtelmezett megvalstst ad, melyet az egyes Ival_box-ok sajtjaikkal fellbrlhatnak. Ezenkvl megadjuk az alapmegoldshoz szksges adatokat is:
class Ival_box { protected: int val; int low, high; bool changed; public: Ival_box(int ll, int hh) { changed = false; val = low = ll; high = hh; } virtual int get_value() { changed = false; return val; } virtual void set_value(int i) { changed = true; val = i; } virtual void reset_value(int i) { changed = false; val = i; } virtual void prompt() { } virtual bool was_changed() const { return changed; } // felhasznlk szmra // alkalmazsok szmra

};

A fggvnyek alaprtelmezett vltozatai meglehetsen vzlatosak, pongyolk; cljuk leginkbb az, hogy illusztrljk a megkzeltst. (Egy valdi osztly pldul rtkellenrzst is vgezne.) Az ival osztlyokat egy programoz gy hasznlhatn fel:
void interact(Ival_box* pb) { pb->prompt(); // jelzs a felhasznlnak // ... int i = pb->get_value(); if (pb->was_changed()) { // j rtk; valamit csinlunk vele }

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

415

else { } // ...

// a rgi rtk j volt; ezt is felhasznljuk valahogy

void some_fct() { Ival_box* p1 = new Ival_slider(0,5); interact(p1); Ival_box* p2 = new Ival_dial(1,12); interact(p2);

// az Ival_slider az Ival_box osztlybl szrmazik

A programkd legnagyobb rsze az interact() fggvny stlusban rdna, s egyszer Ival_box-okat, illetve azokra hivatkoz mutatkat hasznlna. gy a programnak nem kellene tudnia az esetleg nagy szm klnbz Ival_box-vltozatokrl, csak annak a viszonylag kis szm fggvnynek kellene ismernie azokat, amelyek ilyen objektumokat ltrehoznak. Ez a felhasznlkat elszigeteli a szrmaztatott osztlyok esetleges mdostsaitl. A kd legnagyobb rsznek mg arrl sem kell tudnia, hogy egyltaln klnbz Ival_box-ok lteznek. Hogy egyszerstsem a trgyalst, eltekintek attl a krdstl, hogyan vr a program bemenetre. Lehetsges megolds, hogy a program a get_value() fggvnyben tnylegesen vr a felhasznli bemenetre, megoldhat gy is, hogy az Ival_box-ot egy esemnyhez kapcsoljuk s egy visszahvs (callback) segtsgvel vlaszolunk, esetleg a programmal kln vgrehajtsi szlat indttatunk el az Ival_box szmra, majd a szl llapott krdezzk le. Az ilyen dntsek alapvet fontossgak a felhasznli felletet kezel rendszerek tervezsekor, de ha itt a valsgot akr csak megkzelt rszletessggel trgyalnnk ezeket, elvonnnk a figyelmet a programozsi eljrsok s nyelvi eszkzk trgyalstl. Az itt bemutatott tervezsi mdszerek s az azokat tmogat nyelvi eszkzk nem ktdnek adott felhasznli fellethez; jval szlesebb krben is alkalmazhatk. A klnbz Ival_box-okat az Ival_box-bl szrmaztatott osztlyokknt hatrozhatjuk meg:
class Ival_slider : public Ival_box { // a csszka kinzett, viselkedst meghatroz grafikai elemek public: Ival_slider(int, int); int get_value(); void prompt();

};

Az Ival_box adattagjait vdettknt (protected) vezettk be, hogy a szrmaztatott osztlyok-

Forrs: http://www.doksi.hu

416

Absztrakcis mdszerek

bl elrhetek legyenek. gy aztn az Ival_slider::get_value() fggvny elhelyezheti az rtket az Ival_box::val adattagban. A vdett tagok elrhetk az osztly s a szrmaztatott osztlyok fggvnyei szmra is, de az ltalnos felhasznl szmra nem (15.3). Az Ival_box-bl az Ival_slider mellett ms vltozatokat is szrmaztathatunk. Ezek kztt ott lehet az Ival_dial, amelynl egy gomb forgatsval adhatunk meg egy rtket, a Flashing_ival_slider, amely felvillan, ha a prompt() fggvnnyel erre krjk, s a Popup_ival_slider, amely a prompt() hatsra valamilyen feltn helyen jelenik meg, a felhasznltl szinte kikvetelve egy rtk megadst. De vajon honnan vegyk a grafikus elemeket? A legtbb felhasznli felletet kezel rendszer biztost egy osztlyt, amely lerja a kpernyn lev objektumok alapvet tulajdonsgait. Ha pldul a Big Bucks Inc. (Sok Pnz Rt.) rendszert hasznljuk, akkor az Ival_slider, az Ival_dial stb. osztlyok mindegyike egy-egy fajta BBwindow (Big Bucks window) kell, hogy legyen. Ezt a legegyszerbben gy rhetjk el, ha Ival_box-unkat gy rjuk t, hogy a BBwindow-bl szrmaztatott osztly legyen. gy aztn az sszes osztlyunk a BBwindow-bl szrmaztatott lesz, teht elhelyezhet lesz a kpernyn, megjelense igazodik majd a rendszer tbbi grafikus elemnek megjelenshez, tmretezhet, thelyezhet lesz stb., a BBwindow rendszer szablyainak megfelelen. Osztlyhierarchink teht gy fog kinzni:
class Ival_box : public BBwindow { /* ... */ }; // jrarva a BBwindow hasznlatra class Ival_slider : public Ival_box { /* ... */ }; class Ival_dial : public Ival_box { /* ... */ }; class Flashing_ival_slider : public Ival_slider { /* ... */ }; class Popup_ival_slider : public Ival_slider { /* ... */ };

brval: BBwindow Ival_box

Ival_slider

Ival_dial

Popup_ival_slider

Flashing_ival_slider

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

417

12.4.1.1. Brlat Ez gy sok tekintetben jl fog mkdni s az ilyesfajta osztlyfelpts szmos problmra j megolds. m van nhny htultje, melyek miatt ms tervezsi lehetsgek utn fogunk nzni. A BBwindow osztlyt utlag tettk az Ival_box bzisosztlyv, ami nem egszen helyes. A BBwindow osztly nem alapvet rsze az Ival_box-ra ptett rendszernek, meglte csupn rszletkrds. Az, hogy az Ival_box a BBwindow osztly leszrmazottja, ezt a rszletkrdst elsrend tervezsi dntss emeli. Ez abban az esetben helyes, ha cgnk kulcsfontossg zleti dntse, hogy a Big Bucks Inc. ltal biztostott krnyezetet hasznljuk. De mi trtnik, ha Ival_box-unkat olyan rendszerekre is t szeretnnk ltetni, melyek az Imperial Bananas, a Liberated Software vagy a Compiler Whizzles-tl szrmaznak? Ekkor programunkbl ngy vltozatot kellene ksztennk:
class Ival_box : public BBwindow { /* ... */ }; class Ival_box : public CWwindow { /* ... */ }; class Ival_box : public IBwindow { /* ... */ }; class Ival_box : public LSwindow { /* ... */ }; // BB vltozat // CW vltozat // IB vltozat // LS vltozat

Ha ennyi vltozatunk van, mdostsuk, vltozatkvetsk rmlomm vlhat. Egy msik problma, hogy az Ival_box-ban deklarlt adatok minden szrmaztatott osztly rendelkezsre llnak. Ezek az adatok megint csak egy apr rszletet jelentenek, mgis bekerltek az Ival_box felletbe. Ez gyakorlati szempontbl azt is jelenti, hogy nem biztostott, hogy mindig a megfelel adatot kapjuk. Az Ival_slider esetben pldul nem szksges az adat kln trolsa, minthogy ez a csszka llsbl meghatrozhat, valahnyszor vgrehajtjk a get_value()-t. ltalban is problematikus kt rokon, de eltr adathalmaz trolsa. Elbb-utbb valaki elri, hogy ne legyenek tbb sszhangban. A tapasztalat is azt mutatja, hogy kezd programozk szksgtelen s nehezebb mdosthatsgot eredmnyez mdon szeretnek a vdett (protected) adattagokkal gyeskedni. Jobb, ha az adattagok privtok, mert gy a szrmaztatott osztlyok ri nem zavarhatjk ssze azokat. Mg jobb, ha az adatok a szrmaztatott osztlyokban vannak, mert akkor pontosan meg tudnak felelni a kvetelmnyeknek s nem keserthetik meg az egymssal nem rokon szrmaztatott osztlyok lett. A vdett fellet szinte mindig csak fggvnyeket, tpusokat s konstansokat tartalmazzon. Ha az Ival_box a BBwindow-bl szrmazik, ez azzal az elnnyel jr, hogy az Ival_box felhasznli a BBwindow minden szolgltatst ignybe vehetik, ami sajnos azt is jelenti, hogy a BBwindow osztly vltozsakor az Ival_box felhasznlinak jra kell fordtaniuk, esetleg jra kell rniuk a kdjukat. A legtbb C++-vltozat gy mkdik, hogy ha egy bzisosztly mrete megvltozik, akkor az sszes szrmaztatott osztlyt jra kell fordtani.

Forrs: http://www.doksi.hu

418

Absztrakcis mdszerek

Vgl lehetsges, hogy programunknak olyan vegyes krnyezetben kell futnia, ahol klnbz felhasznli felletek ablakai lteznek egyidejleg. Vagy azrt, mert valahogy ezek egy kpernyn tudnak osztozni, vagy mert programunknak klnbz rendszerek felhasznlival kell kapcsolatot tartania. Ehhez pedig nem elg rugalmas megolds, ha a felhasznli felletet az egyetlen Ival_box felletnk bzisosztlyaknt bedrtozzuk.

12.4.2. Absztrakt osztlyok


Nos, kezdjk jra a tervezst, hogy megoldjuk a hagyomnyos felpts brlatban felvetett problmkat: 1. A felhasznli fellet valban olyan rszletkrds legyen, amelyrl nem kell tudomst vennik azon felhasznlknak, akiket nem rdekel. 2. Az Ival_box osztly ne tartalmazzon adatokat. 3. Ha megvltozik a felhasznli felletet kezel rendszer, ne legyen szksges az Ival_box csaldot felhasznl kd jrafordtsa. 4. Klnbz felhasznli felletekhez tartoz Ival_box-ok tudjanak egyszerre ltezni a programban. Tbbfle megolds knlkozik erre; most egy olyat mutatok be, amely tisztn megvalsthat a C++ nyelvvel. Elszr is, az Ival_box osztlyt puszta felletknt (pure interface) hatrozzuk meg:
class Ival_box { public: virtual int get_value() = 0; virtual void set_value(int i) = 0; virtual void reset_value(int i) = 0; virtual void prompt() = 0; virtual bool was_changed() const = 0; virtual ~Ival_box() { } };

Ez sokkal vilgosabb, mint az Ival_box osztly eredeti deklarcija volt. Elhagytuk az adattagokat s a tagfggvnyek egyszerstett kifejtst is. Elmaradt a konstruktor is, mivel nincs kezdrtkre vr adat. Ehelyett egy virtulis destruktorunk van, amely biztostja az rkl osztlyok adatainak helyes eltakartst.

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

419

Az Ival_slider defincija gy alakulhat:


class Ival_slider : public Ival_box, protected BBwindow { public: Ival_slider(int,int); ~Ival_slider(); int get_value(); void set_value(int i); // ... protected: // a BBwindow virtulis fggvnyeit fellr fggvnyek // pl. BBwindow::draw(), BBwindow::mouse1hit() private: // a csszka adatai };

Mivel az Ival_slider osztly az absztrakt Ival_box osztlybl szrmazik, meg kell valstania annak tisztn virtulis (pure virtual) fggvnyeit. A BBwindow osztlybl is szrmazik, ezrt onnan valk az eszkzei, melyekkel ezt megteheti. Az Ival_box adja a szrmaztatott osztly fellett, ezrt nyilvnos (public) mdon szrmazik onnan. Mivel a BBwindow osztlybl val szrmazsa mindssze segtsget nyjt a megvalstshoz, onnan vdett (protected) mdon szrmazik (15.3.2). Ebbl kvetkezik, hogy az Ival_slider-t felhasznl programoz nem hasznlhatja kzvetlenl a BBwindow ltal nyjtott eszkzket. Az Ival_slider fellete az Ival_box-tl rklt rszbl ll, illetve abbl, amit maga az Ival_slider kifejezetten deklarl. Azrt hasznlunk vdett szrmaztatst a szigorbb megktst jelent (s ltalban biztonsgosabb) privt helyett, hogy az Ival_slider-bl szrmaztatott osztlyok szmra a BBwindow-t elrhetv tegyk. A tbb osztlybl val kzvetlen rkldst ltalban tbbszrs rkldsnek (multiple inheritance) hvjk (15.2). Vegyk szre, hogy Ival_slider-nek mind az Ival_box, mind a BBwindow fggvnyei kzl fell kell rnia nhnyat, ezrt kzvetve vagy kzvetlenl mindkt osztlybl szrmaznia kell. Mint a 12.4.1.1 pontban lttuk, lehetsges ugyan az Ival_slider kzvetett szrmaztatsa a BBwindow-bl (azltal, hogy az Ival_box a BBwindow-bl szrmazik), de ez nemkvnatos mellkhatsokkal jr. Hasonlan, az az t, hogy a BBwindow megvalstsi osztly tagja legyen az Ival_box-nak, nem jrhat, mert egy osztly nem rhatja fell tagjainak virtulis fggvnyeit (24.3.4). Az ablaknak az Ival_box osztly egy BBwindow* tpus tagjaknt val brzolsa teljesen eltr szerkezethez vezet, melynek megvannak a maga elnyei s htrnyai (12.7[14], 25.7). rdekes mdon az Ival_slider ilyen mdon val deklarlsa esetn ugyanolyan kdot rhatunk, mint azeltt. Csak azrt vltoztattunk, hogy a szerkezet logikusabb mdon tkrzze a megvalstst.

Forrs: http://www.doksi.hu

420

Absztrakcis mdszerek

Szmos osztlynak szksge van valamilyen rendraksra, mieltt egy objektuma megsemmisl. Mivel az absztrakt Ival_box osztly nem tudhatja, hogy egy szrmaztatott osztlynak nincs-e szksge erre, fel kell tteleznie, hogy igenis szksg van r. A rendrakst gy biztostjuk, hogy a bzisosztlyban definiljuk az Ival_box::~Ival_box() virtulis destruktort s a szrmaztatott osztlyokban megfelel mdon fellrjuk azt:
void f(Ival_box* p) { // ... delete p; }

A delete opertor megsemmisti az objektumot, amelyre p mutat. Nem tudhatjuk, hogy pontosan milyen osztly objektumrl van sz, de mivel az Ival_box-nak virtulis destruktora van, a megfelel destruktor fog meghvdni, (ha az adott osztlynak van ilyen). Az Ival_box hierarchit most gy rhatjuk le:
class Ival_box { /* ... */ }; class Ival_slider : public Ival_box, protected BBwindow { /* ... */ }; class Ival_dial : public Ival_box, protected BBwindow { /* ... */ }; class Flashing_ival_slider : public Ival_slider { /* ... */ }; class Popup_ival_slider : public Ival_slider { /* ... */ };

Egyszer rvidtsekkel pedig gy brzolhatjuk: BBwindow Ival_box BBwindow

Ival_slider

Ival_dial

Popup_slider

Flashing_slider

A szaggatott nyilak a vdett (protected) md rkldst jellik. Az ltalnos felhasznlk szmra ezek csak rszletkrdsek.

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

421

12.4.3. Egyb megvalstsok


Ez a szerkezet tisztbb s knnyebben mdosthat, mint a hagyomnyos, de nem kevsb hatkony. A vltozatkvetsi problmt azonban nem oldja meg:
class Ival_box { /* ... */ }; // kzs class Ival_slider : public Ival_box, protected BBwindow { /* ... */ }; // BB class Ival_slider : public Ival_box, protected CWwindow { /* ... */ }; // CW // ...

Radsul a BBwindow-hoz s a CWwindow-hoz rt Ival_slider-ek nem ltezhetnek egytt, mg akkor sem, ha egybknt maguk a BBwindow s CWwindow felhasznli felletek igen. A nyilvnval megolds az, hogy klnbz nev Ival_slider osztlyokat hozunk ltre:
class Ival_box { /* ... */ }; class BB_ival_slider : public Ival_box, protected BBwindow { /* ... */ }; class CW_ival_slider : public Ival_box, protected CWwindow { /* ... */ }; // ...

brval:

BBwindow

Ival_box

CWwindow

BB_ival_slider

CW_ival_slider

Hogy programunk Ival_box osztlyait jobban elszigeteljk a megvalsts egyb rszleteitl, szrmaztathatunk egy absztrakt Ival_slider osztlyt az Ival_box-bl, majd ebbl rkltethetjk az egyes rendszerfgg Ival_slider-eket:
class Ival_box { /* ... */ }; class Ival_slider : public Ival_box { /* ... */ }; class BB_ival_slider : public Ival_slider, protected BBwindow { /* ... */ }; class CW_ival_slider : public Ival_slider, protected CWwindow { /* ... */ }; // ...

Forrs: http://www.doksi.hu

422

Absztrakcis mdszerek

brval: Ival_box

BBwindow

Ival_slider

CWwindow

BB_ival_slider

CW_ival_slider

ltalban mg ennl is jobban jrunk, ha a hierarchiban egyedibb osztlyokat hasznlunk. Ha pldul a Big Bucks Inc. rendszerben van egy csszka (slider) osztly, akkor a mi Ival_slider-nket kzvetlenl a BBslider-bl szrmaztathatjuk:
class BB_ival_slider : public Ival_slider, protected BBslider { /* ... */ }; class CW_ival_slider : public Ival_slider, protected CWslider { /* ... */ };

brval: BBwindow Ival_box CWwindow

BBslider

Ival_slider

CWslider

BB_ival_slider

CW_ival_slider

Ez a javts jelents lehet abban a (srn elfordul) esetben, ha a mi fogalmaink nem esnek tvol a megvalsts cljbl felhasznlt rendszer fogalmaitl. Ekkor a programozs tulajdonkppen a rokon fogalmak kztti lekpezsre egyszersdik, s a BBwindow-hoz hasonl ltalnos bzisosztlyokbl val rklds ritkn fordul el. A teljes hierarchia egyrszt az eredeti, alkalmazskzpont rendszer szrmaztatott osztlyokknt megvalstott felleteinek viszonyrendszerbl fog llni:
class Ival_box { /* ... */ }; class Ival_slider : public Ival_box { /* ... */ }; class Ival_dial : public Ival_box { /* ... */ }; class Flashing_ival_slider : public Ival_slider { /* ... */ }; class Popup_ival_slider : public Ival_slider { /* ... */ };

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

423

Illetve a hierarchit szintn az rklds segtsgvel tbbfle grafikus felhasznli felletre lekpez szrmaztatott osztlyokbl:
class BB_ival_slider : public Ival_slider, protected BBslider { /* ... */ }; class BB_flashing_ival_slider : public Flashing_ival_slider, protected BBwindow_with_bells_and_whistles { /* ... */ }; class BB_popup_ival_slider : public Popup_ival_slider, protected BBslider { /* ... */ }; class CW_ival_slider : public Ival_slider, protected CWslider { /* ... */ }; // ...

A kapott felptmnyt egyszer rvidtsek segtsgvel gy brzolhatjuk: Ival_box Ival_slider Ival_dial

ipopup

iflash

BBslider

BBslider

CWsl

CWsl

BBb&w

CWsl

BBislider

BBipop

CWipop

CWifl

BBifl

CWislider

Az eredeti Ival_box hierarchia vltozatlan marad, csak a konkrt megvalstst vgz osztlyok veszik krl.

12.4.3.1. Brlat Az absztrakt osztlyokat hasznl osztlyszerkezet rugalmas s majdnem ugyanolyan egyszeren kezelhet, mint a konkrt felhasznli felletet bzisosztlyknt szerepeltet. Az utbbiban a fa gykere a megfelel ablakosztly, az elbbiben viszont vltozatlanul az alkalmazs osztlyhierarchija marad a tnyleges megvalstst vgz osztlyok alapja. A program szempontjbl ezek a szerkezetek egyenrtkek abban az rtelemben, hogy majdnem az egsz kd vltoztats nlkl s ugyangy mkdik mindkt esetben, s mindkettnl az alkalmazott felhasznli fellettl fgg elemekre val tekintet nlkl vizsglhatjuk az Ival_box csald osztlyait. A 12.4.1-beli interact() fggvnyt pldul nem kell jrarnunk, ha az egyik szerkezetrl a msikra vltunk.

Forrs: http://www.doksi.hu

424

Absztrakcis mdszerek

Mindkt esetben jra kell rnunk az egyes Ival_box osztlyokat, ha a felhasznli fellet nyilvnos fellete megvltozik, de az absztrakt osztlyokat hasznl szerkezet esetben szinte az egsz kd vdett a megvalsts vltozstl s egy ilyen vltozs utn nem kell jrafordtani. Ez klnsen akkor fontos, ha a megvalstst vgz elemek ksztje egy j, majdnem kompatibilis vltozatot bocst ki. Radsul az absztrakt osztlyos megoldst vlasztk a klasszikus hierarchia hveinl kevsb vannak kitve az egyedi, mshol nem hasznlhat megvalsts csapdjba val bezrds veszlynek. Az elvont Ival_box osztlyokra ptett programot vlasztva nem hasznlhatjuk vletlenl a megvalst osztlyok nyjtotta lehetsgeket, mert csak az Ival_box hierarchiban kifejezetten megadott lehetsgek rhetk el, semmi sem rkldik automatikusan egy rendszerfgg bzisosztlytl.

12.4.4. Az objektumok ltrehozsnak adott helyre korltozsa


A program legnagyobb rsze megrhat az Ival_box fellet felhasznlsval. Ha a szrmaztatott felletek tovbbfejldnek s tbb szolgltatst nyjtanak, mint a sima Ival_box, akkor nagyrszt hasznlhatjuk az Ival_box, Ival_slider stb. felleteket. Az objektumokat azonban az adott rendszerre jellemz nevek (pldul CW_ival_dial s BB_flashing_ival_slider) felhasznlsval kell ltrehozni. J lenne, ha az ilyen rendszerfgg nevek minl kevesebb helyen fordulnnak el, mivel az objektumok ltrehozsa nehezen kthet helyhez, hacsak nem szisztematikusan jrunk el. Szoks szerint az indirekci (kzvetett hivatkozs) bevezetse a megolds. Ezt tbbflekppen is megtehetjk. Egyszer megolds lehet pldul egy olyan osztly bevezetse, amely az objektumokat ltrehoz mveletekrt felels:
class Ival_maker { public: virtual Ival_dial* dial(int, int) =0; // trcsa (dial) ksztse virtual Popup_ival_slider* popup_slider(int, int) =0; // elugr csszka (popup slider) // ksztse // ... };

Az Ival_maker osztly az Ival_box hierarchia minden olyan fellete szmra rendelkezik az adott tpus objektumot ltrehoz fggvnnyel, mely felletrl a felhasznlk tudhatnak. Az ilyen osztlyokat gyrnak (factory) hvjk, fggvnyeiket pedig nmikpp flrevezet mdon virtulis konstruktoroknak (15.6.2).

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

425

Az egyes klnbz felhasznli felleteket kezel rendszereket most az Ival_maker osztlybl szrmaztatott osztlyokknt brzoljuk:
class BB_maker : public Ival_maker { public: Ival_dial* dial(int, int); Popup_ival_slider* popup_slider(int, int); // ... }; class LS_maker : public Ival_maker { public: Ival_dial* dial(int, int); Popup_ival_slider* popup_slider(int, int); // ... }; // BB-vltozatok ksztse

// LS-vltozatok ksztse

Minden fggvny a kvnt fellet s megvalstsi tpus objektumot hozza ltre:


Ival_dial* BB_maker::dial(int a, int b) { return new BB_ival_dial(a,b); } Ival_dial* LS_maker::dial(int a, int b) { return new LS_ival_dial(a,b); }

Ha adott egy mutat egy Ival_maker objektumra, akkor a programoz ennek segtsgvel gy hozhat ltre objektumokat, hogy nem kell tudnia, pontosan milyen rendszer felhasznli fellet van hasznlatban:
void user(Ival_maker* pim) { Ival_box* pb = pim->dial(0,99); // ... } BB_maker BB_impl; LS_maker LS_impl; void driver() { user(&BB_impl); user(&LS_impl); }

// megfelel trcsa ltrehozsa // BB-felhasznlknak // LS-felhasznlknak

// BB hasznlata // LS hasznlata

Forrs: http://www.doksi.hu

426

Absztrakcis mdszerek

12.5. Osztlyhierarchik s absztrakt osztlyok


Az absztrakt osztlyok felletek (interface). Az osztlyhierarchia annak eszkze, hogy fokozatosan ptsnk fel osztlyokat. Termszetesen minden osztly ad egy felletet a programoz szmra, nmely absztrakt osztly pedig jelents szolgltatsokat knl, amelyekre pthetnk, de fellet s ptk szerepk alapveten az absztrakt osztlyoknak s az osztlyhierarchiknak van. Klasszikus hierarchinak azt a felptst nevezzk, amelynek egyes osztlyai hasznos szolgltatsokat knlnak a felhasznlknak, illetve egyben a fejlettebb vagy egyedi feladatot vgz osztlyok szmra ptkl szolglnak. Az ilyen felpts idelisan tmogatja a lpsenknti finomtssal val fejlesztst, illetve az j osztlyok ltrehozst, amennyiben ezek megfelelen illeszkednek a hierarchiba. A klasszikus felpts a tnyleges megvalstst sokszor sszekapcsolja a felhasznlknak nyjtott fellettel. Ez gyben az absztrakt osztlyok segthetnek. Az absztrakt osztlyok segtsgvel felptett rendszer tisztbb s hatkonyabb mdot ad a fogalmak kifejezsre, anlkl hogy a megvalsts rszleteivel keveredne vagy jelentsen nveln a program futsi idejt. A virtulis fggvnyek meghvsa egyszer s fggetlen attl, hogy mifle elvonatkoztatsi rteg hatrt lpi t. Egy absztrakt osztly virtulis fggvnyt meghvni semmivel sem kerl tbbe, mint brmely ms virtulis fggvnyt. A fentiekbl add vgkvetkeztets az, hogy egy rendszert a felhasznlk fel mindig absztrakt osztlyok hierarchijaknt mutassunk, de klasszikus hierarchiaknt ptsnk fel.

12.6. Tancsok
[1] Kerljk a tpusmezk alkalmazst. 12.2.5. [2] Az objektumok felszeleteldst (slicing) elkerlend hasznljunk mutatkat s referencikat. 12.2.3. [3] Hasznljunk absztrakt osztlyokat, hogy a vilgos felletek elksztsre sszpontosthassunk. 12.3. [4] Hasznljunk absztrakt osztlyokat, hogy minl kisebb felleteket hasznlhassunk. 12.4.2. [5] Hasznljunk absztrakt osztlyokat, hogy a felleteket elvlasszuk a megvalstsi rszletektl. 12.4.2.

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

427

[6] Hasznljunk virtulis fggvnyeket, hogy ksbb j megvalstst kszthessnk a meglev felhasznli kd befolysolsa nlkl. 12.4.1. [7] Hasznljunk absztrakt osztlyokat, hogy minl kevesebbszer kelljen a felhasznli kdot jrafordtani. 12.4.2. [8] Hasznljunk absztrakt osztlyokat, hogy a program tbbfle rendszeren is mkdjn. 12.4.3. [9] Ha egy osztlynak van virtulis fggvnye, akkor legyen virtulis destruktora is. 12.4.2. [10] Az absztrakt osztlyoknak ltalban nincs szksgk konstruktorra. 12.4.2. [11] Az nll fogalmakat kln brzoljuk. 12.4.1.1.

12.7. Gyakorlatok
1. (*1) Ha adott a kvetkez:
class Base { public: virtual void iam() { cout << "Bzisosztly\n"; } };

Szrmaztassunk kt osztlyt a Base-bl, mindegyiknek legyen egy iam() fggvnye, amely kirja az osztly nevt. Hozzunk ltre egy-egy ilyen osztly objektumot s hvjuk meg rjuk az iam() fggvnyt. Rendeljnk a szrmaztatott osztlyok objektumaira hivatkoz mutatkat Base* tpus mutatkhoz s hvjuk meg ezeken keresztl az iam() fggvnyt. 2. (*3.5) Ksztsnk egy egyszer grafikus rendszert a rendelkezsnk ll grafikus felhasznli fellet felett. (Ha nincs ilyen vagy nincs tapasztalatunk ilyesmivel, akkor kszthetnk egy egyszer, ASCII karakterekbl felptett megvalstst, ahol egy pont egy karakterpozcinak felel meg, s az rs a megfelel karakter, mondjuk a * megfelel pozcira val helyezst jelenti.) Ebben a feladatban s a tovbbiakban a kvetkez osztlyokat hasznljuk: Window (Ablak), Point (Pont), Line (Vonal), Dot (Kpernypont), Rectangle (Tglalap), Circle (Kr), Shape (Alakzat, Idom), Square (Ngyzet) s Triangle (Hromszg). A Window(n,m) hozzon ltre egy n-szer m mret terletet a kpernyn. A kperny pontjait az (x,y) derkszg (descartes-i) koordintk segtsgvel cmezzk meg. A Window osztlyba tartoz w aktulis helye w.current()

Forrs: http://www.doksi.hu

428

Absztrakcis mdszerek

3.

4.

5.

6.

7. 8.

kezdetben Point(0,0). A pozcit a w.current(p) hvssal llthatjuk be, ahol p egy Point. A Point objektumokat egy koordinta-pr adja meg: Point(x,y); a Line objektumokat egy Point pr Line(w.current(),p2) ; a Shape osztly a Dot, a Line, a Rectangle, a Circle stb. kzs fellete. Egy Point nem Shape is egyben. A Dot(p)-knt ltrehozott Dot egy Point p-t jelent a kpernyn. A Shape-ek nem lthatk, amg a draw() fggvnyt meg nem hvjuk; pldul: w.draw(Circle(w.current(),10)). Minden Shape-nek 9 rintkezsi pontja van: e (east kelet), w (west nyugat), n (north szak), s (south dl), ne (szakkelet), nw (szaknyugat), se (dlkelet), sw (dlnyugat) s c (center kzppont). A Line(x.c(),y.nw()) pldul egy vonalat hz az x kzeptl az y bal fels sarkhoz. Ha egy Shape-re alkalmaztuk a draw() fggvnyt, az aktulis pozci a Shape se()-je lesz. Egy Rectangle-t a bal als s a jobb fels cscsval adunk meg; Rectangle(w.current(),Point(10,10)). Egyszer tesztknt jelentsnk meg egy gyermekrajzot, amely egy hzat brzol tetvel, kt ablakkal, s egy ajtval. (*2) Egy Shape fontos rszei szakaszokknt jelennek meg a kpernyn. Adjunk meg olyan mveleteket, amelyek segtsgvel meg tudjuk vltoztatni ezen szakaszok kinzett. Az s.thickness(n) a 0,1,2,3 rtkek valamelyikre lltsa be a vonalszlessget, ahol a 2 az alaprtelmezett rtk s a 0 rtk azt jelenti, hogy a vonal lthatatlan. A vonal lehessen tmr, szaggatott vagy pontokbl ll is. Ezt a Shape::outline() fggvny lltsa be. (*2.5) rjuk meg a Line::arrowhead() fggvnyt, amely egy vonal vgre egy nyilat rajzol. Minthogy egy vonalnak kt vge van s a nyl a vonalhoz kpest ktfle irnyba mutathat, gy az arrowhead() fggvny paramtere vagy paramterei ki kell, hogy tudjk fejezni ezt a ngyfle lehetsget. (*3.5) Gondoskodjunk arrl, hogy azon pontok s vonalszakaszok, amelyek kvl esnek egy Window-n, ne jelenjenek meg. Ezt a jelensget gyakran hvjk levgsnak (clipping). E clbl gyakorlatknt ne hagyatkozzunk a felhasznlt grafikus felhasznli felletre. (*2.5) Egsztsk ki grafikai rendszernket a Text tpussal. A Text legyen egy tglalap alak Shape, amely karaktereket tud megjelenteni. Alaprtelmezs szerint egy karakter a koordinta-tengelyen minden irnyban egy egysgnyi helyet foglaljon el. (*2) Hatrozzunk meg egy fggvnyt, amely megtallja kt Shape egymshoz legkzelebbi pontjait s sszekti azokat. (*3) Vezessk be grafikai rendszernkbe a szn fogalmt. Hromfle dolog lehet sznes: a httr, egy zrt Shape belseje s egy Shape hatra.

Forrs: http://www.doksi.hu

12. Szrmaztatott osztlyok

429

9. (*2) Vegyk az albbi osztlyt:


class Char_vec { int sz; char element[1]; public: static Char_vec* new_char_vec(int s); char& operator[ ](int i) { return element[i]; } // ... };

Definiljuk a new_char_vec()-t, hogy egybefgg memriaterletet foglalhassunk le egy Char_vec objektum szmra, gy elemeit az element() fggvnnyel indexelhetjk. Milyen krlmnyek kztt okoz ez a trkk komoly problmkat? 10. (*2.5) Ha adottak a Shape osztlybl szrmaz Circle, Square s Triangle osztlyok, hatrozzuk meg az intersect() fggvnyt, amely kt Shape* paramtert vesz s a megfelel fggvnyek meghvsval megllaptja, hogy a kt Shape tfed-e, metszi-e egymst. Ehhez szksges lesz az osztlyok megfelel (virtulis) fggvnyekkel val bvtse. Ne rjuk meg az tfeds tnyleges megllaptsra szolgl kdot, csak arra gyeljnk, hogy a megfelel fggvnyeket hvjuk meg. Ezt az eljrst angolul ltalban double dispatch vagy multi-method nven emlegetik. 11. (*5) Tervezznk s rjunk meg egy esemnyvezrelt szimulcikat vgz knyvtrat. Segtsg: nzzk meg a <task.h> fejllomnyt. Ez egy rgi program, az olvas jobbat tud rni. Legyen egy task nev osztly. A task osztly objektumok legyenek kpesek llapotuk mentsre s visszalltsra (mondjuk a task::save() s a task::restore() fggvnyekkel), hogy kiegszt eljrsknt (co-routine) mkdhessenek. Az egyes elvgzend feladatokat a task osztlybl rkl osztlyok objektumaiknt adhassuk meg. A task-ok ltal vgrehajtand programokat virtulis fggvnyekkel hatrozzuk meg. Egy j task szmra legyen lehetsges paramtereket megadni konstruktora(i)nak paramtereknt. Legyen egy temez, amely megvalstja a virtulis id fogalmt. Legyen egy task::delay(long) fggvny, amely fogyasztja ezt a virtulis idt. Az, hogy ez az temez a task rsze vagy nll osztly lesz-e, a f tervezsi dntsek egyike. A task-oknak kapcsolatot kell tartaniuk egymssal. Erre a clra tervezznk egy queue osztlyt. Egy task-nak legyen lehetsge tbb forrs fell rkez bemenetre vrakozni. Kezeljk a futsi idej hibkat azonos mdon. Hogyan lehetne egy ilyen knyvtrat hasznl programban hibakeresst vgezni? 12. (*2) Hatrozzuk meg egy kalandjtk szmra a Warrior (harcos), Monster (szrny) s Object (trgy; olyasmi, amit fel lehet kapni, el lehet dobni, hasznlni lehet stb.) osztlyok fellett.

Forrs: http://www.doksi.hu

430

Absztrakcis mdszerek

13. (*1.5) Mirt van a 12.7[2]-ben Point s Dot osztly is? Milyen krlmnyek kztt lenne j tlet a Shape osztlyokat a kulcsosztlyok, pldul a Line konkrt vltozataival bvteni? 14. (*3) Vzoljuk az Ival_box plda (12.4) egy eltr megvalstsi mdjt: minden, a program ltal elrhet osztly egyszeren egy mutatt tartalmazzon a megvalst osztlyra. Ilyen mdon minden felletosztly egy megvalst osztly lerja (handle) lesz, s kt hierarchival fogunk rendelkezni: egy fellet- s egy megvalstsi hierarchival. rjunk olyan rszkdokat, amelyek elg rszletesek ahhoz, hogy bemutassk a tpuskonverzikbl add lehetsges problmkat. Gondoljuk t a kvetkez szempontokat: a hasznlat knnysge; a programozs knnysge; mennyire knny a megvalst osztlyok s a felletek jrahasznostsa, ha j fogalmat vezetnk be a hierarchiba; mennyire knny vltoztatsokat eszkzlni a felletekben vagy a megvalstsban; s szksg van-e jrafordtsra, ha vltozott a rendszerfgg elemek megvalstsa.

Forrs: http://www.doksi.hu

13
Sablonok
Az Olvas idzetnek helye. (B. Stroustrup) Sablonok Egy karakterlnc sablon Pldnyosts Sablonparamterek Tpusellenrzs Fggvnysablonok Sablonparamterek levezetse Sablonparamterek meghatrozsa Fggvnysablonok tlterhelse Eljrsmd megadsa sablonparamterekkel Alaprtelmezett sablonparamterek Specializci rklds s sablonok Tag sablonok Konverzik A forrskd szerkezete Tancsok Gyakorlatok

13.1. Bevezets
Fggetlen fogalmakat fggetlenl brzoljunk s csak szksg esetn hasznljunk egytt. Ha megsrtjk ezt az elvet, akkor vagy nem rokon fogalmakat kapcsolunk ssze vagy szksgtelen fggseket teremtnk, gy kevsb rugalmas rszekbl vagy sszetevkbl kell majd a programokat sszelltanunk. A sablonok (template) egyszer mdot adnak arra, hogy ltalnos fogalmak szles krt brzoljuk s egyszer mdon hasznljuk egytt. Az gy ltrejv osztlyok futsi id s trigny tekintetben felveszik a versenyt a kzzel rott s egyedibb feladatot vgz kddal.

Forrs: http://www.doksi.hu

432

Absztrakcis mdszerek

A sablonok kzvetlenl tmogatjk az ltalnostott (generikus) programozst (2.7.), azaz a tpusoknak paramterknt val hasznlatt. A C++ sablonjai lehetv teszik, hogy egy osztly vagy fggvny definilsakor egy tpust paramterknt adjunk meg. A sablon a felhasznlt tpusnak csak azon tulajdonsgaitl fgg, amelyeket tnylegesen ki is hasznl. A sablon ltal felhasznlt paramtertpusok nem kell, hogy rokonsgban lljanak egymssal, gy nem szksges az sem, hogy egyazon rkldsi hierarchia tagjai legyenek. Ebben a fejezetben a sablonokat gy mutatjuk be, hogy az elsdleges hangsly a standard knyvtr tervezshez, megvalstshoz s hasznlathoz szksges mdszerekre esik. A standard knyvtr nagyobb mrtk ltalnossgot, rugalmassgot s hatkonysgot kvetel, mint a legtbb program. Kvetkezskppen a trgyaland eljrsok szles krben hasznlhatak s igen sokfle problma megoldshoz biztostanak hatkony segtsget. Lehetv teszik, hogy egyszer felletek mg kifinomult megvalstsokat rejtsnk s csak akkor mutassuk be a bonyolult rszleteket a felhasznlnak, ha valban szksge van rjuk. A sort(v) pldul sokfle trol objektum tartalmazta sokfle tpus elemnek sokfle rendez algoritmushoz adhat felletet. Egy adott v-hez a fordtprogram automatikusan vlasztja ki a legalkalmasabb rendez fggvnyt. A standard knyvtr minden fbb fogalmat egy-egy sablonknt brzol (pldul string, ostream, complex, list s map), de a legfbb mveleteket is, pldul a karakterlncok (string-ek) sszehasonltst, a << kimeneti mveletet, a komplex szmok (complex) sszeadst, egy lista (list) kvetkez elemnek vtelt, vagy a rendezst (sort()). Ezrt aztn e knyvnek az emltett knyvtrral foglalkoz fejezetei (a III. rsz) gazdag forrsai a sablonokra s az azokra pt programozsi mdszerekre vonatkoz pldknak. Kvetkezskppen ez a fejezet a sablonok fogalmt jrja krl s csupn a hasznlatuk alapvet mdjait bemutat kisebb pldkra sszpontost: 13.2 Az osztlysablonok ltrehozsra s hasznlatra szolgl alapvet eljrsok 13.3 Fggvnysablonok, fggvnyek tlterhelse, tpusok levezetse 13.4 ltalnostott algoritmusok eljrsmdjnak megadsa sablonparamterekkel 13.5 Sablon tbbfle megvalstsa klnbz definicikkal 13.6 rklds s sablonok (futsi s fordtsi idej tbbalaksg) 13.7 A forrskd szerkezete A sablon (template) fogalmt a 2.7.1 s a 3.8 pont vezette be. A sablonnevek feloldsra, illetve a sablonok formai kvetelmnyeire vonatkoz rszletes szablyok a C.13 pontban vannak.

Forrs: http://www.doksi.hu

13. Sablonok

433

13.2. Egy egyszer karakterlnc sablon


Vegynk egy karakterlncot. A string (karakterlnc) olyan osztly, amely karaktereket trol s olyan indexelsi, sszefzsi s sszehasonltsi mveleteket nyjt, amelyeket rendesen a karakterlnc fogalmhoz ktnk. Ezeket klnfle karakterkszletek szmra szeretnnk biztostani. Pldul az eljeles s eljel nlkli, knai vagy grg stb. karakterekbl ll lncok szmos sszefggsben hasznosak lehetnek. Ezrt gy szeretnnk a karakterlnc fogalmt brzolni, hogy minl kevsb fggjnk egy adott karakterkszlettl. A karakterlnc definicija arra pt, hogy egy karaktert le lehet msolni, ezen kvl nem sok egybre. Ezrt ha a 11.2-beli char-okbl felpl string osztlyban a karakterek tpust paramterr tesszk, ltalnosabb karakterlnc-osztlyt kapunk:
template<class C> class String { struct Srep; Srep *rep; public: String(); String(const C*); String(const String&); C read(int i) const; // ...

};

A template<class C> eltag azt jelenti, hogy egy sablon deklarcija kvetkezik s abban a C tpusparamtert fogjuk hasznlni. Bevezetse utn a C-t ugyangy hasznlhatjuk, mint brmely ms tpusnevet. A C hatkre a template<class C> eltaggal bevezetett deklarci vgig terjed. Jegyezzk meg, hogy a template<class C> eltag azt jelenti, hogy C egy tpusnv; nem felttlenl kell osztlynvnek lennie. Az osztlysablon neve a <> jelpr kz rott tpusnvvel egytt egy, a sablon ltal meghatrozott osztly nevt adja s ugyangy hasznlhat, mint brmely ms osztlynv:
String<char> cs; String<unsigned char> us; String<wchar_t> ws; class Jchar { // japn karakter }; String<Jchar> js;

Forrs: http://www.doksi.hu

434

Absztrakcis mdszerek

A nvre vonatkoz sajtos formai kvetelmnyektl eltekintve a String<char> pontosan ugyangy mkdik, mintha a 11.12-beli String-defincival definiltuk volna. A String sablonn ttele lehetv teszi, hogy a char-okbl ll karakterlncok szolgltatsait ms tpus karakterekbl ll String-ek szmra is elrhetv tegyk. Pldul ha a standard knyvtrbeli map s String sablonokat hasznljuk, a 11.8 pont szszmll pldja gy rhat t:
int main() // szavak elfordulsnak megszmllsa a bemeneten { String<char> buf; map<String<char>,int> m; while (cin>>buf) m[buf]++; // eredmny kirsa }

A Jchar japn karaktereket hasznl vltozat ez lenne:


int main() // szavak elfordulsnak megszmllsa a bemeneten { String<Jchar> buf; map<String<Jchar>,int> m; while (cin>>buf) m[buf]++; // eredmny kirsa }

A standard knyvtrban szerepel a sablonn alaktott String-hez hasonl basic_string sablon is (11.12, 20.3). A standard knyvtrban a string mint a basic_string<char> szinonimja szerepel:
typedef basic_string<char> string;

Ez lehetv teszi, hogy a szszmll pldt gy rjuk t:


int main() // szavak elfordulsnak megszmllsa a bemeneten { string buf; map<string,int> m; while (cin>>buf) m[buf]++; // eredmny kirsa }

A typedef-ek ltalban is hasznosak a sablonokbl ltrehozott osztlyok hossz neveinek lervidtsre. Radsul, ha nem rdekel bennnket egy tpus pontos definicija, akkor egy typedef elrejti ellnk, hogy sablonbl ltrehozott tpusrl van sz.

Forrs: http://www.doksi.hu

13. Sablonok

435

13.2.1. Sablonok meghatrozsa


A sablonbl ltrehozott osztlyok teljesen kznsges osztlyok, ezrt a sablonok hasznlata semmivel sem ignyel hosszabb futsi idt, mint egy egyenrtk kzzel rott osztly, de nem felttlenl jelenti a ltrehozott kd mennyisgnek cskkenst sem. ltalban j tlet hibakeresssel ellenrizni egy osztlyt, pldul a String-et, mieltt sablont ksztnk belle (String<C>). Ezltal szmos tervezsi hibt, a kdhibknak pedig a legtbbjt egy adott osztly sszefggsben kezelhetnk. Ezt a fajta hibakeresst (debugging) a legtbb programoz jl ismeri, s a legtbben jobban boldogulnak egy konkrt pldval, mint egy elvonttal. Ksbb aztn anlkl foglalkozhatunk a tpus ltalnostsbl esetleg add problmkkal, hogy a hagyomnyosabb hibk elvonnk a figyelmnket. Hasonlan, ha meg akarunk rteni egy sablont, akkor hasznos annak viselkedst elszr egy konkrt tpus paramterrel (pldul a char-ral) elkpzelni, mieltt megprbljuk a viselkedst teljes ltalnossgban megrteni. Egy sablon osztly (template class) tagjait ugyangy deklarljuk s definiljuk, mint a kznsges osztlyokt. Egy tagot nem szksges magban az osztlyban definilni; valahol mshol is elg, ugyangy, mint egy nem sablon osztlytag esetben (C.13.7.). A sablon osztlyok tagjai maguk is sablonok, paramtereik pedig ugyanazok, mint a sablon osztlyi. Ha egy ilyen tagot az osztlyn kvl runk le, kifejezetten sablonknt kell megadnunk:
template<class C> struct String<C>::Srep { C* s; // mutat az elemekre int sz; // elemek szma int n; // hivatkozsszmll // ... }; template<class C> C String<C>::read(int i) const { return rep->s[i]; } template<class C> String<C>::String() { rep = new Srep(0,C()); }

A sablonparamterek mint a C inkbb paramterek, mint a sablonon kvl definilt tpusok, de ez nem rinti azt a mdot, ahogyan az azokat hasznl sablonkdot rjuk. A String<C> hatkrn bell a <C>-vel val minsts felesleges, hiszen a sablon neve mr tartalmazza azt, gy a konstruktor neve String<C>::String lesz. De ha jobban tetszik, meg is adhatjuk a minstst:

Forrs: http://www.doksi.hu

436

Absztrakcis mdszerek

template<class C> String<C>::String<C>() { rep = new Srep(0,C()); }

Egy programban egy tagfggvnyt csak egyetlen fggvny definilhat. Ugyangy a sablon osztlyok tagfggvnyeit is csak egy fggvnysablon definilhatja. De amg a fggvnyeket csak tlterhelni lehet (13.3.2), addig a specializcik (13.5) hasznlata lehetv teszi, hogy egy sablonnak tbb vltozatt is elkszthessk. Az osztlysablonok neve nem terhelhet tl, gy ha egy hatkrben mr megadtunk egy osztlysablont, ott nem lehet ugyanolyan nven msik egyedet bevezetni (lsd mg 13.5):
template<class T> class String { /* ... */ }; class String { /* ... */ }; // hiba: kt meghatrozs

A sablonparamterknt hasznlt tpusnak biztostania kell a sablon ltal vrt felletet. A String sablon paramtereknt hasznlt tpusnak pldul tmogatnia kell a szoksos msol mveleteket (10.4.4.1, 20.2.1). Jegyezzk meg: az nem kvetelmny, hogy egy sablon klnbz paramterei rkldsi viszonyban lljanak egymssal.

13.2.2. Sablonok pldnyostsa


Az eljrst, melynek sorn egy sablon osztlybl s egy sablonparamterbl egy osztlydeklarci keletkezik, gyakran sablon-pldnyostsnak (template instantiation) hvjk (C.13.7.). Ha fggvnyt hozunk ltre egy sablon fggvnybl s egy sablonparamterbl, az a fggvny-pldnyosts. A sablon adott paramtertpus szmra megadott vltozatt specializcinak (specialization) nevezzk. ltalban az adott C++ fordt s nem a programoz dolga, hogy minden felhasznlt paramtertpus szmra ltrehozza a megfelel sablon fggvnyt (C.13.7):
String<char> cs; void f() { String<Jchar> js; } cs = "Az adott nyelvi vltozat feladata, hogy kitallja, milyen kdot kell ltrehozni.";

Forrs: http://www.doksi.hu

13. Sablonok

437

A fenti esetben a String<char> s a String<Jchar>, a megfelel Srep tpusok, a destruktorok s az alaprtelmezett konstruktorok, illetve a String<char>::operator=(char *) deklarciit a fordt hozza ltre. Ms tagfggvnyeket nem hasznlunk, gy ilyeneket nem kell ksztenie (remlhetleg nem is teszi). A ltrehozott osztlyok kznsges osztlyok, gy az osztlyokra vonatkoz szoksos szablyok rvnyesek rjuk. Ugyangy a ltrehozott fggvnyek is kznsges fggvnyek s a fggvnyekre vonatkoz szoksos szablyok szerint viselkednek. Nyilvnval, hogy a sablonok hatkony eszkzt adnak arra, hogy viszonylag rvid defincikbl hozzunk ltre kdot. Ezrt aztn nem rt nmi vatossg, hogy elkerljk a memrinak csaknem azonos fggvny-definicikkal val elrasztst (13.5).

13.2.3. Sablonparamterek
A sablonoknak lehetnek tpust meghatroz, kznsges tpus (pl. int), s sablon tpus paramtereik (C.13.3). Termszetesen egy sablonnak tbb paramtere is lehet:
template<class T, T def_val> class Cont { /* ... */ };

Ahogy a plda mutatja, egy sablonparamtert felhasznlhatunk a tovbbi sablonparamterek meghatrozsban is. Az egsz tpus paramterek mretek s korltok megadsnl hasznosak:
template<class T, int i> class Buffer { T v[i]; int sz; public: Buffer() : sz(i) {} // ... }; Buffer<char,127> cbuf; Buffer<Record,8> rbuf;

A Buffer-hez hasonl egyszer s korltozott trolk ott lehetnek fontosak, ahol a futsi idej hatkonysg s a program tmrsge elsdleges szempont, s ahol ezrt nem lehet az ltalnosabb string-et vagy vector-t hasznlni. Mivel a sablon a mretet paramterknt megkapja, a kifejtsben el lehet kerlni a szabad tr hasznlatt. Egy msik plda erre a Range osztly a 25.6.1-ben.

Forrs: http://www.doksi.hu

438

Absztrakcis mdszerek

A sablon paramtere lehet konstans kifejezs (C.5), kls szerkeszts objektum vagy fggvny cme (9.2), illetve egy tagra hivatkoz, tl nem terhelt mutat (15.5). A mutat, ha sablon paramtereknt akarjuk hasznlni, &of alak kell, hogy legyen, ahol of egy objektum vagy fggvny neve, illetve f alak, ahol f egy fggvny neve. A tagra hivatkoz mutatkat &X::of alakban kell megadni, ahol of a tag neve. Karakterlnc literlt nem hasznlhatunk sablonparamterknt. Az egsz tpus paramtereknek konstansnak kell lennik:
void f(int i) { Buffer<int,i> bx; }

// hiba: konstans kifejezs szksges

Megfordtva, a nem tpusba tartoz paramterek a sablonon bell llandk, gy a paramter rtknek mdostsra tett ksrlet hibnak szmt.

13.2.4. Tpusok egyenrtksge


Ha adott egy sablon, akkor klnfle paramtertpusok megadsval klnfle tpusokat hozhatunk ltre belle:
String<char> s1; String<unsigned char> s2; String<int> s3; typedef unsigned char Uchar; String<Uchar> s4; String<char> s5; Buffer<String<char>,10> b1; Buffer<char,10> b2; Buffer<char,20-10> b3;

Ha azonos paramterekkel adunk meg sablonokat, azok ugyanarra a ltrehozott tpusra fognak hivatkozni. De mit is jelent itt az, hogy azonos? Szoks szerint, a typedef-ek nem vezetnek be j tpust, gy a String<Uchar> ugyanaz, mint a String<unsigned char>. Megfordtva, mivel a char s az unsigned char klnbz tpusok (4.3), a String<char> s a String<unsigned char> is klnbzek lesznek. A fordtprogram ki tudja rtkelni a konstans kifejezseket is (C.5), gy a Buffer<char,2010>-rl felismeri, hogy a Buffer<char,10>-zel azonos tpus.

Forrs: http://www.doksi.hu

13. Sablonok

439

13.2.5. Tpusellenrzs
A sablonokat paramterekkel definiljuk s ksbb gy is hasznljuk. A sablondefinciban a fordtprogram ellenrzi a formai hibkat, illetve az olyanokat, amelyek a konkrt paramterek ismerete nlkl felderthetek:
template<class T> class List { struct Link { Link* pre; Link* suc; T val; Link(Link* p, Link* s,const T& v) : pre(p), suc(s), val(v) { } } // szintaktikus hiba: hinyzik a pontosvessz Link* head; public: List() : head(7) { } // hiba: kezdeti rtkads mutatnak int-tel List(const T& t) : head(new Link(0,o,t)) { } // hiba: 'o' nem definilt azonost // ... void print_all() const { for (Link* p = head; p; p=p->suc) cout << p->val << '\n'; } };

A fordtprogram az egyszer nyelvi hibkat mr a defincinl kiszrheti, nha azonban csak ksbb, a hasznlatnl. A felhasznlk jobban szeretik, ha a hibk hamar kiderlnek, de nem minden egyszer hibt knny felderteni. Ebben a pldban hrom hibt vtettem (szndkosan). A sablon paramtertl fggetlenl egy T* tpus mutatnak nem adhatjuk a 7 kezdrtket. Hasonlan, az o vltoz (amely persze egy hibsan rt nulla) nem lehet a List<T>::Link konstruktor paramtere, mert ilyen nv az adott pontrl nem elrhet. A sablon definicijban hasznlt nvnek vagy ismertnek kell lennie, vagy valamilyen sszer s nyilvnval mdon kell fggnie valamelyik sablonparamtertl (C.13.8.1). A T sablonparamtertl val legkznsgesebb s legkzenfekvbb fggs egy T tpus tag vagy T tpus paramter hasznlata. A List<T>::print_all() pldban a cout << p->val kifejezs hasznlata nmileg kifinomultabb plda. A sablonparamterek hasznlatval sszefgg hibk csak a sablon hasznlatnak helyn derthetk fel:
class Rec { /* ... */ }; void f(const List<int>& li, const List<Rec>& lr) { li.print_all(); lr.print_all(); }

Forrs: http://www.doksi.hu

440

Absztrakcis mdszerek

Itt a li.print_all() rendben van, de a lr.print_all() tpushibs, mert a Rec tpusnak nincs << kimeneti mvelete. A legels pont, ahol a sablonparamterek hasznlatval sszefgg hiba kiderlhet, a sablonnak az adott paramterrel val els hasznlata. Ezt a pontot rendszerint els pldnyostsi pontnak (first point of instantiation) vagy egyszeren pldnyostsi pontnak hvjk (C.13.7). Az adott C++-vltozat megengedett mdon ezt az ellenrzst a program sszeszerkesztsig elhalaszthatja. Ha ebben a fordtsi egysgben a print_all()nak csak a deklarcija s nem a defincija ismert, lehetsges, hogy az adott fordtnak el is kell halasztania a tpusellenrzst a program sszeszerkesztsig (13.7). A tpusellenrzs azonos szablyok szerint trtnik, fggetlenl attl, hogy melyik ponton megy vgbe. A felhasznlk itt is a minl korbbi ellenrzst szeretik. A sablonparamterekre vonatkoz megszortsokat a tagfggvnyek segtsgvel is kifejezhetjk (13.9[16]).

13.3. Fggvnysablonok
A legtbb programoz szmra a sablonok els szm s legnyilvnvalbb felhasznlsa olyasfle trol osztlyok ltrehozsa s hasznlata, mint a basic_string (20.3), a vector (16.3), a list (17.2.2) vagy a map (17.4.1). Ksbb azonban felmerl a sablonknt hasznlt fggvnyek szksgessge. Nzzk pldul egy tmb rendezst:
template<class T> void sort(vector<T>&); void f(vector<int>& vi, vector<string>& vs) { sort(vi); sort(vs); } // deklarci

// sort(vector<int>&); // sort(vector<string>&);

A sablon fggvnyek (template function) meghvsakor a fggvny paramterei hatrozzk meg, hogy a sablon melyik pldnyt hasznljuk, vagyis a sablonparamtereket a fggvnyparamterekbl vezetjk le (deduce) (13.3.1). Termszetesen a sablon fggvnyt valahol definilnunk kell (C.13.7):
template<class T> void sort(vector<T>& v) { const size_t n = v.size(); // definci 2 // Shell rendezs (Knuth, III. ktet, 84. o. )

2 Magyarul: D. E. Knuth: A szmtgp-programozs mvszete III. ktet, Keress s rendezs; Mszaki

knyvkiad, Budapest, 1988; 95. oldal

Forrs: http://www.doksi.hu

13. Sablonok

441

for (int gap=n/2; 0<gap; gap/=2) for (int i=gap; i<n; i++) for (int j=i-gap; 0<=j; j-=gap) if (v[j+gap]<v[j]) { T temp = v[j]; v[j] = v[j+gap]; v[j+gap] = temp; }

// v[j] s v[j+gap] felcserlse

Hasonltsuk ssze a sort() ezen definicijt a 7.7-belivel. Ez a sablonn alaktott vltozat vilgosabb s rvidebb, mert a rendezend elemek tpusra vonatkozan tbb informcira tmaszkodhat. Valsznleg gyorsabb is, mert nincs szksge az sszehasonlt fggvnyre hivatkoz mutatra. Ebbl kvetkezik, hogy nincs szksg kzvetett fggvnyhvsra, a < sszehasonltst pedig knnyen lehet helyben kifejtve (inline) fordtani. Tovbbi egyszerstst jelenthet a standard knyvtrbeli swap() sablon hasznlata (18.6.8), mellyel az rtkcsert termszetes formra alakthatjuk:
if (v[j+gap]<v[j]) swap(v[j],v[j+gap]);

Ez a kd hatkonysgt semmilyen mdon nem rontja. Ebben a pldban a < mveletet hasznltuk sszehasonltsra. Nem minden tpusnak van azonban < opertora, ami korltozza a sort() ezen vltozatnak hasznlhatsgt; de ez a korltozs knnyen megkerlhet (13.4).

13.3.1. A fggvnysablonok paramterei


A fggvnysablonok alapvet fontossgak a trol tpusok (2.7.2, 3.8, 18. fejezet) szles krre alkalmazhat ltalnos algoritmusok rshoz. Alapvet jelentsg, hogy egy fggvnyhvskor a sablonparamtereket le lehet vezetni, ki lehet kvetkeztetni (deduce) a fggvny paramtereibl. A fordtprogram akkor tudja levezetni egy hvs tpusos s nem tpusba tartoz paramtereit, ha a fggvny paramterlistja egyrtelmen azonostja a sablonparamterek halmazt (C.13.4):
template<class T, int i> T& lookup(Buffer<T,i>& b, const char* p); class Record { const char[12]; // ... };

Forrs: http://www.doksi.hu

442

Absztrakcis mdszerek

Record& f(Buffer<Record,128>& buf, const char* p) { } return lookup(buf,p); // lookup() hasznlata, ahol T egy Record s i rtke 128

Itt T-rl azt llaptja meg a fordtprogram, hogy Record, az i-rl pedig azt, hogy rtke128. Megjegyzend, hogy a fordtprogram az osztlysablonok paramtereit soha nem vezeti le (C.13.4). Ennek az az oka, hogy az osztlyok tbbfle konstruktora nyjtotta rugalmassg ezt sok esetben megakadlyozn, esetleg ttekinthetetlenn tenn. Egy osztly klnfle vltozatai kztti vlasztsra a specializlt vltozatok hasznlata ad eszkzt (13.5). Ha egy levezett tpus objektumot kell ltrehoznunk, ezt sokszor megtehetjk gy, hogy a ltrehozst egy fggvny meghvsval hajtatjuk vgre (lsd a 17.4.1.2 pontbeli make_pair()-t). Ha egy paramtert nem lehet levezetni a sablon fggvny paramtereibl (C.13.4), akkor kzvetlenl meg kell adnunk. Ezt ugyangy tehetjk meg, mint ahogy egy sablon osztly szmra kzvetlenl megadjuk a sablonparamtereket:
template<class T> class vector { /* ... */ }; template<class T> T* create(); // T ltrehozsa s r hivatkoz mutat visszaadsa void f() { vector<int> v; int* p = create<int>(); }

// osztly, sablonparamtere 'int' // fggvny, sablonparamtere 'int'

A kzvetlen meghatrozs (explicit specification) egyik szoksos hasznlata a sablon fggvny visszatrsirtk-tpusnak megadsa:
template<class T, class U> T implicit_cast(U u) { return u; } void g(int i) { implicit_cast(i); implicit_cast<double>(i); implicit_cast<char,double>(i); implicit_cast<char*,int>(i); }

// hiba: T nem vezethet le // T tpusa double, U tpusa int // T tpusa char, U tpusa double // T tpusa char*, U tpusa int; hiba: int // nem alakthat char*-ra

Az alaprtelmezett fggvnyparamter-rtkekhez hasonlan (7.5), az explicit megadott sablonparamterek kzl is csak az utolskat lehet elhagyni.

Forrs: http://www.doksi.hu

13. Sablonok

443

A sablonparamterek kzvetlen megadsa talakt fggvnycsaldok s objektum-ltrehoz fggvnyek definicijt teszi lehetv (13.3.2, C.13.1, C.13.5). Az automatikus (implicit) konverzik (C.6) explicit vltozatai, pldul az implicit_cast() idnknt igen hasznosak lehetnek. A dynamic_cast, static_cast stb. formai kvetelmnyei megfelelnek az explicit minsts sablon fggvnyekinek. A beptett tpuskonverzis opertorok azonban olyan mveleteket tmogatnak, amelyeket nem fejezhetnk ki ms nyelvi elemmel.

13.3.2. Fggvnysablonok tlterhelse


Azonos nven tbb fggvnysablon is szerepelhet, st ugyanolyan nven tbb kznsges fggvny is. A tlterhelt (vagyis azonos nvvel mst s mst jelent) fggvnyek meghvsakor a megfelel meghvand fggvny vagy fggvnysablon kivlasztshoz a tlterhels (overloading) feloldsa szksges:
template<class T> T sqrt(T); template<class T> complex<T> sqrt(complex<T>); double sqrt(double); void f(complex<double> z) { sqrt(2); // sqrt<int>(int) sqrt(2.0); // sqrt(double) sqrt(z); // sqrt<double>(complex<double>) }

Ahogy a sablon fggvny fogalma a fggvny fogalmnak ltalnostsa, ugyangy a sablon fggvnyekre alkalmazand tlterhels-feloldsi szablyok is a fggvnyekre alkalmazand tlterhels-feloldsi szablyok ltalnostsai. A mdszer alapveten a kvetkez: megkeressk minden sablonhoz azt a specializlt vltozatot, amelyik a paramtereknek a legjobban megfelel. Ezutn ezekre a pldnyokra s az sszes kznsges fggvnyre is a szoksos tlterhels-feloldsi szablyokat alkalmazzuk: 1. Meg kell keresni azokat a specializlt sablon fggvny vltozatokat (13.2.2), amelyek rszt fognak venni a tlterhels feloldsban. Ehhez az sszes fggvnysablont megvizsgljuk, hogy ha ms ugyanilyen nev fggvny vagy sablon fggvny nem lenne elrhet, akkor lehetne-e valamilyen sablonparamterrel alkalmazni. Az sqrt(z) hvs esetben pldul a kvetkez jelltek addnak: sqrt<double>(complex<double>) s sqrt<complex<double>>(complex<double>). 2. Ha kt sablon fggvny is meghvhat lenne s az egyik specializltabb a msiknl (13.5.1), akkor a kvetkez lpsekben csak azt vesszk figyelembe.

Forrs: http://www.doksi.hu

444

Absztrakcis mdszerek

Az sqrt(z) hvs esetben az sqrt<double>(complex<double>)-t vlasztjuk az sqrt<complex<double>>(complex<double>) helyett: minden hvs, ami megfelel sqrt<T>(complex<T>)-nek, megfelel sqrt<T>(T)-nek is. 3. Ezek utn vgezzk el a kznsges tlterhels-feloldst ezen fggvnyekre s a kznsges fggvnyekre (7.4.). Ha egy sablon fggvny paramtert a sablonparamterekbl vezettk le (13.3.1), akkor arra nem alkalmazhatunk kiterjesztst (promotion), illetve szabvnyos vagy felhasznli konverzit. Az sqrt(2) hvs pontosan megfelel az sqrt<int>(int)-nek, gy azt vlasztjuk a sqrt(double) helyett. 4. Ha egy fggvny s egy specializlt vltozata ugyanolyan mrtkben megfelel, akkor a fggvnyt vlasztjuk. Emiatt a sqrt(2.0)-hoz a sqrt(double)-t vlasztjuk, s nem a sqrt<double>(double)-t. 5. Ha nem tallunk megfelel fggvnyt, akkor a hvs hibs. Ha tbb ugyanolyan mrtkben megfelel fggvnyt is tallunk, akkor a hvs tbbrtelm s ezrt hibs:
template<class T> T max(T,T); const int s = 7; void k() { max(1,2); max('a','b'); max(2.7,4.9); max(s,7); max('a',1); max(2.7,4);

// max<int>(1,2) // max<char>('a','b') // max<double>(2.7,4.9) // max<int>(int(s),7) (egyszer konverzi) // hiba: tbbrtelm (nincs szabvnyos konverzi) // hiba: tbbrtelm (nincs szabvnyos konverzi)

A fenti plda kt nem egyrtelm hvst explicit minstssel oldhatjuk fel:


void f() { max<int>('a',1); max<double>(2.7,4); }

// max<int>(int('a'),1) // max<double>(2.7,double(4))

Vagy megfelel deklarcik alkalmazsval:


inline int max(int i, int j) { return max<int>(i,j); } inline double max(int i, double d) { return max<double>(i,d); } inline double max(double d, int i) { return max<double>(d,i); } inline double max(double d1, double d2) { return max<double>(d1,d2); }

Forrs: http://www.doksi.hu

13. Sablonok

445

void g() { max('a',1); max(2.7,4); }

// max(int('a'),1) // max(2.7,double(4))

Kznsges fggvnyekre a kznsges tlterhels-feloldsi szablyok rvnyesek (7.4), s a helyben kifejts (inline) biztostja, hogy a hvs nem jr kln kltsggel. A max() fggvny igen egyszer, gy explicit mdon is rhattuk volna, de a sablon specializlt hasznlata knny s ltalnosan hasznlhat mdja az ilyen tlterhels-felold fggvnyek rsnak. A tlterhels-feloldsi szablyok biztostjk, hogy a sablon fggvnyek helyesen mkdnek egytt az rkldssel:
template<class T> class B { /* ... */ }; template<class T> class D : public B<T> { /* ... */ }; template<class T> void f(B<T>*); void g(B<int>* pb, D<int>* pd) { f(pb); // f<int>(pb) f(pd); // f<int>(static_cast<B<int>*>(pd)); szabvnyos talakts D<int>*-rl B<int>*-ra }

Ebben a pldban az f() sablon fggvny minden T tpusra elfogadja B<T>*-ot. Egy D<int>* tpus paramternk van, gy a fordtprogram knnyen jut arra a kvetkeztetsre, hogy T-t int-nek vve a hvs egyrtelmen feloldhat, f(B<int>*)-knt. Az olyan fggvnyparamtereket, amelyek nem vesznek rszt a sablonparamter levezetsben, pontosan gy kezelhetjk, mint egy nem sablon fggvny paramtert, gy a szoksos tkonverzik megengedettek:
template<class T, class C> T get_nth(C& p, int n); // az n-edik elem

Ez a fggvny felttelezheten a C tpus trol n-edik elemt adja vissza. Minthogy C-t a hvs aktulis paramterbl kell levezetni, az els paramterre nem alkalmazhat konverzi, a msodik paramter azonban teljesen kznsges, gy a szoksos konverzik mindegyike tekintetbe vehet:
class Index { public: operator int(); // ... };

Forrs: http://www.doksi.hu

446

Absztrakcis mdszerek

void f(vector<int>& v, short s, Index i) { int i1 = get_nth<int>(v,2); // pontos illeszkeds int i2 = get_nth<int>(v,s); // szabvnyos konverzi: short-rl int-re int i3 = get_nth<int>(v,i); // felhasznli konverzi: Index-rl int-re }

13.4. Eljrsmd megadsa sablonparamterekkel


Gondoljuk meg, hogyan rendezhetjk a karakterlncokat. Hrom dolog jtszik szerepet: a karakterlnc, az elemek tpusa s a lnc elemeinek sszehasonltsakor alkalmazott szempont. Nem betonozhatjuk be a rendezsi elvet a trolba, mert az ltalban nem szabhatja meg, mire van szksge az elemek tpusval kapcsolatban, de az elemek tpusba sem, mert az elemeket sokfle mdon rendezhetjk. Ehelyett a megfelel mvelet vgrehajtsakor kell megadni az alkalmazand feltteleket. Milyen rendezsi elvet alkalmazzunk, ha pldul svd neveket tartalmaz karakterlncokat akarunk rendezni? A svd nevek rendezse szmra a karakterek kt klnbz numerikus megfeleltetsi mdja (collating sequence) hasznlatos. Termszetesen sem egy ltalnos string tpus, sem egy ltalnos rendez algoritmus nem tudhat a nevek rendezsnek svd szoksairl, ezrt brmely ltalnos megolds megkveteli, hogy a rendez eljrst ne csak egy adott tpusra adhassuk meg, hanem adott tpusra val adott alkalmazskor is. ltalnostsuk pldul a C standard knyvtrnak strcmp() fggvnyt tetszleges T tpusbl ll String-ekre (13.2):
template<class T, class C> int compare(const String<T>& str1, const String<T>& str2) { for(int i=0; i<str1.length() && i< str2.length(); i++) if (!C::eq(str1[i],str2[i])) return C::lt(str1[i],str2[i]) ? -1 : 1; return str1.length()-str2.length(); }

Ha valaki azt szeretn, hogy a compare() eltekintsen a kis- s nagybetk kztti klnbsgtl vagy figyelembe vegye a program nyelvi krnyezett (locale, helyi sajtossgok), akkor ezt a C::eq() s a C::lt() fggvnyek megfelel definilsval teheti meg. Ezzel minden (sszehasonlt, rendez stb.) eljrst lerhatunk, ha az a trol s a C-mveletek nyelvn megfogalmazhat:

Forrs: http://www.doksi.hu

13. Sablonok

447

template<class T> class Cmp { // szoksos, alaprtelmezett sszehasonlts public: static int eq(T a, T b) { return a==b; } static int lt(T a, T b) { return a<b; } }; class Literate { // svd nevek sszehasonltsa public: static int eq(char a, char b) { return a==b; } static int lt(char,char); // kikeress tblzatbl karakterrtk alapjn (13.9[14]) };

A sablonparamterek megadsakor most mr pontosan megadhatjuk az sszehasonltsi szablyokat:


void f(String<char> swede1, String<char> swede2) { compare< char,Cmp<char> >(swede1,swede2); compare< char,Literate >(swede1,swede2); }

Az sszehasonlt mveletek sablonparamterknt val megadsnak kt jelents elnye van az egyb lehetsgekhez, pldul a fggvnymutatk alkalmazshoz kpest. Egyrszt tbb mvelet megadhat egyetlen paramterknt, a futsi id nvekedse nlkl. Msrszt, az eq() s az lt() sszehasonlt mveleteket knny helyben kifejtve (inline) fordtani, mg egy fggvnymutatn keresztli hvs ilyen md fordtsa klnleges mrtk figyelmet kvetel a fordtprogramtl. Termszetesen sszehasonlt mveleteket nemcsak a beptett, hanem a felhasznli tpusokra is megadhatunk. Ez alapvet fontossg felttele annak, hogy ltalnos algoritmusokat olyan tpusokra alkalmazhassunk, amelyeknek nem maguktl rtetd sszehasonltsi feltteleik vannak (18.4). Minden osztlysablonbl ltrehozott osztly sajt pldnyokat kap a sablon statikus vltozibl (C.13.1).

13.4.1. Alaprtelmezett sablonparamterek


Fradsgos dolog minden egyes hvsnl kzvetlenl meghatrozni az sszehasonltsi feltteleket. Tlterhelssel szerencsre knnyen megadhatunk olyan alaprtelmezst, hogy csak a szoksostl eltr sszehasonltsi szempontot kelljen megadni:

Forrs: http://www.doksi.hu

448

Absztrakcis mdszerek

template<class T, class C> int compare(const String<T>& str1, const String<T>& str2); template<class T> int compare(const String<T>& str1, const String<T>& str2);

// sszehasonlts C // hasznlatval // sszehasonlts // Cmp<T> hasznlatval

De a szoksos rendezst megadhatjuk, mint alaprtelmezett sablonparamter-rtket is:


template<class T, class C = Cmp<T> > int compare(const String<T>& str1, const String<T>& str2) { for(int i=0; i<str1.length() && i< str2.length(); i++) if (!C::eq(str1[i],str2[i])) return C::lt(str1[i],str2[i]) ? -1 : 1; return str1.length()-str2.length(); }

gy mr lerhatjuk a kvetkezt:
void f(String<char> swede1, String<char> swede2) { compare(swede1,swede2); compare<char,Literate>(swede1,swede2); }

// Cmp<char> hasznlata // Literate hasznlata

Egy (nem svdek szmra) kevsb ezoterikus plda a kis- s nagybetk kztti klnbsget figyelembe vev, illetve elhanyagol rendezs:
class No_case { /* ... */ }; void f(String<char> s1, String<char> s2) { compare(s1,s2); compare<char,No_case>(s1,s2); }

// kisbet-nagybet klnbzik // kisbet-nagybet nem klnbzik

A standard knyvtr szles krben alkalmazza azt a mdszert, hogy az alkalmazand eljrsmdot (policy) egy sablonparamter adja meg, s ennek a legltalnosabb eljrsmd az alaprtelmezett rtke (pldul 18.4). Elgg furcsa mdon azonban a basic_string (13.2, 20. fejezet) sszehasonltsaira ez nem ll. Az eljrsmdot kifejez sablonparamtereket gyakran nevezik jellemvonsoknak (traits) is. Pldul a standard knyvtrbeli string a char_traits-re pl (20.2.1), a szabvnyos algoritmusok a bejrk (itertorok) jellemvonsait (19.2.2), a standard knyvtrbeli trolk pedig a memriafoglalkt (alloktor, 19.4.) hasznljk fel.

Forrs: http://www.doksi.hu

13. Sablonok

449

Egy alaprtelmezett sablonparamter rtelmi ellenrzse ott s csak akkor trtnik meg, ahol s amikor az alaprtelmezett paramtert tnylegesen felhasznljuk. gy ha nem hasznljuk fel az alaprtelmezett Cmp<T> paramtert, akkor olyan X tpusokra is hasznlhatjuk a compare()-t, amelyekre a fordt nem fordtan le aCmp<X>-et, mert mondjuk az X-re a < nem rtelmezett. Ez dnt jelentsg a szabvnyos trolk tervezsnl, hiszen ezek sablonparamtert hasznlnak az alaprtelmezett rtkek megadsra (16.3.4).

13.5. Specializci
Alaprtelmezs szerint egy sablon (template) egyetlen defincit ad a felhasznlhat ltal elkpzelhet sszes paramterrtk (vagy paramterrtkek) szmra. Ez azonban nem minden sablon rsakor kedvez. Elfordulhat, hogy olyasmit szeretnnk kifejezni, hogy ha a sablonparamter egy mutat, hasznld ezt, ha nem, hasznld azt, vagy hogy hiba, ha a sablonparamter nem a My_base osztly egy leszrmazottjra hivatkoz mutat. Sok hasonl tervezsi szempontot figyelembe lehet gy venni, hogy a sablonnak tbbfle defincit adunk s a fordtprogram az alkalmazott paramtertpusok szerint vlaszt kzlk. A sablon ilyenfle tbbszrs meghatrozst specializcinak (specialization, egyedi cl felhasznli vltozatok hasznlata, szakosts) hvjuk. Vegyk egy Vector sablon valszn felhasznlsait:
template<class T> class Vector { T* v; int sz; public: Vector(); Vector(int); T& elem(int i) { return v[i]; } T& operator[ ](int i); void swap(Vector&); // ... // ltalnos vektortpus

};

Vector<int> vi; Vector<Shape*> vps; Vector<string> vs; Vector<char*> vpc; Vector<Node*> vpn;

Forrs: http://www.doksi.hu

450

Absztrakcis mdszerek

A legtbb Vector valamilyen mutattpus Vector-a lesz. Tbb okbl is, de fleg azrt, mert a tbbalak (polymorph) viselkeds megrzse cljbl mutatkat kell hasznlnunk (2.5.4, 12.2.6). Ezrt aki objektumorientlt programozst folytat s tpusbiztos, pldul standard knyvtrbeli trolkat hasznl, az biztosan szmos mutatt tartalmaz trolt fog hasznlni. A legtbb C++-vltozat alaprtelmezs szerint lemsolja a sablon fggvnyek kdjt. Ez j a vgrehajtsi sebessg szempontjbl, de kritikus esetekben (mint az imnti Vector-nl) a kd felfvdsval jr. Szerencsre ltezik egyszer megolds. A mutatkat tartalmaz trolknak elg egyetlen megvalsts. Ezt specializlt vltozat ksztsvel rhetjk el. Elszr definiljuk a Vectornak a void mutatkra vonatkoz vltozatt (specializcijt):
template<> class Vector<void*> { void** p; // ... void*& operator[ ](int i); };

Ezt a vltozatot aztn az sszes, mutatt tartalmaz vektor kzs megvalstsaknt hasznlhatjuk. A template<> eltag azt jelenti, hogy ennl a specializlt vltozatnl nem kell sablonparamtert megadnunk. Azt, hogy milyen tpus sablonparamterre hasznljuk, a nv utni <> jelpr kztt adjuk meg: vagyis a <void*> azt jelenti, hogy ezt a defincit kell minden olyan Vector esetben hasznlni, amelyikre a T tpusa void*. A Vector<void*> egy teljes specializci, azaz ezen vltozat hasznlatakor nincs sablonparamter, amit meg kellene adni vagy le kellene vezetni; a Vector<void*>-ot a kvetkez mdon deklarlt Vector-ok szmra hasznljuk:
Vector<void*> vpv;

Ha olyan vltozatot akarunk megadni, ami mutatkat tartalmaz Vector-ok, s csak azok esetn hasznland, rszleges specializcira van szksgnk:
template<class T> class Vector<T*> : private Vector<void*> { public: typedef Vector<void*> Base; Vector() : Base() {} explicit Vector(int i) : Base(i) {}

Forrs: http://www.doksi.hu

13. Sablonok

451

T*& elem(int i) { return reinterpret_cast<T*&>(Base::elem(i)); } T*& operator[ ](int i) { return reinterpret_cast<T*&>(Base::operator[ ](i)); } }; // ...

A nv utni <T*> specializl minta azt jelzi, hogy ezt a vltozatot kell minden mutattpus esetben hasznlni; azaz minden olyan sablonparamternl, ami T* alakba rhat:
Vector<Shape*> vps; Vector<int**> vppi; // <T*> most <Shape*>, gy T is Shape // <T*> most <int**>, gy T is int*

Jegyezzk meg, hogy rszleges specializci hasznlata esetn a sablonparamter a specializcira hasznlt mintbl addik; a sablonparamter nem egyszeren az aktulis sablonparamter. gy pldul a Vector<Shape*> esetben T tpusa Shape s nem Shape*. Ha adott a Vector ezen rszlegesen specializlt vltozata, akkor ez az sszes mutattpusra vonatkoz Vector kzs megvalstsa. A Vector<T*> osztly egyszeren egy fellet a void*-os vltozathoz, melyet kizrlag az rklds s a helyben kifejts eszkzvel valstottuk meg. Fontos, hogy a Vector ezen finomtsa a felhasznli fellet megvltoztatsa nlkl trtnt. A specializci a kzs fellet tbbfle meghatrozsnak eszkze. Termszetesen az ltalnos Vector-t s a mutatkra vonatkoz vltozatot hvhattuk volna klnbzkppen is. Amikor ezt kiprbltam, kiderlt, hogy sok felhasznl, akinek tudnia kellett volna rla, mgsem a mutats vltozatot hasznlta s a kapott kd a vrtnl sokkal nagyobb lett. Ebben az esetben sokkal jobb a fontos rszleteket egy kzs fellet mg rejteni. Ez a mdszer a gyakorlatban a kd felfvdsnak megakadlyozsban volt sikeres. Akik nem alkalmaznak ilyen mdszereket (akr a C++-ban, akr egyb tpus-paramterezsi lehetsgeket tartalmaz nyelvekben), knnyen azt vehetik szre, hogy az ismtld kd kzepes mret programok esetben is megabjtokra rghat. A vektormveletek klnbz vltozatainak lefordtshoz szksges id megtakartsval ez a mdszer a fordtsi s szerkesztsi idt is drmai mdon cskkenti. Az sszes mutatt tartalmaz lista egyetlen specializlt vltozattal val megvalstsa j plda arra, hogyan lehet a kdfelfvdst megakadlyozni gy, hogy a kzs kdot a lehet legjobban nveljk. Az ltalnos sablont az sszes specializlt vltozat eltt kell megadni:
template<class T> class List<T*> { /* ... */ }; template<class T> class List { /* ... */ }; // hiba: ltalnos sablon a specializlt utn

Forrs: http://www.doksi.hu

452

Absztrakcis mdszerek

Az ltalnos sablon ltal adott ltfontossg informci az, hogy milyen paramtereket kell a felhasznls vagy a specializci sorn megadni. Ezrt elg az ltalnos sablont a specializlt vltozat deklarcija vagy definicija eltt megadni:
template<class T> class List; template<class T> class List<T*> { /* ... */ };

Ha hasznljuk is, akkor az ltalnos sablont valahol definilnunk kell (13.7). Ha valahol szerepel egy felhasznli specializlt vltozat, akkor annak deklarcija a specializlt hasznlat minden helyrl elrhet kell, hogy legyen:
template<class T> class List { /* ... */ }; List<int*> li; template<class T> class List<T*> { /* ... */ }; // hiba

Itt a List-et az int*-ra a List<int*> hasznlata utn specializlunk. Egy sablon minden specializlt vltozatt ugyanabban a nvtrben kell megadni, mint magt a sablont. Ha hasznljk, akkor egy explicit deklarlt (azaz nem egy ltalnosabbl ltrehozott) sablonnak valahol szintn explicit definiltnak kell lennie (13.7). Vagyis a specializlt vltozat explicit megadsbl kvetkezik, hogy szmra a fordt nem hoz ltre defincit.

13.5.1. Specializcik sorrendje


Az egyik vltozat specializltabb egy msiknl, ha minden olyan paramterlista, amely illeszkedik az egyikre, illeszkedik a msikra is. Ez fordtva nem ll fenn:
template<class T> class Vector; template<class T> class Vector<T*>; template<> class Vector<void*>; // ltalnos // mutatkhoz // void*-okhoz

Minden tpust hasznlhatunk a legltalnosabb Vector paramtereknt, de a Vector<T*> paramtereknt csak mutatt, a Vector<void*> paramtereknt pedig csak void* mutatt. Az objektumok s mutatk stb. (13.5) deklarcijban, illetve a tlterhels feloldsakor (13.3.2) a leginkbb specializlt vltozat rszesl elnyben.

Forrs: http://www.doksi.hu

13. Sablonok

453

A specializl minta megadsnl a sablonparamterek levezetsnl (13.3.1) hasznlt tpusokbl sszelltott tpusok hasznlhatk fel.

13.5.2. Fggvnysablon specializci


A specializci termszetesen a sablon fggvnyeknl (template function) is hasznos. Vegyk a 7.7 s 13.3 pldabeli Shell rendezst. Ez az elemeket a < segtsgvel hasonltja ssze s a rszletezett kd segtsgvel cserli fel. Jobb definci lenne a kvetkez:
template<class T> bool less(T a, T b) { return a<b; } template<class T> void sort(Vector<T>& v) { const size_t n = v.size(); for (int gap=n/2; 0<gap; gap/=2) for (int i=gap; i<n; i++) for (int j=i-gap; 0<=j; j-=gap) if (less(v[j+gap],v[j])) swap(v[j],v[j+gap]);

Ez nem javtja magt az algoritmust, de lehetsget nyjt a megvalsts javtsra. Eredeti formjban egy Vector<char*>-ot nem rendez jl, mert kt char*-ot a < segtsgvel hasonlt ssze, azaz az els karakter cmt fogja az sszehasonlts alapjul venni. Ehelyett a mutatott karakterek szerinti sszehasonltst szeretnnk. Erre a less()-nek egy egyszer, const char*-ra vonatkoz specializcija fog gyelni:
template<> bool less<const char*>(const char* a, const char* b) { return strcmp(a,b)<0; }

Mint az osztlyoknl is (13.5), a <> sablon-eltag azt jelzi, hogy ez egy sablonparamter megadsa nlkli specializlt vltozat. A sablon fggvny neve utni <const char*> azt jelenti, hogy a fggvnyt azokban az esetekben kell alkalmazni, amikor a sablonparamter const char*. Minthogy a sablonparamter kzvetlenl levezethet a fggvny paramterlistjbl, nem kell explicit megadnunk. gy egyszersthetnk a specializci megadsn:
template<> bool less<>(const char* a, const char* b) { return strcmp(a,b)<0; }

Forrs: http://www.doksi.hu

454

Absztrakcis mdszerek

Minthogy adott a template<> eltag, a msodik, res <> felesleges, hiszen nem hordoz j informcit. gy aztn ltalban gy rnnk:
template<> bool less(const char* a, const char* b) { return strcmp(a,b)<0; }

n jobban kedvelem ezt a rvidebb deklarcis formt. Vegyk a swap() kzenfekv meghatrozst:
template<class T> void swap(T& x, T& y) { T t = x; // x msolsa az ideiglenes vltozba x = y; // y msolsa x-be y = t; // ideiglenes vltoz msolsa y-ba }

Ez nem tl hatkony, ha Vector-ok vektoraira hvjuk meg; az sszes elem msolsval cserli fel a Vector-okat. De ezt is megoldhatjuk megfelel specializcival. Maga a Vector objektum csak annyi adatot tartalmaz, hogy az elemeihez val kzvetett hozzfrst tmogassa (mint a string 11.12, 13.2). gy aztn a csert a megfelel brzol adatok cserjvel lehet megoldani. Hogy lehetv tegyk az brzol adatok kezelst, adjunk meg egy swap() fggvnyt a Vector osztly szmra (13.5):
template<class T> void Vector<T>::swap(Vector & a) { swap(v,a.v); swap(sz,a.sz); } // brzolsok cserje

A swap() tagot felhasznlhatjuk az ltalnos swap() specializlt definicijban:


template<class T> void swap(Vector<T>& a, Vector<T>& b) { a.swap(b); }

A less() s a swap() ezen vltozatait hasznlja a standard knyvtr (16.3.9, 20.3.16) is. Radsul ezek a pldk szles krben hasznlt mdszereket mutatnak be. A specializci akkor hasznos, ha a sablonparamterek egy adott halmazra egy ltalnos algoritmusnl ltezik hatkonyabb megvalsts (itt a swap()). Ezenkvl akkor is hasznos, ha a paramtertpus valamilyen szablytalansga miatt a szabvnyos algoritmus valamilyen nem kvnt mdon mkdne (mint a less() esetben). Ezek a szablytalan tpusok tbbnyire a beptett mutat- s tmbtpusok.

Forrs: http://www.doksi.hu

13. Sablonok

455

13.6. rklds s sablonok


Az rklds s a sablonok olyan eszkzk, amelyekkel meglevk alapjn j tpusok pthetk, s amelyekkel ltalnossgban a kzs vonsok klnfle kihasznlsval hasznos kd rhat. Mint a 3.7.1, 3.8.5 s 13.5 pontokban lttuk, ezen eszkzk prostsa sok hasznos mdszer alapja. Egy sablon osztlynak (template class) egy nem sablon osztlybl val szrmaztatsa mdot nyjt arra, hogy sablonok egy halmaza kzs megvalstson osztozzk. A 13.5 pontbeli vektor j plda erre:
template<class T> class List<T*> : private List<void*> { /* ... */ };

Msknt nzve a plda azt mutatja, hogy a sablon elegns s tpusbiztos felletet ad egy msknt nem biztonsgosan s nem elegnsan hasznlhat eszkzhz. Termszetesen a sablon osztlyok kztti rklds is hasznos. A bzisosztly egyik haszna az, hogy tovbbi osztlyok szmra ptkl szolgl. Ha a bzisosztly adatai vagy mveletei valamely szrmaztatott osztly sablonparamtertl fggenek, akkor magnak a bzisosztlynak is paramtereket kell adni (lsd pldul a 3.7.2 pontbeli Vec osztlyt):
template<class T> class vector { /* ... */ }; template<class T> class Vec : public vector<T> { /* ... */ };

A sablon fggvnyek tlterhels-feloldsi szablyai biztostjk a fggvnyek helyes mkdst az ilyen szrmaztatott tpusokra (13.3.2). Az az eset a leggyakoribb, amikor ugyanaz a sablonparamter szerepel a bzis- s a szrmaztatott osztlyban is, de ez nem szksgszer. rdekes, br ritkbban alkalmazott mdszerek alapulnak azon, hogy magt a szrmaztatott osztlyt adjuk t a bzisosztlynak:
template <class C> class Basic_ops { // trolk alapmveletei public: bool operator==(const C&) const; // minden elem sszehasonltsa bool operator!=(const C&) const; // ... // hozzfrs biztostsa C mveleteihez const C& derived() const { return static_cast<const C&>(*this); } };

Forrs: http://www.doksi.hu

456

Absztrakcis mdszerek

template<class T> class Math_container : public Basic_ops< Math_container<T> > { public: size_t size() const; T& operator[ ](size_t); const T& operator[ ](size_t) const; // ... };

Ezltal lehetv vlik a trolk alapvet mveleteinek az egyes trolktl elklntett, egyszeri meghatrozsa. m mivel az olyan mveletek defincijhoz, mint az == s a != mind a trolnak, mind annak elemeinek tpusra szksg van, a bzisosztlyt t kell adni a trolsablonnak. Ha feltesszk, hogy a Math_container egy hagyomnyos vektorra hasonlt, akkor a Basic_ops egy tagja valahogy gy nzhet ki:
template <class C> bool Basic_ops<C>::operator==(const C& a) const { if (derived().size() != a.size()) return false; for (int i = 0; i<derived().size(); ++i) if (derived()[i] != a[i]) return false; return true; }

A trolk s a mveletek elvlasztsra szolgl msik mdszer rklds helyett sablonparamterekkel kapcsolja ssze azokat:
template<class T, class C> class Mcontainer { C elements; public: T& operator[ ](size_t i) { return elements[i]; } .. // ... friend bool operator==(const Mcontainer&, const Mcontainer&); friend bool operator!=(const Mcontainer&, const Mcontainer&); // ... // elemek // sszehasonltsa

};

template<class T> class My_array { /* ... */ }; Mcontainer< double,My_array<double> > mc;

Forrs: http://www.doksi.hu

13. Sablonok

457

Egy sablonbl ltrehozott osztly teljesen kznsges osztly, ezrt lehetnek bart (friend) fggvnyei (C.13.2) is. Ebben a pldban azrt hasznltam bartokat, hogy a == s a != szmra a kt paramter szoksos felcserlhetsgt biztostsam (11.3.2). Ilyen esetekben meg lehetne fontolni azt is, hogy a C paramterknt trol helyett sablont hasznljunk.

13.6.1. Paramterezs s rklds


A sablonok egy tpust vagy fggvnyt egy msik tpussal paramtereznek. A sablon kdja minden paramtertpus esetben azonos, csakgy, mint a sablont hasznl legtbb kdrsz. Az absztrakt osztlyok felleteket rnak le, klnfle megvalstsaik kdrszeit az osztlyhierarchik kzsen hasznlhatjk, s az absztrakt osztlyt hasznl kd legnagyobb rsze nem is fgg a megvalststl. A tervezs szempontjbl a kt megkzelts annyira kzeli, hogy kzs nevet rdemel. Minthogy mindkett azt teszi lehetv, hogy egy algoritmust egyszer rjunk le s aztn klnfle tpusokra alkalmazzuk, nha mindkettt tbbalaksgnak (polimorfizmus, polymorphism) hvjk. Hogy megklnbztessk ket, a virtulis fggvnyek ltal biztostottat futsi idej (run-time) tbbalaksgnak, a sablonok ltal nyjtottat pedig fordtsi idej (compile-time) tbbalaksgnak vagy paramteres (parametric) tbbalaksgnak hvjk. Vgl is mikor vlasszunk absztrakt osztlyt s mikor alkalmazzunk sablont? Mindkt esetben olyan objektumokat kezelnk, amelyeknek azonos mveleteik vannak. Ha nem szksges egymssal al- s flrendeltsgi viszonyban llniuk, akkor legyenek sablonparamterek. Ha az objektumok aktulis tpusa a fordtsi idben nem ismert, akkor legjobban egy kzs absztrakt osztlybl rkld osztlyknt brzolhatjuk azokat. Ha a futsi id nagyon fontos, azaz a mveletek helyben kifejtve trtn fordthatsga alapvet szempont, hasznljunk sablont. Errl rszletesebben a 24.4.1 pont r.

13.6.2. Tag sablonok


Egy osztlynak vagy osztlysablonnak lehetnek olyan tagjai is, amelyek maguk is sablonok:
template<class Scalar> class complex { Scalar re, im; public: template<class T> complex(const complex<T>& c) : re(c.real()), im(c.imag()) { } // ... };

Forrs: http://www.doksi.hu

458

Absztrakcis mdszerek

complex<float> cf(0,0); complex<double> cd = cf; class Quad { }; complex<Quad> cq; complex<int> ci = cq;

// rendben: float-rl double-ra alakts hasznlata // nincs talakts int-re

// hiba: nincs talakts Quad-rl int-re

Vagyis kizrlag akkor tudunk complex<T2>-bl complex<T1>-et pteni, ha T1-nek kezdrtkl adhatjuk T2-t, ami sszer megszortsnak tnik. Sajnos azonban a C++ nyelv elfogad a beptett tpusok kztti bizonyos sszertlen konverzikat is, pldul double-rl int-re. A vgrehajtsi idben a csonktsbl ered hibkat el lehetne kapni, implicit_cast (13.3.1) vagy checked (C.6.2.6.2) stlus ellenrztt konverzival:
template<class Scalar> class complex { Scalar re, im; public: complex() : re(0), im(0) { } complex(const complex<Scalar>& c) : re(c.real()), im(c.imag()) { } template<class T2> complex(const complex<T2>& c) : re(checked_cast<Scalar>(c.real())), im(checked_cast<Scalar>(c.imag())) { } // ...

};

A teljessg kedvrt alaprtelmezett s msol konstruktort is megadtam. Elg klns mdon egy sablon konstruktorbl a fordt soha nem hoz ltre msol konstruktort, gy kzvetlenl deklarlt msol konstruktor hjn a fordt alaprtelmezett konstruktort hozott volna ltre, ami ebben az esetben azonos lett volna az ltalam megadottal. Egy sablon tag nem lehet virtulis:
class Shape { // ... template<class T> virtual bool intersect(const T&) const =0; };

// hiba: virtulis sablon

Ez szablytalan. Ha megengedett lenne, akkor a virtulis fggvnyek megvalstsnak hagyomnyos virtulisfggvny-tbls mdja (2.5.5) nem lenne alkalmazhat. A szerkesztnek az intersect() fggvny minden j paramtertpussal trtn meghvsa esetn egy jabb elemmel kellene bvtenie a Shape osztly virtulisfggvny-tbljt.

Forrs: http://www.doksi.hu

13. Sablonok

459

13.6.3. rkldsi viszonyok


ltalban gy gondolnunk egy sablonra, mint j tpusok ltrehozst segt ltalnostsra. Ms szval a sablon egy olyan eszkz, amely szksg esetn a felhasznl elrsai szerinti tpusokat hoz ltre. Ezrt az osztlysablonokat nha tpusksztknek vagy tpusgenertoroknak hvjk. A C++ nyelv szablyai szerint kt, azonos osztlysablonbl ltrehozott osztly nem ll rokonsgban egymssal:
class Shape { /* ... */ }; class Circle : public Shape { /* ... */ };

Ilyenkor egyesek megprbljk a set<Circle*>-ot set<Shape*>-knt kezelni, ami hibs rvelsen nyugv slyos logikai hiba: az egy Circle egyben Shape is, gy a Circle-k halmaza egyben Shape-ek halmaza is; teht a Circle-k halmazt Shape-ek halmazaknt is kezelhetem rvels teht pontja hibs. Oka, hogy a Circle-k halmaza kizrlag Circle tpus objektumokat tartalmaz, mg a Shape-ek halmaza esetben ez egyltaln nem biztos:
class Triangle : public Shape { /* ... */ }; void f(set<Shape*>& s) { // ... s.insert(new Triangle()); // ... } void g(set<Circle*>& s) { f(s); // tpushiba: s tpusa set<Circle*>, nem set<Shape*> }

A fenti pldt a fordtprogram nem fogja lefordtani, mert nincs beptett konverzi set<Circle*>&-rl set<Shape*>&-re. Nagyon helyesen. Az a garancia, hogy a set<Circle*> elemei Circle-k, lehetv teszi, hogy az elemekre biztonsgosan s hatkonyan vgezznk Circle-kre jellemz mveleteket, pldul a sugr lekrdezst. Ha megengednnk a set<Circle*>-knek set<Shape*>-knt val kezelst, akkor ez mr nem lenne biztostott. Pldul az f() fggvny egy Triangle* elemet tesz set<Shape*> paramterbe; ha a set<Shape*> egy set<Circle*> lehetne, akkor nem lenne tbb igaz, hogy egy set<Circle*> csak Circle*-okat tartalmaz.

Forrs: http://www.doksi.hu

460

Absztrakcis mdszerek

13.6.3.1. Sablonok konverzija Az elz plda azt mutatta be, mirt nem lehet semmilyen alaprtelmezett kapcsolat kt, azonos osztlysablonbl ltrehozott osztly kztt. Egyes osztlyoknl azonban mgiscsak szeretnnk ilyen kapcsolatot kifejezni. Pldul ha egy mutat sablont ksztnk, szeretnnk tkrzni a mutatott objektumok kztti rkldsi viszonyokat. Tag sablonok (13.6.2) igny esetn sokfle hasonl kapcsolatot kifejezhetnek:
template<class T> class Ptr { T* p; public: Ptr(T*); template<class T2> operator Ptr<T2> (); // ... }; // mutat T-re

// Ptr<T> konverzija Ptr<T2>-re

Ezutn szeretnnk a konverzis opertorokat gy definilni, hogy Ptr-jeinkre fennlljanak a beptett mutatknl megszokott rkldsi kapcsolatok:
void f(Ptr<Circle> pc) { Ptr<Shape> ps = pc; Ptr<Circle> pc2 = ps; }

// mkdnie kell // elvileg hibt eredmnyez

Azt szeretnnk, hogy az els kezdrtk-ads csak akkor legyen engedlyezett, ha a Shape valban kzvetett vagy kzvetlen nyilvnos bzisosztlya a Circle-nek. ltalban gy szeretnnk a konverzis opertorokat megadni, hogy a Ptr<T>-rl Ptr<T2>-re trtn konverzi csak akkor legyen engedlyezett, ha egy T2* tpus mutat rtkl kaphat egy T* tpus mutatt. Ezt gy tehetjk meg:
template<class T> template<class T2> Ptr<T>::operator Ptr<T2> () { return Ptr<T2>(p); }

A return utastst a fordt csak akkor fogadja el, ha p (ami T* tpus) a Ptr<T2>(T2*) konstruktor paramtere lehet. Ezrt ha a T* automatikusan T2*-ra konvertlhat, a Ptr<T>rl Ptr<T2>-re trtn konverzi mkdni fog:
void f(Ptr<Circle> pc) { Ptr<Shape> ps = pc; Ptr<Circle> pc2 = ps; }

// rendben: Circle* talakthat Shape*-ra // hiba: Shape* nem alakthat Circle*-g

Forrs: http://www.doksi.hu

13. Sablonok

461

Legynk vatosak s csak logikailag rtelmes konverzikat definiljunk. Jegyezzk meg, hogy nem lehetsges egy sablonnak, illetve annak szintn sablon tagjnak sablonparamter-listit egyesteni:
template<class T, class T2> // hiba Ptr<T>::operator Ptr<T2> () { return Ptr<T2>(p); }

13.7. A forrskd szerkezete


A sablonokat hasznl kd szerkezete alapveten ktfle lehet: 1. A sablonok defincijt beptjk (#include) egy fordtsi egysgbe, mieltt hasznlnnk azokat. 2. A hasznlat eltt a sablonoknak csak a deklarcijt emeljk be, definicijukat kln fordtjuk. Ezenkvl lehetsges, hogy a sablonfggvnyeket egy fordtsi egysgen bell elszr csak deklarljuk, majd hasznljuk s csak vgl definiljuk. Hogy lssuk a ktfle megkzelts kztti klnbsget, vegynk egy egyszer pldt:
#include<iostream> template<class T> void out(const T& t) { std::cerr << t; }

Hvjuk ezt a fjlt out.c-nek s ptsk be, valahnyszor szksgnk van az out()-ra:
// user1.c: #include "out.c" // out() hasznlata // user2.c: #include "out.c" // out() hasznlata

Vagyis az out()-ot s a hozz szksges valamennyi deklarcit tbb klnbz fordtsi egysgbe is beptjk. A fordtprogram dolga, hogy csak akkor hozzon ltre kdot, ha szksges, s hogy a felesleges informcik feldolgozst optimalizlja, ami azt is jelenti, hogy a sablon fggvnyeket ugyangy kezeli, mint a helyben kifejtett (inline) fggvnyeket.

Forrs: http://www.doksi.hu

462

Absztrakcis mdszerek

Ezzel az a nyilvnval gond, hogy minden olyan informci, amelyre az out()-nak szksge van, az out()-ot felhasznl valamennyi fordtsi egysgbe belekerl, s ilyen mdon megn a fordtprogram ltal feldolgozand adat mennyisge. Egy msik gond, hogy a felhasznlk esetleg vletlenl rkapnak az eredetileg csak az out() definicijhoz szksges deklarcik hasznlatra. Ezt a veszlyt nvterek hasznlatval, a makrk hasznlatnak elkerlsvel s ltalban a beptend informci mennyisgnek cskkentsvel hrthatjuk el. E gondolatmenet logikus folyomnya a kln fordts: ha a sablon nem pl be a felhasznli kdba, azok az elemek, melyektl fgg, nem is befolysolhatjk azt. gy az eredeti out.c llomnyt kt rszre bontjuk:
// out.h: template<class T> void out(const T& t); // out.c: #include<iostream> #include "out.h" export template<class T> void out(const T& t) { std::cerr << t; }

Az out.c fjl most az out() definilshoz szksges sszes informcit tartalmazza, az out.h csak a meghvshoz szksgeset. A felhasznl csak a deklarcit (vagyis a felletet) pti be:
// user1.c: #include "out.h" // out() hasznlata // user2.c: #include "out.h" // out() hasznlata

Ezen a mdon a sablon fggvnyeket a nem helyben kifejtett fggvnyekhez hasonlan kezeljk. Az out.c-beli defincit kln fordtjuk, s az adott fordt dolga szksg esetn az out() lersnak megkeresse. Ez nmi terhet r a fordtra, hiszen a sablon-meghatrozs fls pldnyainak kiszrse helyett szksg esetn meg kell tallnia az egyetlen defincit. Jegyezzk meg, hogy a sablon defincija csak akkor rhet el ms fordtsi egysgbl, ha kifejezetten export-knt adjuk meg (9.2.3). (Ez gy trtnik, hogy a defincihoz vagy egy megelz deklarcihoz hozzadjuk az export szt.) Ha nem gy tesznk, a defincinak minden hasznlat helyrl elrhetnek kell lennie.

Forrs: http://www.doksi.hu

13. Sablonok

463

A fordt- s szerkesztprogramtl, a fejlesztett program fajtjtl, illetve a fejleszts kls feltteleitl fgg, hogy melyik mdszer vagy azok milyen prostsa a legjobb. ltalban a helyben kifejtett fggvnyeket s az egyb, alapveten ms sablon fggvnyeket hv fggvnyeket rdemes minden olyan fordtsi egysgben elhelyezni, ahol felhasznljk azokat. Egy, a sablon-pldnyosts tern tlagos tmogatst nyjt szerkesztprogram esetben ez meggyorstja a fordtst s pontosabb hibazenetekhez vezet. Ha a defincit beptjk, sebezhetv tesszk azt, mert gy rtelmt a bepts helyn rvnyes makrk s deklarcik befolysolhatjk. Ezrt a nagyobb vagy bonyolultabb fggseket ignyl sablonokat jobb kln fordttatni, de akkor is ez az eljrs kvetend, ha a sablon definicija sok deklarcit ignyel, mert ezek nem kvnatos mellkhatsokkal jrhatnak a sablon felhasznlsnak helyn. n azt tekintem idelis megoldsnak, ha a sablondefincikat kln fordtjuk, a felhasznli kdban pedig csak deklarcijukat szerepeltetjk. Ezen elvek alkalmazst azonban mindig az adott helyzethez kell igaztanunk, a sablonok kln fordtsa pedig egyes nyelvi vltozatok esetben kltsges mulatsg lehet. Brmelyik megkzeltst vlasszuk is, a nem helyben kifejtett statikus tagoknak (C.13.1) csak egyetlen definicija lehet, valamelyik fordtsi egysgben. Ebbl kvetkezen ilyen tagokat lehetleg ne hasznljunk olyan sablonoknl, amelyek sok fordtsi egysgben szerepelnek. Fontos cl, hogy a kd ugyangy mkdjk, akr egyetlen egysgben szerepel, akr kln fordtott egysgekbe elosztva. Ezt inkbb gy rhetjk el, hogy cskkentjk a definci fggst a krnyezettl, nem pedig gy, hogy a krnyezetbl minl tbbet tvisznk a pldnyosts folyamatba.

13.8. Tancsok
[1] Olyan algoritmusok lersra, amelyek sokfle paramtertpusra alkalmazhatk, sablont hasznljunk. 13.3. [2] A trolkat sablonknt ksztsk el. 13.2. [3] A trolkat specializljuk mutattpusokra, hogy cskkentsk a kd mrett. 13.5. [4] A specializci eltt mindig adjuk meg a sablon ltalnos formjt. 13.5.

Forrs: http://www.doksi.hu

464

Absztrakcis mdszerek

[5] A specializcit deklarljuk, mieltt hasznlnnk. 13.5. [6] A sablonok fggst a pldnyosts mdjtl cskkentsk a lehet legkisebbre. 13.2.5, C.13.8. [7] Definiljunk minden deklarlt specializcit. 13.5. [8] Gondoljuk meg, hogy a sablonnak nincs-e szksge C stlus karakterlncokra s tmbkre vonatkoz specializcikra. 13.5.2. [9] Paramterezznk eljrsmd objektummal. 13.4. [10] Specializci s tlterhels segtsgvel adjunk azonos felletet ugyanazon fogalom klnbz tpusokra vonatkoz megvalstsnak. 13.5. [11] Egyszer esetekre vonatkozan egyszer felletet adjunk; a ritkbb eseteket tlterhelssel s alaprtelmezett paramter-rtkekkel kezeljk. 13.5, 13.4. [12] Mieltt sablonn ltalnostannk valamit, vgezznk hibakeresst egy konkrt pldn. 13.2.1. [13] Ne felejtsk el kitenni az export kulcsszt azoknl a definciknl, amelyeket ms fordtsi egysgbl is el kell rni. 13.7. [14] A nagy vagy nem magtl rtetd krnyezeti fggsg sablonokat kln fordtsi egysgben helyezzk el. 13.7. [15] Konverzikat sablonokkal fejezznk ki, de ezeket nagyon vatosan definiljuk. 13.6.3.1. [16] Szksg esetn egy constraint() fggvny segtsgvel korltozzuk, milyen paramterei lehetnek a sablonnak. 13.9[16], C.13.10. [17] A fordtsi s sszeszerkesztsi idvel val takarkoskods cljbl explicit pldnyostst hasznljunk. C.13.10. [18] Ha a futsi id dnt szempont, rklds helyett hasznljunk sablonokat. 13.6.1. [19] Ha fontos szempont, hogy j vltozatokat jrafordts nlkl vezethessnk be, sablonok helyett hasznljunk rkldst. 13.6.1. [20] Ha nem lehet kzs bzisosztlyt megadni, rklds helyett hasznljunk sablonokat. 13.6.1. [21] Ha olyan beptett tpusokat s adatszerkezeteket kell hasznlnunk, amelyeknek a korbbi vltozatokkal sszeegyeztethetnek kell maradniuk, rklds helyett hasznljunk sablonokat. 13.6.1.

Forrs: http://www.doksi.hu

13. Sablonok

465

13.9. Gyakorlatok
1. (*2) Javtsuk ki a hibkat a List 13.2.5 pontbeli definicijban s rjuk meg a fordtprogram ltal az f() fggvny s a List szmra ltrehozott kddal egyenrtk kdot. Futtassunk le egy egyszer programot a sajt, illetve a sablonbl ltrehozott vltozat ellenrzsre. Amennyiben mdunk van r, hasonltsuk ssze a ktfle kdot. 2. (*3) rjunk osztlysablont egy egyszeresen lncolt lista szmra, amely egy Link tpusbl szrmaztatott tpus elemeket tud trolni. A Link tpus tartalmazza az elemek sszekapcsolshoz szksges informcikat. Az ilyen listt tolakod (intrusive) listnak nevezik. Ezen lista felhasznlsval rjunk egy brmilyen tpus elemeket tartalmazni kpes (azaz nem tolakod) egyszeresen lncolt listt. Hasonltsuk ssze a kt lista hatkonysgt, elnyeiket s htrnyaikat. 3. (*2.5) rjunk tolakod s nem tolakod ktszeresen lncolt listkat. Milyen mveletek szksgesek az egyszeresen lncolt lista mveletein fell? 4. (*2) Fejezzk be a 13.2 pontbeli String sablont a 11.12 pontbeli String osztly alapjn. 5. (*2) Hatrozzunk meg egy olyan sort()-ot, amely az sszehasonltsi felttelt sablonparamterknt veszi t. Hatrozzunk meg egy Record osztlyt, melynek kt tagja van, a count s a price. Rendezznk egy set<Record> halmazt mindkt tag szerint. 6. (*2) Ksztsnk egy qsort() sablont. 7. (*2) rjunk egy programot, amely (key,value) prokat olvas be s az egyes kulcsokhoz (key) tartoz rtkek (value) sszegt rja ki. Adjuk meg, milyen tpusok lehetnek kulcsok, illetve rtkek. 8. (*2.5) A 11.8 pontbeli Assoc osztly alapjn ksztsnk egy egyszer Map osztlyt. A Map mkdjk helyesen kulcsknt hasznlt C stlus karakterlncokkal s string-ekkel is, valamint akkor is, ha az alkalmazott tpusnak van alaprtelmezett konstruktora, s akkor is, ha nincs. 9. (*3) Hasonltsuk ssze a 11.8 pontbeli szszmll program hatkonysgt egy asszociatv tmbt nem hasznl programval. Azonos stlus be- s kimeneti mveleteket hasznljunk mindkt esetben. 10. (*3) rjuk t a 13.9[8]-beli Map-et valamilyen alkalmasabb adatszerkezet pldul vrs-fekete fa (red-black tree) vagy S-fa (splay tree) felhasznlsval. 11. (*2.5) Hasznljuk fel a Map-et topologikus rendezst megvalst fggvny rsra. (A topologikus rendezst a [Knuth,1987] els ktete rja le, a 280. oldaltl kezdden.) 12. (*1.5) A 13.9[7]-beli sszegz programot javtsuk ki, hogy szkzket is tartalmaz nevekre is helyesen mkdjn.

Forrs: http://www.doksi.hu

466

Absztrakcis mdszerek

13. (*2) rjunk readline() sablonokat klnfle sorfajtk szmra (pldul (cikk, szm, r)). 14. (*2) Hasznljuk a 13.4 pontbeli Literate-ben vzolt mdszert karakterlncok fordtott bcsorrend rendezsre. Gondoskodjunk rla, hogy a mdszer olyan C++-vltozatokkal is mkdjn, ahol a char eljeles (signed), s ott is, ahol unsigned. Adjunk egy, a kis- s nagybetk kztti klnbsget elhanyagol rendezst tmogat vltozatot is. 15. (*1.5) Szerkessznk egy pldt, amely legalbb hromfle eltrst mutat be egy fggvnysablon s egy makr kztt (a formai kvetelmnyek klnbsgn fell). 16. (*2) Tervezznk egy mdszert, amellyel biztosthat, hogy a fordtprogram minden olyan sablon minden paramterre ellenrizzen bizonyos megszortsokat, amelyeknek megfelel tpus objektum ltrejn a programban. Csak a T paramternek egy My_base-bl szrmaztatott osztlynak kell lennie tpus megszortsok ellenrzse nem elg!

Forrs: http://www.doksi.hu

14
Kivtelkezels
Ne szljon kzbe, amikor ppen kzbeszlok. (Winston S. Churchill) Hibakezels A kivtelek csoportostsa A kivtelek elkapsa Minden kivtel elkapsa Tovbbdobs Az erforrsok kezelse auto_ptr A kivtelek s a new opertor Az erforrsok kimerlse Kivtelek konstruktorokban Kivtelek destruktorokban Olyan kivtelek, amelyek nem hibk Kivtelek specifikcija Vratlan kivtelek El nem kapott kivtelek A kivtelek s a hatkonysg A hibakezels egyb mdjai Szabvnyos kivtelek Tancsok Gyakorlatok

14.1. Hibakezels
Ahogy a 8.3 pontban rmutattunk, egy knyvtr szerzje felfedezheti a futsi idej hibkat, de ltalban fogalma sincs rla, mit kezdjen velk. A knyvtr felhasznlja tudhatja, hogyan kell az ilyen hibkat kezelni, de nem tudja felderteni azokat msklnben a felhasznli kdban kezeln s nem a knyvtrnak kellene megtallnia ket. A kivtelek (exception) az ilyen problmk kezelst segtik. Az alaptlet az, hogy ha egy fggvny olyan hibt tall, amelyet nem tud kezelni, akkor egy kivtelt vlt ki (kivtelt dob, throw)

Forrs: http://www.doksi.hu

468

Absztrakcis mdszerek

abban a remnyben, hogy a (kzvetett vagy kzvetlen) hv kpes kezelni a problmt. Az adott problmt kezelni tud fggvnyek jelezhetik, hogy el akarjk kapni (catch) a kivtelt (2.4.2, 8.3). A hibakezels ezen mdja felveszi a versenyt a hagyomnyosabb mdszerekkel. Tekintsk t a tbbi lehetsget: ha a program szreveszi, hogy olyan problma lpett fel, amelyet nem lehet helyben megoldani, akkor 1. befejezheti a program futst, 2. egy hiba jelents rtket adhat vissza, 3. egy normlis rtket adhat vissza s a programot szablytalan llapotban hagyhatja, 4. meghvhat egy, hiba esetn meghvand fggvnyt. Alaprtelmezs szerint az els eset, azaz a program futsnak befejezse trtnik, ha olyan kivtel lp fel, amelyet nem kap el a program. A legtbb hiba kezelsre ennl jobb megolds szksges s lehetsges. Azok a knyvtrak, amelyek nem ismerik a befoglal program cljt vagy annak ltalnos mkdst, nem hajthatnak vgre egyszeren egy abort()ot vagy exit()-et. Olyan knyvtrat, amely felttel nlkl befejezi a program futst, nem hasznlhatunk olyan programban, amelynek nem szabad elszllnia. A kivtelek szerept teht gy is megfogalmazhatnnk, hogy lehetsget adnak arra, hogy a vezrls visszakerljn a hvhoz, ha a megfelel mvelet helyben nem vgezhet el. A msodik eset (hiba jelents rtk visszaadsa) nem mindig kivitelezhet, mert nem mindig ltezik elfogadhat hibt jelent rtk. Ha egy fggvny pldul int tpussal tr vissza, akkor minden int rtk hihet visszatrsi rtk lehet. De ha alkalmazhat is ez a mdszer, sokszor akkor is knyelmetlen, mert minden hvst ellenrizni kell, ami a program mrett akr ktszeresre is nvelheti (14.8). Ezrt aztn ezt a mdszert ritkn alkalmazzk annyira kvetkezetesen, hogy minden hibt szleljenek vele. A harmadik mdszer (normlis rtk visszaadsa s a program szablytalan llapotban val hagysa) azzal a gonddal jr, hogy a hv esetleg nem veszi szre, hogy a program nem megengedett llapotba kerlt. A C standard knyvtrnak szmos fggvnye pldul az errno globlis vltozt lltja be, hogy hibt jelezzen (14.8), a programok azonban jellemzen elmulasztjk az errno vltoz kellen kvetkezetes vizsglatt, ami megakadlyozn a hibs hvsok halmozdst. Ezenkvl az egyidej hozzfrsnl (konkurrencia, concurrency) a globlis vltozk hibajelzsre val hasznlata nem mkdik jl.

Forrs: http://www.doksi.hu

14. Kivtelkezels

469

A kivtelkezels nem az olyan esetek kezelsre szolgl, mint amelyekre a negyedik, a hibakezel fggvny meghvsa md alkalmas, kivtelek hjn viszont a hibakezel fggvnynek csak a tbbi hrom lehetsge van a hiba kezelsre. A hibakezel fggvnyek s a kivtelek tmjt a 14.4.5 pont trgyalja. A kivtelkezelst akkor clszer hasznlnunk a hagyomnyos mdszerek helyett, ha azok nem elgsgesek, nem elegnsak vagy hibkat okozhatnak. A kivtelek hasznlata lehetv teszi a hibakezel kdnak a kznsges kdtl val elvlasztst, ezltal a programot olvashatbb s a kdelemz eszkzk szmra kezelhetbb teszi. A kivtelkezel eljrs szablyosabb hibakezelst tesz lehetv s megknnyti a kln megrt kdrszek kztti egyttmkdst. A C++ kivtelkezelsnek a Pascal- s C-programozk szmra j vonsa, hogy a hibk (klnsen a knyvtrakban fellpett hibk) alaprtelmezett kezelse a program lelltsa. A hagyomnyos kezels az volt, hogy valahogy tevickltnk a hibn, aztn remnykedtnk. A kivtelkezels trkenyebb teszi a programot abban az rtelemben, hogy tbb gondot s figyelmet kell fordtani arra, hogy a program elfogadhatan fusson. Mindazonltal ez elnysebbnek tnik, mintha ksbb a fejleszts sorn lpnnek fel hibs eredmnyek, vagy akr a fejleszts lezrulta utn, amikor a program mr a mit sem sejt felhasznlk kezben van. Ha a lells az adott programnl elfogadhatatlan, akkor el lehet kapni az sszes kivtelt (14.3.2) vagy az sszes adott fajtjt (14.6.2), gy a kivtel csak akkor lltja le a programot, ha a programoz ezt hagyja. Ez pedig jobb, mint ha a hiba hagyomnyos mdon trtnt nem teljes kezelst kveten vgzetes hiba, majd felttel nlkli lells trtnne. Egyesek a hibkon val tevickls nem vonz tulajdonsgait hibazenetek kiratsval, a felhasznl segtsgt kr ablakokkal stb. prbltk enyhteni. Az ilyesmi fleg a program hibakeressnl (debugging, a program belvse) hasznos, amikor a felhasznl a program szerkezett ismer programoz. Nem fejlesztk szmra az esetleg jelen sem lev felhasznl/kezel segtsgt kr knyvtr elfogadhatatlan. Ezenkvl sokszor nincs is hov rtestst kldeni a hibrl (pldul ha a program olyan krnyezetben fut, ahol a cerr nem vezet a felhasznl ltal elrhet helyre), s a hibazenetek a vgfelhasznl szmra gysem mondannak semmit. Az a legkevesebb, hogy a hibazenet esetleg nem is a megfelel nyelven jelenne meg, mondjuk finnl egy angol felhasznl szmra. Ennl rosszabb, hogy a hibazenet jellemzen a knyvtr fogalmaival lenne megfogalmazva, mondjuk egy grafikus felhasznli felletrl rkezett rossz adat hatsra bad argument to atan2. Egy j knyvtr nem halandzszik gy. A kivtelek lehetv teszik az adott kdrszlet szmra, hogy a hibt, amit nem tud kezelni, a kd olyan rsze szmra tovbbtsa, amely taln boldogul vele. A programnak csak olyan rsze lehet kpes rtelmes hibazenet kldsre, amelynek van fogalma a program sszefggseirl, krnyezetrl.

Forrs: http://www.doksi.hu

470

Absztrakcis mdszerek

A kivtelkezelsre gy is tekinthetnk, mint a fordtsi idej tpusellenrzs s tbbrtelmsg-kiszrs futsi idej megfeleljre. A tervezsi folyamatra nagyobb hangslyt helyez s megnvelheti egy kezdeti (mg hibs) vltozat ellltshoz szksges munka mennyisgt. m az eredmny egy olyan kd, amelynek sokkal nagyobb az eslye arra, hogy az elvrt mdon fusson, hogy egy nagyobb program rsze lehessen, hogy ms programozk szmra is rthet legyen, hogy eszkzkkel lehessen kezelni. Ennek megfelelen a kivtelek nyelvileg tmogatott kezelse kifejezetten tmogatja a j stlus programozst, mint ahogy a C++ nyelv ms eszkzei tmogatjk azt. Ms nyelveken (C vagy Pascal) a j stlus csak egyes szablyok megkerlsvel s nem is tkletesen rhet el. Meg kell azonban jegyeznnk, hogy a hibakezels ezutn is nehz feladat marad s a kivtelkezel eljrs jllehet rendszerezettebb, mint azok a mdszerek, amelyeket helyettest a vezrlst kizrlag helyben szablyoz nyelvi elemekhez kpest kevsb hatkony. A C++ nyelv kivtelkezelse a programoznak a hibk azon helyen val kezelsre ad lehetsget, ahol ez a rendszer szerkezetbl addan a legtermszetesebb. A kivtelek nyilvnvalv teszik a hibakezels bonyolultsgt, de vigyzzunk, hogy a rossz hrrt ne annak hozjt hibztassuk. Ezen a ponton clszer jraolvasni a 8.3 pontot, amely a kivtelkezels alapvet vonsait mutatja be.

14.1.1. A kivtelek ms megkzeltsei


A kivtel (exception) azon szavak egyike, amelyek klnbz emberek szmra klnbz jelentssel brnak. A C++ nyelv kivtelkezel rendszert gy terveztk, hogy hibk s ms kivteles jelensgek kezelst tmogassa innen a neve s hogy tmogassa a hibk kezelst fggetlenl fejlesztett sszetevkbl ll programokban. A kivtelkezels csak a szinkron kivtelek, pldul a tmbindex-hibk vagy ki- s bemeneti hibk kezelsre szolgl. Az aszinkron esemnyek, mint a billentyzet fell rkez megszaktsok vagy bizonyos aritmetikai hibk nem felttlenl kivtelek s nem kzvetlenl ezzel az eljrssal kezelendk. Az aszinkron esemnyek vilgos s hatkony kezelshez a kivtelkezels itt lert mdjtl alapveten klnbz eljrsokra van szksg. Sok rendszernek vannak az aszinkronits kezelsre szolgl eljrsai (pldul szignlok (jelzsek, signal) hasznlata), de mivel ezek rendszerfggek szoktak lenni, lersuk itt nem szerepel. A kivtelkezels egy nem loklis, a (vgrehajtsi) verem visszatekersn (stack unwinding, 14.4) alapul vezrlsi szerkezet, amelyet alternatv visszatrsi eljrsknt is tekinthetnk. Ezrt kivteleket olyan esetekben is szablyosan alkalmazhatunk, amelyeknek semmi kzk a hibkhoz (14.5). A kivtelkezelsnek azonban a hibakezels s a hibatr viselkeds tmogatsa az elsdleges clja s ez a fejezet is ezekre sszpontost.

Forrs: http://www.doksi.hu

14. Kivtelkezels

471

A szabvnyos C++ nem ismeri a vgrehajtsi szl (thread) s a folyamat (process, processz) fogalmt, ezrt az egyidej hozzfrssel sszefgg kivteles helyzeteket itt nem trgyaljuk. A hasznlt rendszer dokumentcija lerja az ezeket kezel eszkzket; itt csak azt jegyzem meg, hogy a C++ nyelv kivtelkezel rendszert gy terveztk, hogy konkurens programban is hatkony legyen, feltve, hogy a programoz vagy a rendszer betart bizonyos alapvet szablyokat, pldul azt, hogy szablyosan zrolja (lock) a megosztott adatszerkezeteket a hasznlat eltt. A C++ kivtelkezel eljrsainak a hibk s kivteles esemnyek jelentse s kezelse a cljuk, de a programoznak kell eldntenie, hogy egy adott programban mi szmt kivtelesnek. Ez nem mindig knny (14.5). Tekintsnk-e kivtelesnek egy olyan esemnyt, amely a program legtbb futsakor fellp? Lehet-e egy tervezett s kezelt esemny hiba? Mindkt krdsre igen a vlasz. A kivteles nem azt jelenti, hogy szinte soha nem trtnhet meg vagy hogy vgzetes. Jobb gy rtelmezni a kivtelt, hogy a rendszer valamely rsze nem tudta megtenni, amire krtk. Szoks szerint ilyenkor valami mssal prblkozunk. Kivtelek kivltsa (dobsa) a fggvnyhvsokhoz kpest ritkn forduljon el, klnben a rendszer szerkezete ttekinthetetlen lesz. A legtbb nagy program normlis s sikeres futtatsa sorn azonban nhny kivtel kivltsa s elkapsa biztosan el fog fordulni.

14.2. A kivtelek csoportostsa


A kivtel olyan objektum, melynek osztlya valamilyen kivtel elfordulst rja le. A hibt szlel kd (ltalban egy knyvtr) eldobja (throw) az objektumot (8.3). A hibt kezelni kpes kdrszlet beavatkozsi szndkt egy elkap (catch) zradkkal jelzi. A kivtel eldobsa visszatekeri a vgrehajtsi vermet, egszen addig, amg egy megfelel (vagyis a kivtelt kivlt fggvnyt kzvetve vagy kzvetlenl meghv) fggvnyben catch-et nem tallunk. A kivtelek gyakran termszetes mdon csaldokra oszthatk. Ebbl kvetkezleg a kivtelek csoportostshoz s kezelshez az rklds hasznos segtsget nyjthat. Egy matematikai knyvtr kivteleit pldul gy csoportosthatjuk:
class Matherr { }; class Overflow: public Matherr { }; class Underflow: public Matherr { }; class Zerodivide: public Matherr { }; // ...

Forrs: http://www.doksi.hu

472

Absztrakcis mdszerek

Ez a szerkezet lehetv teszi, hogy a pontos tpusra val tekintet nlkl kezeljnk brmilyen Matherr-t:
void f() { try {

// ... } catch (Overflow) { // az Overflow (tlcsorduls) vagy ms onnan szrmaz hiba kezelse } catch (Matherr) { // nem Overflow matematikai (Matherr) hibk kezelse }

Itt az Overflow-t kln kezeltk. Az sszes tbbi Matherr kivtelt az ltalnos zradk fogja kezelni. A kivteleknek hierarchiba val szervezse fontos lehet a kd tmrsge cljbl. Gondoljuk meg pldul, hogyan lehetne a matematikai knyvtr sszes kivtelt kezelni, ha nem volnnak csoportostva. Az sszes kivtelt fel kellene sorolni:
void g() { try {

// ... } catch (Overflow) { /* ... */ } catch (Underflow) { /* ... */ } catch (Zerodivide) { /* ... */ }

Ez nemcsak fraszt, de egy kivtel knnyen ki is maradhat. Gondoljuk meg, mi lenne, ha nem csoportostannk a matematikai kivteleket. Ha a matematikai knyvtr egy j kivtellel bvlne, minden, az sszes kivtelt kezelni kvn kdrszletet mdostani kellene. ltalban a knyvtr els vltozatnak kibocstsa utn ilyen teljes kr mdostsokra nincs md. De ha van is, nem biztos, hogy minden kd a rendelkezsnkre ll, vagy ha igen, nem biztos, hogy tnyleg hajlandak vagyunk tdolgozni. Ezek az jrafordtsi s mdosthatsgi szempontok ahhoz az irnyelvhez vezetnnek, hogy az els vltozat kibocstsa utn a knyvtr nem bvlhetne tovbbi kivtelekkel; ez pedig a legtbb knyvtr szmra elfogadhatatlan. Ezrt teht a kivteleket clszer knyvtranknti vagy alrendszerenknti csoportokban megadni (14.6.2).

Forrs: http://www.doksi.hu

14. Kivtelkezels

473

Jegyezzk meg, hogy sem a beptett matematikai mveletek, sem a (C-vel kzs) alapvet matematikai knyvtr nem kivtelek formjban jelzik az aritmetikai hibkat. Ennek egyik oka, hogy szmos utastscsvet alkalmaz (pipelined) rendszer aszinkron mdon szlel bizonyos aritmetikai hibkat, pldul a nullval val osztst. A Matherr hierarchia ezrt itt csak illusztrciul szolgl. A standard knyvtrbeli kivteleket a 14.10 rja le.

14.2.1. Szrmaztatott kivtelek


Az osztlyhierarchik kivtelkezelsre val hasznlata termszetesen vezet olyan kivtelkezelkhz, amelyeket a kivtelek hordozta informcinak csak egy rsze rdekel. Vagyis egy kivtelt ltalban egy bzisosztlynak a kezelje kap el, nem sajt osztlynak kezelje. A kivtel elkapsnak s megnevezsnek mkdse a paramteres fggvnyekvel azonos. Vagyis a formlis paramter a paramter-rtkkel kap kezdrtket (7.2). Ebbl kvetkezik, hogy a kivtelnek csak az elkapott osztlynak megfelel rsze msoldik le (felszeletelds, slicing, 12.2.3):
class Matherr { // ... virtual void debug_print() const { cerr << "Matematikai hiba"; } }; class Int_overflow: public Matherr { const char* op; int a1, a2; public: Int_overflow(const char* p, int a, int b) { op = p; a1 = a; a2 = b; } virtual void debug_print() const { cerr << op << '(' << a1 << ',' << a2 << ')'; } // ... }; void f() { try {

g(); } catch (Matherr m) { // ... }

A Matherr kezelbe val belpskor az m egy Matherr objektum, mg ha g() egy Int_overflow-t vltott is ki. Kvetkezskppen az Int_overflow-ban lev tbblet informci elrhetetlen.

Forrs: http://www.doksi.hu

474

Absztrakcis mdszerek

Mint mindig, mutatk vagy referencik hasznlatval megakadlyozhatjuk az informcivesztst:


int add(int x, int y) { if ((x>0 && y>0 && x>INT_MAX-y) || (x<0 && y<0 && x<INT_MIN-y)) throw Int_overflow("+",x,y); } return x+y; // x+y nem fog tlcsordulni

void f() { try {

} catch (Matherr& m) { // ... m.debug_print(); }

int i1 = add(1,2); int i2 = add(INT_MAX,-2); int i3 = add(INT_MAX,2);

// ez az!

Az utols add() hvs kivltotta kivtel hatsra Int_overflow::debug_print() fog vgrehajtdni. Ha a kivtelt rtk s nem referencia szerint kaptuk volna el, akkor ehelyett Matherr::debug_print()-re kerlt volna sor.

14.2.2. sszetett kivtelek


Nem minden kivtelcsoport fa szerkezet. Egy kivtel gyakran kt csoportba is tartozik:
class Netfile_err : public Network_err, public File_system_err { /* ... */ };

Egy ilyen Netfile_err -t el lehet kapni a hlzati kivtelekkel trd fggvnyekben:


void f() { try {

// valami } catch(Network_err& e) { // ... }

Forrs: http://www.doksi.hu

14. Kivtelkezels

475

De akr a fjlrendszer-kivtelekkel foglalkoz fggvnyekben is:


void g() { try {

// valami ms } catch(File_system_err& e) { // ... }

A hibakezels ilyen nem hierarchikus szervezse akkor fontos, amikor a szolgltatsok (pldul a hlzati szolgltatsok) a felhasznl szmra lthatatlanok. Ebben a pldban a g() rja esetleg nem is tudott arrl, hogy hlzat is szerepet jtszik (lsd mg 14.6).

14.3. A kivtelek elkapsa


Vegyk az albbi kdrszletet:
void f() { try {

throw E(); } catch(H) { // mikor jutunk ide? }

A vezrls akkor kerl a kezelhz, ha 1. 2. 3. 4. H ugyanaz a tpus, mint E, H egyrtelm bzisosztlya E-nek, H s E mutattpusok s a mutatott tpusokra teljesl 1. vagy 2., H referencia s tpusra teljesl 1. vagy 2.

Forrs: http://www.doksi.hu

476

Absztrakcis mdszerek

Ezenkvl egy kivtel elkapsnl ugyangy alkalmazhatjuk a const minstt, mint egy fggvny paramternl. Ettl az elkaphat kivtelek tpusa nem vltozik meg, csak megakadlyozza, hogy az elkapott kivtelt mdosthassuk. Az az elv, hogy a kivtelrl kivltsakor msolat kszl, a kezel pedig az eredeti kivtel msolatt kapja meg. Lehetsges, hogy egy kivtelrl elkapsa eltt tbb msolat is kszl, ezrt nem lehet olyan kivtelt kivltani, ami nem msolhat. Az adott nyelvi vltozat nagyon sokfle mdszert alkalmazhat a kivtelek trolsra s tovbbtsra, de az biztostott, hogy a memria elfogysakor szabvnyos mdon kivltand kivtel, a bad_alloc (14.4.5) szmra van hely.

14.3.1. A kivtelek tovbbdobsa


Gyakran elfordul, hogy miutn egy kezel elkapott egy kivtelt, gy dnt, hogy nem tudja teljes egszben kezelni azt. Ilyenkor a kezel ltalban megteszi helyben, amit lehet, majd tovbbdobja a kivtelt. gy a hibt vgl is a legalkalmasabb helyen lehet kezelni. Ez akkor is igaz, ha a kivtel kezelshez szksges informci nem egyetlen helyen ll rendelkezsre s a hiba kvetkezmnyeit legjobban tbb kezel kztt elosztva kszblhetjk ki:
void h() { try {

// esetleg matematikai hibkat kivlt kd } catch (Matherr) { if (teljesen_le_tudjuk_kezelni) { // Matherr kezelse } else { return; // megtesszk, amit lehet throw; // a kivtel tovbbdobsa

A kivtel tovbbdobst az operandus nlkli throw jelzi. Ha akkor prblunk meg kivtelt tovbbdobni, ha nincs is kivtel, terminate() hvs fog bekvetkezni (14.7). A fordtprogram az ilyen esetek egy rszt de nem mindet feldertheti s figyelmeztethet rjuk.

Forrs: http://www.doksi.hu

14. Kivtelkezels

477

A tovbbdobs az eredeti kivtelre vonatkozik, nem csak annak elkapott rszre, ami Matherr-knt rendelkezsre ll. Ms szval, ha a program Int_overflow-t vltott ki, amelyet h() egy Matherr-knt kapott el s gy dnttt, hogy tovbbdobja, akkor a h() hvja is Int_overflow-t kap.

14.3.2. Minden kivtel elkapsa


Az elkapsi s tovbbdobsi mdszer egy vgletes esetvel is rdemes megismerkednnk. Mint ahogy fggvnyekre a ... tetszleges paramtert jelent (7.6), a catch(...) jelentse is a tetszleges kivtel elkapsa:
void m() { try {

// valami } catch (...) { // takarts throw; }

// minden kivtelt elkapunk

gy ha m() f rsznek vgrehajtsa sorn brmilyen kivtel lp fel, sor kerl a kezel fggvny rendrak tevkenysgre. Amint a helyi rendraks megtrtnt, az arra okot ad kivtel tovbbdobdik a tovbbi hibakezels kivltsa cljbl. A 14.6.3.2 pont r arrl, hogyan lehet informcihoz jutni a ... kezel ltal elkapott kivtelrl. A hibakezelsnek ltalban (a kivtelkezelsnek pedig klnsen) fontos szempontja a program ltal felttelezett llapot rvnyessgnek megrzse (invarins, 24.3.7.1). Pldul ha m() bizonyos mutatkat olyan llapotban hagy htra, ahogy azokat tallta, akkor a kivtelkezelbe berhatjuk a nekik elfogadhat rtket ad kdot. gy a minden kivtelt elkap kezel tetszleges invarinsok kezelsre alkalmas hely lehet. Sok fontos esetben azonban egy ilyen kivtelkezel nem a legelegnsabb megolds (lsd 14.4).

Forrs: http://www.doksi.hu

478

Absztrakcis mdszerek

14.3.2.1. A kezelk sorrendje Mivel egy szrmaztatott osztly kivtelt tbb tpus kivtel kezelje is elkaphat, a try utastsban a kezelk sorrendje lnyeges. A kezelk kiprblsra ebben a sorrendben kerl sor:
void f() { try {

// ... } catch (std::ios_base::failure) { // i/o adatfolyam hibk kezelse (14.10) } catch (std::exception& e) { // standard knyvtrbeli kivtelek kezelse (14.10) } catch (...) { // egyb kivtelek kezelse (14.3.2) }

Mivel a fordtprogram ismeri az osztlyhierarchit, sok logikai hibt kiszrhet:


void g() { try {

// ... } catch (...) { // minden kivtel kezelse (14.3.2) } catch (std::exception& e) { // standard knyvtrbeli kivtelek kezelse (14.10) } catch (std::bad_cast) { // dynamic_cast hibk kezelse (15.4.2) }

Itt az exception soha nem jut szerephez. Mg ha el is tvoltjuk a mindent elkap kezelt, a bad_cast akkor sem kerl szba, mert az exception leszrmazottja.

Forrs: http://www.doksi.hu

14. Kivtelkezels

479

14.4. Erforrsok kezelse


Amikor egy fggvny lefoglal valamilyen erforrst pldul megnyit egy fjlt, lefoglal valamennyi memrit a szabad trban, zrol valamit stb. , gyakran fontos felttel a rendszer tovbbi mkdse szempontjbl, hogy az erforrst rendben fel is szabadtsa a hvhoz val visszatrs eltt:
void use_file(const char* fn) { FILE* f = fopen(fn,"r"); // f hasznlata } fclose(f);

Ez mkdkpesnek tnik, amg szre nem vesszk, hogy ha valami hiba trtnik az fopen() meghvsa utn, de az fclose() meghvsa eltt, akkor egy kivtel miatt a use_file() fggvny az fclose() vgrehajtsa nlkl trhet vissza. Ugyanez a problma kivtelkezelst nem tmogat nyelvek esetben is fellphet. Pldul a C standard knyvtrnak longjmp() fggvnye ugyanilyen problmt okozhat. Mg egy kznsges return utasts miatt is visszatrhet a use_file() az fclose() vgrehajtsa nlkl. Egy els ksrlet a use_file() hibatrv ttelre gy nzhet ki:
void use_file(const char* fn) { FILE* f = fopen(fn,"r"); try { // f hasznlata } catch (...) { fclose(f); throw; } fclose(f); }

A fjlt hasznl kd egy try blokkban van, amely minden hibt elkap, bezrja a fjlt, majd tovbbdobja a kivtelt.

Forrs: http://www.doksi.hu

480

Absztrakcis mdszerek

Ezzel a megoldssal az a gond, hogy feleslegesen hossz, fradsgos s esetleg lass is lehet. Radsul minden tl hossz s fradsgos megolds nyomban ott jrnak a hibk, hiszen a programozk elfradnak. Szerencsre van jobb megolds. A megoldand helyzet ltalnos formjban gy nz ki:
void acquire() { // els erforrs lektse // ... // n-edik erforrs lektse // erforrsok felhasznlsa // n-edik erforrs felszabadtsa // ... // els erforrs felszabadtsa

ltalban fontos szempont, hogy az erforrsokat megszerzskkel ellenttes sorrendben szabadtsuk fel. Ez erteljesen emlkeztet a konstruktorok ltal felptett s destruktorok ltal megsemmistett helyi objektumok viselkedsre. gy aztn az ilyen erforrs-lefoglalsi s -felszabadtsi problmkat konstruktorokkal s destruktorokkal br osztlyok objektumainak hasznlatval kezelhetjk. Pldul megadhatjuk a File_ptr osztlyt, amely egy FILE*-hoz hasonlan viselkedik:
class File_ptr { FILE* p; public: File_ptr(const char* n, const char* a) { p = fopen(n,a); } File_ptr(FILE* pp) { p = pp; } ~File_ptr() { fclose(p); } }; operator FILE*() { return p; }

Egy File_ptr objektumot vagy egy FILE* mutatval, vagy az fopen()-hez szksges paramterekkel hozhatunk ltre; brmelyik esetben lettartama vgn a File_ptr objektum megsemmisl, destruktora pedig bezrja a fjlt. Programunk mrete gy minimlisra cskken:
void use_file(const char* fn) { File_ptr f(fn,"r"); // f hasznlata }

Forrs: http://www.doksi.hu

14. Kivtelkezels

481

A destruktor attl fggetlenl meghvdik, hogy a fggvnybl normlisan vagy kivtel kivltsa folytn lptnk-e ki. gy a kivtelkezel eljrs lehetv teszi, hogy a hibakezelst eltvoltsuk a f algoritmusbl. Az gy add kd egyszerbb s kevesebb hibt okoz, mint hagyomnyos megfelelje. Azt a mveletet, amikor a kivtelek kezelsekor a hvsi lncban megkeressk a megfelel kezelt, rendszerint a verem visszatekersnek hvjk. Ahogy a vermet visszatekerik, gy hvjk meg a ltrehozott loklis objektumok destruktorait.

14.4.1. Konstruktorok s destruktorok hasznlata


Az erforrsok loklis objektumok hasznlatval val kezelst szoks szerint gy hvjk, hogy kezdeti rtkads az erforrs megszerzsvel (resource acquisition is initialization). Ez az ltalnos eljrs a konstruktorok s destruktorok tulajdonsgain, valamint a kivtelkezel rendszerrel val egyttmkdskn alapul. Egy objektumot addig nem tekintnk lteznek, amg konstruktora le nem fut. Destruktora csak ebben az esetben fog a verem visszatekersekor meghvdni. Egy rszobjektumokbl ll objektumot olyan mrtkben tekintnk ltrehozottnak, amilyen mrtkben rszobjektumainak konstruktorai lefutottak, egy tmbt pedig addig az elemig, amelynek konstruktora mr lefutott. A destruktor a verem visszatekersekor csak a teljes egszben ltrehozott elemekre fut le. A konstruktor azt prblja elrni, hogy az objektum teljesen s szablyosan ltrejjjn. Ha ez nem rhet el, egy jl megrt konstruktor amennyire csak lehetsges olyan llapotban hagyja a rendszert, mint meghvsa eltt volt. Idelis esetben a nav mdon megrt konstruktorok teljestik ezt s nem hagyjk az objektumot valamilyen flig ltrehozott llapotban. Ezt a kezdeti rtkads az erforrs megszerzsvel mdszernek a tagokra val alkalmazsval rhetjk el. Vegynk egy X osztlyt, amelynek konstruktora kt erforrst ignyel: egy x llomnyt s egy y zrolst. Ezek megszerzse nem biztos, hogy sikerl, s kivtelt vlthat ki. Az X osztly konstruktornak semmilyen krlmnyek kztt nem szabad gy visszatrnie, hogy az llomnyt lefoglalta, de a zrat nem. Ezenkvl ezt anlkl kell elrni, hogy a dolog bonyolultsgval a programoznak trdnie kelljen. A megszerzett erforrsok brzolsra kt osztlyt hasznlunk, a File_ptr-t s a Lock_ptr-t. Az adott erforrs megszerzst az azt jell loklis objektum kezdeti rtkadsa brzolja:

Forrs: http://www.doksi.hu

482

Absztrakcis mdszerek

class X { File_ptr aa; Lock_ptr bb; public: X(const char* x, const char* y) : aa(x,"rw"), bb(y) {} // ... };

// 'x' lefoglalsa // 'y' lefoglalsa

Csakgy mint a loklis objektumos pldban, itt is az adott fordt dolga a szksges nyilvntartsok vezetse. A felhasznlnak nem kell ezzel trdnie. Pldul ha az aa ltrehozsa utn, de a bb- eltt kivtel vltdik ki, akkor az aa destruktornak meghvsra sor kerl, de a bb-re nem. Ebbl kvetkezleg ahol az erforrsok megszerzse ezen egyszer modell szerint trtnik, ott a konstruktor rjnak nem kell kifejezett kivtelkezel kdot rnia. Az alkalmi mdon kezelt erforrsok kztt a leggyakoribb a memria:
class Y { int* p; void init(); public: Y(int s) { p = new int[s]; init(); } ~Y() { delete[ ] p; } // ... };

A fenti mdszer hasznlata ltalnos gyakorlat s a memria elszivrgshoz (memory leak) vezethet. Ha az init()-ben kivtel keletkezik, a lefoglalt memria nem fog felszabadulni, mivel az objektum nem jtt ltre teljesen, gy destruktora nem fut le. me egy biztonsgos vltozat:
class Z { vector<int> p; void init(); public: Z(int s) : p(s) { init(); } // ... };

Forrs: http://www.doksi.hu

14. Kivtelkezels

483

A p ltal hasznlt memrit a vector kezeli. Ha az init() kivtelt vlt ki, a lefoglalt memrit p (automatikusan meghvott) destruktora fogja felszabadtani.

14.4.2. Auto_ptr
A standard knyvtr auto_ptr sablon osztlya tmogatja a kezdeti rtkads az erforrs megszerzsvel mdszert. Egy auto_ptr-nek alapveten egy mutatval adhatunk kezdrtket s ugyangy hivatkozhatunk a mutatott objektumra, mint egy hagyomnyos mutatnl. Ezenkvl amikor az auto_ptr megsemmisl, az ltala mutatott objektum is automatikusan trldik:
void f(Point p1, Point p2, auto_ptr<Circle> pc, Shape* pb) { auto_ptr<Shape> p(new Rectangle(p1,p2)); auto_ptr<Shape> pbox(pb); // kilpskor ne felejtsk // el trlni pb-t

// p tglalapra mutat

p->rotate(45); // az auto_ptr<Shape> pont gy hasznlhat, mint a Shape* // ... if (in_a_mess) throw Mess(); // ...

Itt a Rectangle, a pb ltal mutatott Shape s a pc ltal mutatott Circle egyarnt trldni fog, akr vltdott ki kivtel, akr nem. Ezen tulajdonos szerinti kezels (vagy destruktv msols) tmogatsa cljbl az auto_ptreknek a kznsges mutatktl gykeresen eltr msolsi mdszerk van: egy auto_ptrnek egy msikba val msolsa utn a forrs nem mutat semmire. Minthogy az auto_ptreket a msols megvltoztatja, konstans (const) auto_ptr-eket nem lehet msolni. Az auto_ptr sablont a <memory> fejllomny deklarlja. me egy lehetsges kifejtse:
template<class X> class std::auto_ptr { template <class Y> struct auto_ptr_ref { /* ... */ }; X* ptr; public: typedef X element_type; explicit auto_ptr(X* p =0) throw() { ptr=p; } ~auto_ptr() throw() { delete ptr; } // segdosztly

// throw() jelentse "nem vlt ki kivtelt", // lsd 14.6

Forrs: http://www.doksi.hu

484

Absztrakcis mdszerek

// figyeljk meg: msols s rtkads nem konstans paramterekkel auto_ptr(auto_ptr& a) throw(); // msol, majd a.ptr=0 template<class Y> auto_ptr(auto_ptr<Y>& a) throw(); // msol, majd a.ptr=0 auto_ptr& operator=(auto_ptr& a) throw(); // msol, majd a.ptr=0 template<class Y> auto_ptr& operator=(auto_ptr<Y>& a) throw(); // msol, majd a.ptr=0 X& operator*() const throw() { return *ptr; } X* operator->() const throw() { return ptr; } X* get() const throw() { return ptr; } X* release() throw() { X* t = ptr; ptr=0; return t; } void reset(X* p =0) throw() { if (p!=ptr) { delete ptr; ptr=p; } }

// mutat kinyerse // tulajdonosvlts

};

auto_ptr(auto_ptr_ref<X>) throw(); // msols auto_ptr_ref-bl template<class Y> operator auto_ptr_ref<Y>() throw(); // msols auto_ptr_ref-be template<class Y> operator auto_ptr<Y>() throw(); // destruktv msols auto_ptr-bl

Az auto_ptr_ref clja, hogy megvalstsa a kznsges auto_ptr szmra a destruktv msolst, ugyanakkor megakadlyozza egy konstans auto_ptr msolst. Ha egy D*-ot B*-g lehet alaktani, akkor a sablonkonstruktor s a sablon-rtkads (meghatrozott mdon vagy automatikusan) egy auto_ptr<D>-t auto_ptr<B>-v tud alaktani:
void g(Circle* pc) { auto_ptr<Circle> p2 = pc; auto_ptr<Circle> p3 = p2; p2->m = 7; Shape* ps = p3.get(); auto_ptr<Shape> aps = p3; auto_ptr<Circle> p4 = pc; }

// most p2 felel a trlsrt // most p3 felel a trlsrt (p2 mr nem) // programozi hiba: p2.get()==0 // mutat kinyerse auto_ptr-bl // tulajdonosvlts s tpuskonverzi // programozi hiba: most p4 is felel a // trlsrt

Az nem meghatrozott, mi trtnik, ha tbb auto_ptr is mutat egy objektumra, de az a legvalsznbb, hogy az objektum ktszer trldik ami hiba. Jegyezzk meg, hogy az auto_ptr destruktv msolsi mdszere miatt nem teljesti a szabvnyos trolk elemeire vagy a sort()-hoz hasonl szabvnyos eljrsokra vonatkoz kvetelmnyeket:
vector< auto_ptr<Shape> >& v; // veszlyes: auto_ptr hasznlata trolban // ... sort(v.begin(),v.end()); // Ezt ne tegyk: a rendezstl v srlhet

Forrs: http://www.doksi.hu

14. Kivtelkezels

485

Vilgos, hogy az auto_ptr nem egy ltalnos okos vagy intelligens mutat (smart pointer). De amire terveztk az automatikus mutatk kivtelbiztos kezelsre arra lnyeges kltsg nlkl megfelel.

14.4.3. Figyelmeztets
Nem minden programnak kell mindenfajta hibval szemben immunisnak lennie s nem minden erforrs annyira ltfontossg, hogy a vdelme megrje a kezdeti rtkads az erforrs megszerzsvel mdszernek, az auto_ptr-nek, illetve a catch(...) alkalmazsnak fradsgt. Pldul sok, egyszeren a bemenetet olvas s azzal le is fut programnl a slyosabb futsi idej hibkat gy is kezelhetjk, hogy egy alkalmas hibazenet kiadsa utn a programot lelltjuk. Ezzel a rendszerre bzzuk a program ltal lefoglalt sszes erforrs felszabadtst, gy a felhasznl jrafuttathatja a programot egy jobb bemenettel. Az itt lert mdszer olyan alkalmazsok szmra hasznos, amelyeknl a hibk ilyesfle egyszerstett kezelse elfogadhatatlan. Egy knyvtr tervezje pldul ltalban nem lhet feltevsekkel a knyvtrat hasznl program hibatrsi kvetelmnyeit illeten, gy aztn el kell kerlnie minden felttel nlkli futsi idej hibt s minden erforrst fel kell szabadtania, mieltt egy knyvtri fggvny visszatr. A kezdeti rtkads az erforrs megszerzsvel mdszer a kivteleknek a hibk jelzsre val felhasznlsval egytt sok ilyen knyvtr szmra megfelel.

14.4.4. A kivtelek s a new opertor


Vegyk a kvetkezt:
void f(Arena& a, X* buffer) { X* p1 = new X; X* p2 = new X[10]; X* p3 = new(buffer[10]) X; X* p4 = new(buffer[11]) X[10]; X* p5 = new(a) X; } X* p6 = new(a) X[10]; // X tmeneti trba helyezse (nem // szksges felszabadts) // trfoglals az 'a' Arena-tl (a-t kell // felszabadtani)

Forrs: http://www.doksi.hu

486

Absztrakcis mdszerek

Mi trtnik, ha X konstruktora kivtelt vlt ki? Felszabadul-e a new() opertor ltal lefoglalt memria? Kznsges esetben a vlasz igen, gy p1 s p2 kezdeti rtkadsa nem okoz memria-elszivrgst. Ha az elhelyez utastst (placement syntax, 10.4.11) hasznljuk, a vlasz nem ennyire egyszer. Az elhelyez utasts nmely felhasznlsa lefoglal nmi memrit, amit aztn fel kellene szabadtani, de nmelyik nem. Ezenkvl az elhelyez utasts alkalmazsnak ppen a memria nem szabvnyos lefoglalsa a lnyege, gy aztn jellemzen nem szabvnyos mdon is kell felszabadtani azt. Kvetkezskppen a tovbbiak a felhasznlt memriafoglaltl (alloktortl) fggnek. Ha egy Z::operator new() memriafoglal szerepelt, akkor a rendszer meghvja a Z::operator delete()-et, feltve, hogy van ilyen; ms mdon nem prbl felszabadtst vgezni. A tmbk kezelse ezzel azonos mdon trtnik (15.6.1). Ez az eljrs helyesen kezeli a standard knyvtrbeli elhelyez new opertort (10.4.11), csakgy, mint minden olyan esetet, amikor a programoz sszeill lefoglal s felszabadt fggvnyprt rt.

14.4.5. Az erforrsok kimerlse


Visszatr programozsi dilemma, hogy mi trtnjk, ha nem sikerl egy erforrs lefoglalsi ksrlete, pldul mert korbban meggondolatlanul nyitogattunk fjlokat (az fopen()nel) s foglaltunk le memrit a szabad trban (a new opertorral), anlkl, hogy trdtnk volna vele, mi van, ha nincs meg a fjl vagy hogy kifogytunk-e a szabad trbl. Ilyen problmval szembeslve a programozk ktfle megoldst szoktak alkalmazni: 1. jrakezds: krjnk segtsget valamelyik hv fggvnytl s folytassuk a program futst. 2. Befejezs: hagyjuk abba a szmtst s trjnk vissza a hvhoz. Az els esetben a hvnak fel kell kszlnie arra, hogy segtsget adjon egy ismeretlen kdrszletnek annak erforrs-lefoglal problmjban. A msodik esetben a hvnak arra kell felkszlnie, hogy kezelje az erforrs-lefoglals sikertelensgbl add problmt. A msodik eset a legtbbszr sokkal egyszerbb s a rendszer elvonatkoztatsi szintjeinek jobb sztvlasztst teszi lehetv. Jegyezzk meg, hogy a befejezs vlasztsakor nem a program futsa fejezdik be, hanem csak az adott szmts. A befejezs (termination) azon eljrs hagyomnyos neve, amely egy sikertelen szmtsbl a hv ltal adott hibakezelbe tr vissza (ami aztn jra megprblhatja a szmtst elvgeztetni), ahelyett, hogy megprbln maga orvosolni a helyzetet s a hiba fellptnek helyrl folytatni a szmtst.

Forrs: http://www.doksi.hu

14. Kivtelkezels

487

A C++-ban az jrakezd modellt a fggvnyhvsi eljrs, a befejez modellt a kivtelkezel eljrs tmogatja. Mindkettt szemllteti a standard knyvtrbeli new() opertor egy egyszer megvalstsa s felhasznlsa:
void* operator new(size_t size) { for (;;) { if (void* p = malloc(size)) return p; if (_new_handler == 0) throw bad_alloc(); _new_handler(); } }

// megprblunk memrit tallni // nincs kezel: feladjuk // segtsget krnk

Itt a C standard knyvtrnak malloc() fggvnyt hasznltam a szabad memriahely tnyleges megkeressre; az operator new() ms vltozatai ms mdokat vlaszthatnak. Ha sikerlt memrit tallni, a new() opertor visszaadhatja az arra hivatkoz mutatt; ha nem, a new() meghvja a _new_handler-t. Ha az tall a malloc() szmra lefoglalhat memrit, akkor minden rendben. Ha nem, akkor a kezel nem trhet vissza a new() opertorba vgtelen ciklus okozsa nlkl. A _new_handler ekkor kivtelt vlthat ki, ilyen mdon valamelyik hvra hagyva a helyzet tisztzst:
void my_new_handler() { int no_of_bytes_found = find_some_memory(); if (no_of_bytes_found < min_allocation) throw bad_alloc(); }

// feladjuk

Valahol lennie kell egy try blokknak is, a megfelel kivtelkezelvel:


// ... } catch (bad_alloc) { // valahogy reaglunk a memria kifogysra } try {

A new() opertor ezen vltozatban hasznlt _new_handler egy, a szabvnyos set_new_handler() fggvny ltal fenntartott fggvnymutat. Ha sajt my_new_handler()nket szeretnnk _new_handler-knt hasznlni, ezt rhatjuk:
set_new_handler(&my_new_handler);

Forrs: http://www.doksi.hu

488

Absztrakcis mdszerek

Ha el akarjuk kapni a bad_alloc kivtelt is, ezt rhatjuk:


void f() { void(*oldnh)() = set_new_handler(&my_new_handler); // ... } catch (bad_alloc) { // ... } catch (...) { set_new_handler(oldnh); throw; } } set_new_handler(oldnh); try {

// kezel jbli belltsa // tovbbdobs // kezel jbli belltsa

Mg jobb, ha a catch(...) kezelt a kezdeti rtkads az erforrs megszerzsvel mdszernek (14.4) a _new_handler-re val alkalmazsval elkerljk (14.12[1]). A _new_handler alkalmazsval a hiba szlelsnek helytl semmifle tovbbi informci nem jut el a segdfggvnyig. Knny tbb informcit kzvetteni, m minl tbb jut el belle egy futsi idej hiba szlelsnek helytl a kijavtst segt helyig, a kt kd annl inkbb fgg egymstl. Ebbl addan az egyik kdrszleten csak a msikat rtve s esetleg azt mdostva lehet vltoztatni. A klnbz helyen lev programrszek elszigetelsre j mdszer, ha az ilyen fggseket a legkisebb mrtkre szortjuk vissza. A kivtelkezel eljrs jobban tmogatja az elszigetelst, mint a hv ltal biztostott segdfggvnyek meghvsa. ltalban clszer az erforrsok megszerzst rtegekbe, elvonatkoztatsi szintekbe szervezni, s elkerlni, hogy egy rteg a hv rteg segtsgre szoruljon. Nagyobb rendszerek esetben a tapasztalat azt mutatja, hogy a sikeres rendszerek ezt az utat kvetik. A kivtelek kivltshoz szksg van egy eldoband objektumra. Az egyes C++vltozatoktl elvrjuk, hogy a memria elfogysakor is maradjon annyi tartalk, amennyi egy bad_alloc kivltshoz kell, de lehetsges, hogy valamilyen ms kivtel dobsa a memria elfogyshoz vezet.

Forrs: http://www.doksi.hu

14. Kivtelkezels

489

14.4.6. Kivtelek konstruktorokban


A kivtelek adnak megoldst arra a problmra, hogyan jelentsnk hibt egy konstruktorbl. Minthogy a konstruktorok nem adnak vissza kln visszatrsi rtket, amelyet a hv megvizsglhatna, a hagyomnyos (azaz kivteleket nem alkalmaz) lehetsgek a kvetkezk: 1. Adjunk vissza egy hibs llapot objektumot, megbzva a hvban, hogy az majd ellenrzi az llapotot. 2. lltsunk be egy nem loklis vltozt (pldul az errno-t), hogy jelezze a ltrehozs sikertelensgt, s bzzunk a felhasznlban, hogy ellenrzi a vltozt. 3. Ne vgezznk kezdeti rtkadst a konstruktorban s bzzunk meg a felhasznlban, hogy az els hasznlat eltt elvgzi azt. 4. Jelljk meg az objektumot kezdrtk nlkli-knt, s vgezze az els meghvott tagfggvny a kezdeti rtkadst. Ez a fggvny jelenti majd a hibt, ha a kezdeti rtkads nem sikerlt. A kivtelkezels lehetsget ad arra, hogy a ltrehozs sikertelensgre vonatkoz informcit a konstruktorbl tadjuk. Egy egyszer Vector osztly pldul gy vdekezhet a tlzott ignyek ellen:
class Vector { public: class Size { }; enum { max = 32000 }; Vector(int sz) { if (sz<0 || max<sz) throw Size(); // ... } }; // ...

A Vector-okat ltrehoz kd most elkaphatja a Vector::Size hibkat, s megprblhatunk valami rtelmeset kezdeni velk:
Vector* f(int i) { try { Vector* p = new Vector(i); // ... return p; }

Forrs: http://www.doksi.hu

490

Absztrakcis mdszerek

catch(Vector::Size) { // mrethiba kezelse }

Mint mindig, maga a hibakezel az alapvet hibakezel s -helyrellt mdszerek szoksos kszlett alkalmazhatja. Valahnyszor a hv egy kivtelt kap, megvltozik annak rtelmezse, hogy mi volt a hiba. Ha a kivtellel egytt a megfelel informci is tovbbtdik, a problma kezelshez rendelkezsre ll ismeretek halmaza akr bvlhet is. Ms szval, a hibakezel mdszerek alapvet clja az, hogy a hiba felfedezsnek eredeti helyrl olyan helyre jutassunk el informcit, ahol elegend ismeret ll rendelkezsre a hiba kvetkezmnyeinek elhrtshoz, s ezt radsul megbzhatan s knyelmesen tegyk. A kezdeti rtkads az erforrs megszerzsvel eljrs az egynl tbb erforrst ignybe vev konstruktorok kezelsnek legbiztosabb s legelegnsabb mdja (14.4). Ez lnyegben a sok erforrs kezelsnek problmjt az egy erforrst kezel egyszerbb eljrs ismtelt alkalmazsra vezeti vissza.

14.4.6.1. Kivtelek s a tagok kezdeti rtkadsa Mi trtnik, ha egy tag kezdeti rtkadsa kzvetlenl vagy kzvetve kivtelt vlt ki? Alaprtelmezs szerint a kivtelt az a hv fggvny kapja meg, amelyik a tag osztlynak konstruktornak meghvta, de maga a konstruktor is elkaphatja azt, ha a teljes fggvnytrzset a tag kezdrtk-listjval egytt egy try blokkba zrjuk:
class X { Vector v; // ... public: X(int); // ... }; X::X(int s) try :v(s) { // ... } catch (Vector::Size) { // ... }

// v kezdrtke s

// a v ltal kivltott kivtelt itt kapjuk el

Forrs: http://www.doksi.hu

14. Kivtelkezels

491

14.4.6.2. Kivtelek s msols Ms konstruktorokhoz hasonlan a msol konstruktor is jelezheti a hibs futst kivtel kivltsval. Ebben az esetben az objektum nem jn ltre. A vector msol konstruktora pldul memrit foglal le s tmsolja az elemi objektumokat (16,3,4,, E.3.2.), ami kivtelt vlthat ki. A kivtel kivltsa eltt a msol konstruktor fel kell szabadtsa a lefoglalt erforrsokat. Az E.2. s E.3. fggelkekben rszletesen trgyaljuk a kivtelkezels s a trolk erforrs-gazdlkodsnak kapcsolatait. A msol rtkad opertor ugyangy lefoglalhat erforrsokat s kivlthat kivtelt. Mieltt ez utbbit tenn, az rtkadsnak biztostania kell, hogy mindkt operandust azonos llapotban hagyja. Ms esetben megszegjk a standard knyvtrbeli elrsokat, melynek kvetkezmnye nem meghatrozott viselkeds lehet.

14.4.7. Kivtelek destruktorokban


A kivtelkezel eljrs szemszgbl nzve egy destruktort ktflekppen lehet meghvni: 1. Norml (szablyos) meghvs: a hatkrbl val szablyos kilpskor ((10.4.3) vagy egy delete hvs folytn (10.4.5) stb. 2. Kivtelkezels kzbeni meghvs: a verem visszatekerse kzben (14.4) a kivtelkezel eljrs elhagy egy blokkot, amely destruktorral br objektumot tartalmaz. Az utbbi esetben a kivtel nem lphet ki a destruktorbl. Ha megteszi, az a kivtelkezel eljrs hibjnak szmt, s meghvdik az std::terminate() (14.7). Vglis a kivtelkezel eljrsnak s a destruktornak ltalban nincs mdja eldnteni, hogy elfogadhat-e az egyik kivtelnek a msik kedvrt val elhanyagolsa. Ha a destruktor olyan fggvnyt hv meg, amely kivtelt vlthat ki, akkor ez ellen vdekezhet:
X::~X() try { f(); // kivtelt vlthat ki } catch (...) { // valamit csinlunk }

A standard knyvtrbeli uncaught_exception() fggvny true-val tr vissza, ha van olyan eldobott kivtel, amelyet mg nem kaptak el. Ez lehetv teszi a destruktor attl fgg programozst, hogy az objektum szablyos vagy a verem visszatekerse kzbeni megsemmistsrl van-e sz.

Forrs: http://www.doksi.hu

492

Absztrakcis mdszerek

14.5. Kivtelek, amelyek nem hibk


Ha egy kivtelre szmtunk s elkapjuk s gy annak nincs a program mkdsre nzve rossz kvetkezmnye, akkor mirt lenne hiba? Csak mert a programoz a kivtelre mint hibra s a kivtelkezel eljrsokra pedig mint hibakezel eszkzkre gondol? Nos, a kivteleket gy is tekinthetjk, mintha vezrlszerkezetek lennnek:
void f(Queue<X>& q) { try { for (;;) { X m = q.get(); // ... } } catch (Queue<X>::Empty) { return; } }

// 'Empty' kivtelt vlt ki, ha a sor res

Ez egszen jnak tnik, gy ebben az esetben tnyleg nem teljesen vilgos, mi szmt hibnak s mi nem. A kivtelkezels kevsb rendezett eljrs, mint az ifhez vagy a for-hoz hasonl helyi vezrlsi szerkezetek s azoknl gyakran kevsb hatkony is, ha a kivtelre tnylegesen sor kerl. Ezrt kivteleket csak ott hasznljunk, ahol a hagyomnyosabb vezrlsi szerkezetek nem elegnsak vagy nem hasznlhatak. A standard knyvtr pldul tartalmaz egy tetszleges elemekbl ll queue-t (sor) is, kivtelek alkalmazsa nlkl (17.3.2). A keresfggvnyeknl klnsen a rekurzv hvsokat nagymrtkben alkalmaz fggvnyeknl (pldul egy fban keres fggvnynl) j tlet befejezsknt kivtelt alkalmazni:
void fnd(Tree* p, const string& s) { if (s == p->str) throw p; if (p->left) fnd(p->left,s); if (p->right) fnd(p->right,s); } Tree* find(Tree* p, const string& s) {

// megtallta s-t

Forrs: http://www.doksi.hu

14. Kivtelkezels

493

fnd(p,s); } catch (Tree* q) { return q; } return 0;

try {

// q->str==s

Ugyanakkor a kivtelek ilyen hasznlata knnyen tlzsba vihet s ttekinthetetlen kdhoz vezethet. Ha sszer, ragaszkodjunk a kivtelkezels hibakezels elvhez. Ekkor a kd vilgosan kt rszre klnl: kznsges s hibakezel kdra. Ez rthetbb teszi a kdot. Sajnos a val vilg nem ilyen tiszta, a program szerkezete pedig ezt (bizonyos fokig kvnatos mdon) tkrzni fogja. A hibakezels lnyegnl fogva nehz. Becsljnk meg mindent, ami segt egy vilgos modellt kialaktani arrl, mi szmt hibnak s hogyan kezeljk.

14.6. Kivtelek specifikcija


A kivtel kivltsa vagy elkapsa befolysolja a fggvnynek ms fggvnyekhez val viszonyt. Ezrt rdemes lehet a fggvny deklarcijval egytt megadni azon kivteleket is, amelyeket a fggvny kivlthat.
void f(int a) throw (x2, x3);

Ha egy fggvny megadja, milyen kivtelek lphetnek fel vgrehajtsa kzben, akkor ezzel valjban garancit nyjt a hasznlinak. Ha a fggvny futsa kzben valamit olyasmit prbl tenni, ami ezt a garancit rvnytelenn tenn, akkor ez a ksrlet az std::unexpected() hvss fog talakulni. Az unexpected() alaprtelmezett jelentse std::terminate(), ami szokvnyos esetben meghvja az abort()-ot. (Rszletesen lsd a 9.4.1.1 pontban.) Valjban a
void f() throw (x2, x3) { // trzs }

Forrs: http://www.doksi.hu

494

Absztrakcis mdszerek

egyenrtk a kvetkezvel:
void f() try { // trzs } catch (x2) { throw; } catch (x3) { throw; } catch (...) { std::unexpected(); }

// tovbbdobs // tovbbdobs // az unexpected() nem fog visszatrni

Ennek legfontosabb elnye, hogy a fggvny deklarcija a hvk ltal elrhet fellet rsze. A fggvnydefincik viszont nem ltalnosan elrhetek. Ha hozz is frnk az sszes knyvtr forrshoz, hatrozottan nem szeretnk srn beljk nzegetni, radsul a megkivtel-specifikcikkal (exception specification) elltott fggvnydeklarcik sokkal rvidebbek s vilgosabbak, mint az egyenrtk kzzel rott vltozat. A kivtel-specifikcik nlkli fggvnyekrl azt kell feltteleznnk, hogy brmilyen kivtelt kivlthatnak:
int f(); // brmilyen kivtelt kivlthat

A kivtelt ki nem vlt fggvnyeket res listval adhatjuk meg:


int g() throw (); // nem vlt ki kivtelt

Azt gondolhatnnk, az lenne a j alaprtelmezs, hogy a fggvny nem vltana ki kivtelt. Ekkor azonban alapveten minden fggvny rszre szksges lenne kivteleket meghatrozni, emiatt pedig sokszor kellene jrafordtani s meg is akadlyozn a ms nyelveken rt programokkal val egyttmkdst. Ez aztn arra sztnzn a programozkat, hogy aknzzk al a kivtelkezel rendszert s hogy hibs kdot rjanak a kivtelek elfojtsra, ez pedig az aknamunkt szre nem vevknek hamis biztonsgrzetet adna.

14.6.1. A kivtelek ellenrzse


Egy felletnek fordtsi idben nem lehet minden megsrtsi ksrlett ellenrizni, de azrt fordtskor sok ellenrzs trtnik. A specifiklt (teht engedlyezett) kivteleket a fordt gy rtelmezi, hogy a fggvny azok mindegyikt ki fogja vltani. A kivtel-specifikcik fordtsi idej ellenrzsnek szablyai a knnyen felderthet hibkat tiltjk.

Forrs: http://www.doksi.hu

14. Kivtelkezels

495

Ha egy fggvny valamely deklarcija megad kivteleket is, akkor mindegyik deklarcijnak (s definicijnak is) tartalmaznia kell pontosan ugyanazokat a kivteleket:
int f() throw (std::bad_alloc); int f() { // ... } // hiba: a kivtel-meghatrozs hinyzik

Fontos szempont, hogy a kivtelek fordtsi egysgek hatrain tnyl ellenrzse nem ktelez. Termszetesen az adott nyelvi vltozat ellenrizhet gy, de a nagyobb rendszerek legtbbje szmra fontos, hogy ez ne trtnjen meg, vagy ha mgis, csak akkor jelezzen vgzetes hibt, ha a kivtel-specifikcik megsrtst nem lehet majd futsi idben elkapni. A lnyeg az, hogy j kivtellel val bvts ne knyszertse ki a kapcsold kivtelspecifikcik kijavtst s az esetleg rintett sszes kd jrafordtst. A rendszer gy egy flig feljtott llapotban tovbb mkdhet s a vratlan kivtelek dinamikus (futsi idbeli) szlelsre hagyatkozhat, ami alapvet az olyan nagy rendszereknl, ahol a nagyobb frisstsek kltsgesek s nincs is meg az sszes forrs. Egy virtulis fggvnyt csak olyan fggvnnyel lehet fellrni, amely legalbb annyira szigoran hatrozza meg a kivteleket, mint maga az eredeti fggvny:
class B { public: virtual void f(); virtual void g() throw(X,Y); virtual void h() throw(X); }; class D : public B { public: void f() throw(X); void g() throw(X); void h() throw(X,Y); };

// brmit kivlthat

// rendben // rendben: D::g() szigorbb, mint B::g() // hiba: D::h() megengedbb, mint B::h()

Ezt a szablyt a jzan sz diktlja. Ha egy szrmaztatott osztly olyan kivtelt vltana ki, amit az eredeti fggvny nem adott meg lehetsgesknt, annak elkapst nem vrhatnnk el a hvtl. Msrszt egy fellr fggvny, amely kevesebb kivtelt vlt ki, betartja a fellrt fggvny kivtel-specifikcijban fellltott szablyokat.

Forrs: http://www.doksi.hu

496

Absztrakcis mdszerek

Ugyangy egy, a kivteleket szigorbban meghatroz fggvnyt hozzrendelhetnk egy megengedbb fggvnymutathoz, de fordtva nem:
void f() throw(X); void (*pf1)() throw(X,Y) = &f; void (*pf2)() throw() = &f; // rendben // hiba: f() megengedbb, mint pf2

Kivtel-specifikci nlkli fggvnyre hivatkoz mutatt kivteleket meghatroz fggvnymutathoz klnskppen nem rendelhetnk:
void g(); // brmit kivlthat // hiba: g() megengedbb, mint pf3

void (*pf3)() throw(X) = &g;

A kivtel-specifikci nem rsze a fggvny tpusnak, a typedef-ek pedig nem is tartalmazhatnak ilyet:
typedef void (*PF)() throw(X); // hiba

14.6.2. Vratlan kivtelek


Ha a kivtelek kre specifiklt, az ott nem szerepl kivtelek az unexpected() meghvst vlthatjk ki. Az ilyen hvsok a tesztelsen kvl ltalban nem kvnatosak. A kivtelek gondos csoportostsval s a felletek megfelel meghatrozsval viszont elkerlhetk, de az unexpected() hvsokat is el lehet fogni s rtalmatlann tenni. Egy megfelelen kidolgozott Y alrendszer gyakran az Yerr osztlybl szrmaztatja a kivteleit:
class Some_Yerr : public Yerr { /* ... */ };

A fenti esetben a
void f() throw (Xerr, Yerr, exception);

minden Yerr-t tovbbtani fog a hvjnak. gy teht f() a Some_Yerr osztly hibt is tovbbtani fogja s f()-ben semmilyen Yerr nem fog unexpected() hvst kivltani. A standard knyvtr ltal kivltott sszes kivtel az exception osztly (14.10) leszrmazottja.

Forrs: http://www.doksi.hu

14. Kivtelkezels

497

14.6.3. Kivtelek lekpezse


A programot lelltani kezeletlen kivtel fellpse esetn nha tl knyrtelen eljrs. Ilyenkor az unexpected() viselkedst kell elfogadhatbb tenni. Ennek az a legegyszerbb mdja, hogy a standard knyvtrbeli std::bad_exception-t felvesszk a specifiklt kivtelek kz. Ekkor az unexpected() egyszeren egy bad_exceptiont fog kivltani egy kezelfggvny meghvsa helyett:
class X { }; class Y { }; void f() throw(X,std::bad_exception) { // ... throw Y(); // bad_exception-t vlt ki }

A kivtelkezel ekkor elkapja az elfogadhatatlan Y kivtelt s bad_exception tpust vlt ki helyette. A bad_exception-nel semmi baj, a terminate()-nl kevsb drasztikus de azrt gy is meglehetsen durva beavatkozs, radsul az informci, hogy milyen kivtel okozta a problmt, elvsz.

14.6.3.1. Kivtelek felhasznli lekpezse Vegynk egy g() fggvnyt, amely nem hlzati krnyezethez kszlt. Ttelezzk fel, hogy g() csak a sajt Y alrendszervel kapcsolatos kivtelek kivltst engedlyezi. Tegyk fel, hogy g()-t hlzati krnyezetben kell meghvnunk.
void g() throw(Yerr);

Termszetesen g() nem fog tudni semmit a hlzati kivtelekrl s az unexpected()-et fogja meghvni, ha ilyennel tallkozik. A g() hlzati krnyezetben val hasznlathoz olyan kdra van szksgnk, amely hlzati kivteleket kezel, vagy t kell rnunk g()-t. Tegyk fel, hogy az jrars nem lehetsges vagy nem kvnatos. Ekkor a problmt az unexpected() jelentsnek fellbrlsval oldhatjuk meg. A memria elfogyst a _new_handler kezeli, amelyet a set_new_handler() llt be. Ugyangy a vratlan kivtelre adott vlaszt egy _unexpected_handler hatrozza meg, amelyet az <exception> fejllomnyban megadott std::set_unexpected() llt be:

Forrs: http://www.doksi.hu

498

Absztrakcis mdszerek

typedef void(*unexpected_handler)(); unexpected_handler set_unexpected(unexpected_handler);

A vratlan kivtelek megfelel kezelshez ltre kell hoznunk egy osztlyt, hogy a kezdeti rtkads az erforrs megszerzsvel mdszert hasznlhassuk az unexpected() fggvnyekben:
class STC { // trol s visszallt unexpected_handler old; public: STC(unexpected_handler f) { old = set_unexpected(f); } ~STC() { set_unexpected(old); } };

Ezutn definilunk egy fggvnyt az unexpected() erre az esetre kvnt jelentsvel:


class Yunexpected : public Yerr { }; void throwY() throw(Yunexpected) { throw Yunexpected(); }

A throwY()-t unexpected()-knt hasznlva brmilyen kivtelbl Yunexpected lesz. Vgl elkszthetjk g()-nek egy hlzati krnyezetben alkalmazhat vltozatt:
void networked_g() throw(Yerr) { STC xx(&throwY); // az unexpected() most Yunexpected-et vlt ki g(); }

Mivel az Yunexpected az Yerr-bl szrmazik, a kivtel-specifikci nem srl. Ha a throwY() olyan kivtelt vltott volna ki, amit a specifikci nem engedlyez, akkor a terminate() hajtdott volna vgre. Azltal, hogy mentettk s visszalltottuk az _unexpected_handler-t, tbb alrendszer szmra tettk lehetv egymsra val hats nlkl a vratlan kivtelek kezelst. A vratlan kivtelek engedlyezett alaktsnak e mdszere alapveten a rendszer ltal a bad_exception-nal nyjtott szolgltats rugalmasabb vltozata.

Forrs: http://www.doksi.hu

14. Kivtelkezels

499

14.6.3.2. Kivtelek tpusnak visszalltsa A vratlan kivtelek Yunexpected-d alaktsa lehetv tenn a networked_g() felhasznljnak, hogy megtudja: egy vratlan kivtelt kpeztnk le az Yunexpected-re. Azt azonban nem fogja tudni, hogy melyik kivtel lekpezse trtnt meg. Egy egyszer eljrssal lehetv tehetjk ennek megjegyzst s tovbbtst. Pldul gy tudnnk informcit gyjteni a Network_exception-krl:
class Yunexpected : public Yerr { public: Network_exception* pe; Yunexpected(Network_exception* p) :pe(p?p->clone():0) { } ~Yunexpected() { delete p; } }; void throwY() throw(Yunexpected) { try { throw; } catch(Network_exception& p) { throw Yunexpected(&p); } catch(...) { throw Yunexpected(0); } }

// A tovbbdobott kivtelt azonnal el kell kapni! // lekpezett kivtel kivltsa

A kivtelek tovbbdobsa s elkapsa lehetv teszi, hogy azon tpusok sszes kivtelt kezeljk, amelyeket meg tudunk nevezni. A throwY() fggvnyt az unexpected() hvja meg, azt pedig elvileg egy catch(...) kezel. gy teht biztosan van tovbbdobhat kivtel. Egy unexpected() fggvny nem tekinthet el a hibtl s nem trhet vissza. Ha megprbl gy tenni, az unexpected() maga fog bad_expection-t kivltani (14.6.3). A clone() fggvnyt arra hasznljuk, hogy a kivtel egy msolata szmra helyet foglaljon a szabad memriban. Ez a msolat tlli a verem visszatekerst.

14.7. El nem kapott kivtelek


Ha egy kivtelt nem kapnak el, akkor az std::terminate() meghvsra kerl sor. A terminate() fog meghvdni akkor is, ha a kivtelkezel eljrs a vermet srltnek tallja, vagy ha egy, a verem visszatekerse sorn meghvott destruktor kivtel kivltsval prbl vget rni.

Forrs: http://www.doksi.hu

500

Absztrakcis mdszerek

A vratlan kivteleket az _unexpected_handler kezeli, amelyet az std::set_unexpected() llt be. Ehhez hasonlan, az el nem kapott kivtelek kezelst az _uncaught_handler vgzi, amelyet az <exception> fejllomnyban megadott std::set_uncaught() llt be:
typedef void(*terminate_handler)(); terminate_handler set_terminate(terminate_handler);

A visszatrsi rtk a set_terminate()-nek elzleg adott fggvny. A terminate() meghvsnak oka, hogy idnknt a kivtelkezelssel fel kell hagyni kevsb kifinomult hibakezelsi mdszerek javra. A terminate()-et pldul hasznlhatjuk arra, hogy megszaktsunk egy folyamatot vagy esetleg jraindtsunk egy rendszert (j kezdeti rtkadssal). A terminate() meghvsa drasztikus intzkeds: akkor kell hasznlni, amikor a kivtelkezel eljrs ltal megvalstott hibakezel stratgia csdt mondott s ideje a hibatrs ms szintjre ttrni. A terminate() alaprtelmezs szerint az abort()-ot hvja meg (9.4.1.1). Ez az alaprtelmezs a legtbb felhasznl szmra megfelel vlaszts, klnsen a program hibakeresse (debugging) alatt. Az _uncaught_handler fggvnyekrl a rendszer felttelezi, hogy nem fognak visszatrni a hvjukhoz. Ha az adott fggvny megprblja, a terminate() meg fogja hvni az abort()-ot. Jegyezzk meg, hogy az abort() a programbl val nem szablyos kilpst jelent. Az exit() fggvny visszatrsi rtkvel jelezhetjk a rendszernek, hogy a programbl szablyosan vagy nem szablyosan lptnk-e ki (9.4.1.1). Az adott nyelvi vltozat hatrozza meg, hogy a program futsnak el nem kapott kivtel miatti befejezdsnl a destruktorok meghvdnak-e. Bizonyos rendszereknl alapvet, hogy a destruktorok ne hvdjanak meg, hogy a program futtatst folytatni lehessen a hibakeresbl. Ms rendszerek szmra felptskbl addan szinte lehetetlen nem meghvni a destruktorokat a kivtel kezeljnek keressekor. Ha biztostani akarjuk a rendrakst egy el nem kapott kivtel esetben, a main() fggvnyben a tnylegesen elkapni kvnt kivtelek mell rhatunk egy minden kivtelt elkap kezelt is (14.3.2):
int main() try { // ... }

Forrs: http://www.doksi.hu

14. Kivtelkezels

501

catch (std::range_error) { cerr << "Tartomnyhiba: mr megint!\n"; } catch (std::bad_alloc) { cerr << "A new kifogyott a memribl.\n"; } catch (...) { // ... }

Ez a globlis vltozk konstruktorai s destruktorai ltal kivltottak kivtelvel minden kivtelt elkap. A globlis vltozk kezdeti rtkadsa alatt fellp kivteleket nem lehet elkapni. Egy nem loklis statikus objektum kezdeti rtkadsa kzbeni throw esetben csak a set_unexpected() (14.6.2) segtsgvel kaphatjuk meg a vezrlst, ami jabb ok arra, hogy lehetsg szerint kerljk a globlis vltozkat. A kivtelek kivltsnl a kivtel fellpsnek pontos helye ltalban nem ismert, vagyis kevesebb informcit kapunk, mint amit egy hibakeres (debugger) tudhat a program llapotrl. Ezrt bizonyos C++ fejlesztkrnyezetek, programok vagy fejlesztk szmra elnysebb lehet nem elkapni azokat a kivteleket, amelyek kvetkezmnyeit a programban nem kszbljk ki.

14.8. A kivtelek s a hatkonysg


Elvileg lehetsges a kivtelkezelst gy megtervezni, hogy az ne jrjon a futsi id nvekedsvel, ha nem kerl sor kivtel kivltsra. Radsul ezt gy is meg lehet tenni, hogy egy kivtel kivltsa ne legyen klnsebben kltsges egy fggvnyhvshoz kpest. Ha azonban a memriaignyt is cskkenteni szeretnnk, illetve a kivtelkezelst a C hvsi sorrendjvel s a hibakeresk szabvnyos eljrsaival is ssze szeretnnk egyeztetni, ha nem is lehetetlen, de nehz feladatra vllalkozunk de emlkezznk arra, hogy a kivtelek hasznlatnak alternatvi sincsenek ingyen. Nem szokatlan olyan hagyomnyos rendszerekkel tallkozni, amelyeknl a kd felt a hibakeressre szntk.

Forrs: http://www.doksi.hu

502

Absztrakcis mdszerek

Vegynk egy egyszer f() fggvnyt, amelynek ltszlag semmi kze nincs a kivtelkezelshez:
void g(int); void f() { string s; // ... g(1); g(2); }

m g() kivtelt vlthat ki, gy f()-nek tartalmaznia kell a kivtel fellpte esetn s-et megsemmist kdot. Ha g() nem vltott volna ki kivtelt, valamilyen ms mdon kellett volna a hibt jeleznie. gy a fentivel sszehasonlthat hagyomnyos kd, amely kivtelek helyett hibkat kezel, nem a fenti egyszer kd lenne, hanem valami ilyesmi:
bool g(int); bool f() { string s; // ... if (g(1)) if (g(2)) return true; else return false; else return false; }

A programozk szoks szerint persze nem kezelik ennyire mdszeresen a hibkat, s az nem is mindig ltfontossg. De ha gondos s mdszeres hibakezels szksges, akkor jobb azt a szmtgpre, vagyis a kivtelkezel rendszerre hagyni. A kivtelek specifikcija (14.6) nagyon hasznos lehet a fordt ltal ltrehozott kd javtsra. Ha az albbi mdon kijelentettk volna, hogy g() nem vlt ki kivtelt, akkor az f() szmra ltrehozott kdon javthattunk volna:
void g(int) throw();

rdemes megjegyezni, hogy a hagyomnyos C fggvnyek nem vltanak ki kivtelt, gy a legtbb programban az sszes C fggvny az res throw() kivtel-meghatrozssal adhat meg. Az adott nyelvi vltozat tudhatja, hogy a C-nek csak nhny standard knyvtrbeli fggvnye vlt ki kivtelt, pldul az atexit() s a qsort(), s ennek ismeretben jobb kdot kszthet.

Forrs: http://www.doksi.hu

14. Kivtelkezels

503

Mieltt egy C fggvnyt elltnnk az res kivtel-meghatrozssal (a throw()-val), gondoljuk meg, nem vlthat-e ki kivtelt. Pldul trhattk, hogy a new opertort hasznlja, amely viszont bad_alloc-ot vlthat ki vagy olyan C++-knyvtrat hvhat meg, amely ugyanezt teheti.

14.9. A hibakezels egyb mdjai


A kivtelkezel eljrs clja, hogy lehetv tegye egy programrsz szmra, hogy a program ms rszeit rtestse egy kivteles krlmny szlelsrl. A feltevs az, hogy a kt rsz fggetlenl kszlt s a kivtelt kezel rsz tud valami rtelmeset kezdeni a hibval. A kivtelkezelk hatkony hasznlathoz tfog stratgira van szksgnk. Vagyis a program klnbz rszeinek egyet kell rtenik abban, hogyan hasznljk a kivteleket s hol kezelik a hibkat. A kivtelkezel eljrs lnyegnl fogva nem loklis, gy alapvet jelentsg, hogy tfog stratgia rvnyesljn. Ebbl kvetkezik, hogy a hibakezels mdjra legjobb a rendszer tervezsnek legkorbbi szakaszban gondolni, s hogy az alkalmazott mdszernek (a teljes program sszetettsghez kpest) egyszernek s pontosan meghatrozottnak kell lennie. Egy, a lnyegnl fogva annyira knyes terleten, mint a hibakezels, egy bonyolult mdszerhez nem tudnnk kvetkezetesen alkalmazkodni. Elszr is fel kell adni azt a tvhitet, hogy az sszes hibt egyetlen eljrssal kezelhetjk; ez csak bonyoltan a helyzetet. A sikeres hibatr rendszerek tbbszintek. Mindegyik szint annyi hibval birkzik meg, amennyivel csak tud, anlkl, hogy tlsgosan megszenvedne vele, a maradk kezelst viszont a magasabb szintekre hagyja. A terminate() ezt a kezelsi mdot azzal tmogatja, hogy meneklsi utat hagy arra az esetre, ha maga a kivtelkezel rendszer srlne vagy nem tkletes hasznlata miatt maradnnak nem elkapott kivtelek. Az unexpected() clja ugyangy az, hogy meneklsi utat hagyjon arra az esetre, ha a kivtelek specifikcijra pl hibakezel rendszer mgsem jelentene a kivtelek szmra thatolhatatlan falat. Nem minden fggvny kell, hogy tzfalknt viselkedjen. A legtbb rendszerben nem lehet minden fggvnyt gy megrni, hogy vagy teljes sikerrel jrjon vagy egy pontosan meghatrozott mdon legyen sikertelen. Hogy ez mirt nem lehetsges, az programrl programra s programozrl programozra vltozik. Nagyobb programok esetben a kvetkez okokat sorolhatjuk fel:

Forrs: http://www.doksi.hu

504

Absztrakcis mdszerek

1. Tl sok munka kellene ennek a megbzhatsgnak olyan biztostshoz, hogy kvetkezetesen mindenhol rvnyesljn. 2. A szksges trterlet s futsi id nvekedse nagyobb lenne az elfogadhatnl (mert rendszeresen ugyanazon hibk pldul rvnytelen paramterek ismtelt ellenrzsre kerl sor). 3. Ms nyelven rt fggvnyek nem alkalmazkodnnak a szablyokhoz. 4. Ez a pusztn helyi rtelemben vett megbzhatsg olyan bonyolultsghoz vezetne, amely vgl alsn a teljes rendszer megbzhatsgt. Ugyanakkor egy program olyan klnll rszrendszerekre bontsa, amelyek vagy teljes sikerrel jrnak, vagy meghatrozott mdon lesznek sikertelenek, alapvet, megtehet s gazdasgos. Egy fbb knyvtrat, rszrendszert vagy kulcsfontossg fggvnyt gy kell megtervezni. A kivteleket ilyen knyvtrak vagy rszrendszerek felletei szmra clszer meghatrozni. Rendszerint nem adatik meg az a luxus, hogy egy rendszer sszes kdjt a nullrl kszthessk el. Ezrt egy ltalnos, minden programrszre kiterjed hibakezelsi stratgia megalkotsakor figyelembe kell vennnk az egyes, a minktl eltr mdszert alkalmaz programrszleteket is. Ehhez pedig tekintetbe kell vennnk olyan krdseket, mint hogy a programrszlet hogyan kezeli az erforrsokat, vagy milyen llapotba kerl egy hibja utn a rendszer. Az a cl, hogy a programrszlet ltszlag akkor is az ltalnos hibakezelsi mdszer szerint kezelje a hibkat, ha valjban eltr bels eljrst alkalmaz. Alkalmasint szksges lehet az egyik stlus hibajelzsrl a msikra ttrni. Pldul egy C knyvtri fggvny meghvsa utn ellenrizhetjk az errno-t s kivtelt vlthatunk ki, vagy fordtva, elkaphatunk egy kivtelt s az errno-t bellthatjuk, mieltt a C++-knyvtrbl visszatrnk egy C programba:
void callC() throw(C_blewit) { errno = 0; c_function(); if (errno) { // takarts (ha lehetsges s szksges) throw C_blewit(errno); } } extern "C" void call_from_C() throw() {

Forrs: http://www.doksi.hu

14. Kivtelkezels

505

try {

c_plus_plus_function(); } catch (...) { // takarts (ha lehetsges s szksges) errno = E_CPLPLFCTBLEWIT; }

Ilyenkor fontos, hogy kvetkezetesen jrjunk el, hogy a hibajelent stlusok talaktsa teljes legyen. A hibakezels a lehetsgekhez mrten hierarchikus legyen. Ha egy fggvny futsi idej hibt szlel, ne krjen segtsget vagy erforrst a hvjtl. Az ilyen krsek az egymstl fgg elemek kztt krbe-krbe jr vgtelen ciklusokat okoznak, ami a programot ttekinthetetlenn teszi, a vgtelen ciklusok lehetsgvel pedig a hibkat kezel kdba egy nem kvnatos lehetsget pt. Alkalmazzunk olyan egyszerst mdszereket, mint a kezdeti rtkads az erforrs megszerzsvel, illetve olyan egyszerst feltevseket, mint a kivtelek hibkat jelentenek. Ezzel a hibakezel kdot szablyosabb tehetjk. Lsd mg a 24.3.7.1 pontot, ahol elmagyarzzuk, hogyan lehet llapotbiztostkat (invarinsokat) s feltevseket hasznlni, hogy a kivtelek kivltsa szablyosabb legyen.

14.10. Szabvnyos kivtelek


A kvetkez tblzat a szabvnyos kivteleket s az azokat kivlt fggvnyeket, opertorokat, ltalnos eszkzket mutatja be:

Szabvnyos (a nyelvbe beptett) kivtelek Nv bad_alloc bad_cast bad_typeid bad_exception Kivlt new dynamic_cast typeid kivtel_specifikci Hivatkozs 6.2.6.2, 19.4.5 15.4.1.1 15.4.4 14.6.3 Fejllomny <new> <typeinfo> <typeinfo> <exception>

Forrs: http://www.doksi.hu

506

Absztrakcis mdszerek

Szabvnyos (a standard knyvtr ltal kivltott) kivtelek Nv out_of_range Ki dobja Hivatkozs 16.3.3,20.3.3 17.5.3 17.5.3.1 17.5.3.3 21.3.6 Fejllomny <stdexcept> <stdexcept> <stdexcept> <stdexcept> <ios>

at() bitset<>::operator[ ]() invalid_argument bitset konstruktor overflow_error bitset<>::to_ulong() ios_base::failure ios_base::clear()

A knyvtri kivtelek a standard knyvtr exception nev az <exception> fejllomnyban megadott kivtelosztlybl kiindul osztlyhierarchia tagjai:
class exception { public: exception() throw(); exception(const exception&) throw(); exception& operator=(const exception&) throw(); virtual ~exception() throw(); virtual const char* what() const throw(); private: // ... };

A hierarchia gy nz ki: exception

logic_error

runtime_error

lenght_error domain_error out_of_range invalid_argument bad_alloc bad_exception bad_cast bad_typeid range_error overflow_error underflow_error

ios_base::failure

Forrs: http://www.doksi.hu

14. Kivtelkezels

507

Ez meglehetsen krlmnyesnek tnik ahhoz kpest, hogy nyolc szabvnyos kivtelt lel fel. Oka, hogy a hierarchia megprbl a standard knyvtrban meghatrozott kivteleken kvli kivtelek szmra is hasznlhat keretrendszert adni. A logikai hibk (logic error) azok, amelyeket elvileg a program indulsa eltt, fggvny- vagy konstruktorparamterek ellenrzsvel el lehetne kapni. A tbbi futsi idej hiba (run-time error). Nmelyek ezt az sszes hiba s kivtel szmra hasznos keretrendszernek ltjk n nem. A standard knyvtrbeli kivtelek az exception ltal meghatrozott fggvnyeket nem bvtik jakkal, csak megfelelen definiljk a megkvnt virtulis fggvnyeket. gy az albbit rhatjuk:
void f() try { // a standard knyvtr hasznlata } catch (exception& e) { cout << "standard knyvtri kivtel " << e.what() << '\n'; // ... } catch (...) { cout << "msik kivtel\n"; // ... }

// taln

A standard knyvtrbeli kivtelek az exception-bl szrmaznak, a tbbi viszont nem felttlenl, gy hiba lenne az sszes kivtelt az exception elkapsval megprblni lekezelni. Hasonlan hiba lenne felttelezni, hogy az sszes, az exception-bl szrmaz kivtel standard knyvtrbeli kivtel, a felhasznlk ugyanis hozzadhatjk sajt kivteleiket az exception hierarchihoz. Jegyezzk meg, hogy az expection-mveletek maguk nem vltanak ki kivtelt. Ebbl kvetkezen egy standard knyvtrbeli kivtel kivltsa nem vlt ki bad_alloc kivtelt. A kivtelkezel rendszer fenntart a sajt cljaira egy kis memrit (pldul a veremben), hogy ott trolhassa a kivteleket. Termszetesen rhatunk olyan kdot, amely a rendszer sszes memrijt elfogyasztja s gy hibt knyszert ki. me egy fggvny, amit ha meghvunk, kiprblja, hogy a fggvnyhvsnl vagy a kivtelkezelsnl fogy-e ki elszr a memria:
void perverted() { try { throw exception(); }

// ismtld kivtel

Forrs: http://www.doksi.hu

508

Absztrakcis mdszerek

catch (exception& e) { perverted(); cout << e.what(); }

// ismtld fggvnyhvs

A kimeneti utasts egyedl azt a clt szolglja, hogy megakadlyozza a fordtprogramot az e nev kivtel ltal felhasznlt memria jrahasznostsban.

14.11. Tancsok
[1] Hibakezelsre hasznljunk kivteleket. 14.1, 14.5, 14.9. [2] Ne hasznljunk kivteleket, ha a helyi vezrlsi szerkezetek elgsgesek. 14.1. [3] Az erforrsok kezelsre a kezdeti rtkads az erforrs megszerzsvel mdszert hasznljuk. 14.4. [4] Nem minden programnak kell kivtelbiztosnak lennie. 14.4.3. [5] Az invarinsok rvnyessgnek megrzsre hasznljuk a kezdeti rtkads az erforrs megszerzsvel mdszert s a kivtelkezelket. 14.3.2. [6] Minl kevesebb try blokkot hasznljunk. Meghatrozott kezelkd helyett a kezdeti rtkads az erforrs megszerzsvel mdszert hasznljuk. 14.4. [7] Nem minden fggvnynek kell minden lehetsges hibt kezelnie. 14.9. [8] A konstruktorhibk jelzsre vltsunk ki kivtelt. 14.9. [9] Ha egy rtkads vlt ki kivtelt, eltte gondoskodjon paramtereinek kvetkezetes llapotba hozsrl. 14.4.6.2. [10] A destruktorokban kerljk a kivtelek kivltst. 14.4.7. [11] A main() fggvny kapja el s jelentse az sszes hibt. 14.7. [12] Klntsk el a kznsges kdot a hibakezelstl. 14.4.5 s 14.5. [13] Gondoskodjunk arrl, hogy egy konstruktorban minden lefoglalt erforrst felszabadtsunk, ha a konstruktor kivtelt vlt ki. 14.4. [14] Az erforrsok kezelse hierarchikus legyen. 14.4. [15] A fbb felletekben hatrozzuk meg az engedlyezett kivteleket. 14.9. [16] Legynk rsen, nehogy a new ltal lefoglalt s a kivtelek fellpte esetn fel nem szabadtott memria elszivrogjon. 14.4.1, 14.4.2, 14.4.4. [17] A fggvnyekrl ttelezzk fel, hogy minden kivtelt kivlthatnak, amit megengedtek nekik. 14.6. [18] Ne ttelezzk fel, hogy minden kivtel az exception osztly leszrmazottja. 14.10. [19] Egy knyvtr ne fejezze be egyoldalan a program futst. Ehelyett vltson ki kivtelt s hadd dntsn a hv. 14.1.

Forrs: http://www.doksi.hu

14. Kivtelkezels

509

[20] Egy knyvtr ne bocssson ki a vgfelhasznlnak sznt diagnosztikai zeneteket. Ehelyett vltson ki kivtelt s hadd dntsn a hv. 14.1. [21] A tervezs sorn minl hamarabb alaktsuk ki a hibakezelsi stratgit. 14.9

14.12. Gyakorlatok
1. (*2) ltalnostsuk a 14.6.3.1 pontbeli STC osztlyt sablonn, amely a kezdeti rtkads az erforrs megszerzsvel mdszert hasznlja klnfle tpus fggvnyek trolsra s visszalltsra. 2. (*3) Egsztsk ki a 11.11 pontbeli Ptr_to_T osztlyt mint egy olyan sablont, amely kivtelekkel jelzi a futsi idej hibkat. 3. (*3) rjunk fggvnyt, amely egy binris fa cscsaiban keres egy char* tpus mez alapjn. A hello-t tartalmaz cscs megtallsakor a find(hello) a cscsra hivatkoz mutatval trjen vissza; a keress sikertelensgt kivtellel jelezzk. 4. (*3.) Hozzunk ltre egy Int osztlyt, amely pontosan gy viselkedik, mint az elemi int tpus, csak tl- s alulcsorduls esetn kivtelt vlt ki. 5. (*2.5) Vegyk a felhasznlt opercis rendszer C felletnek llomnyok megnyitsra, lezrsra, olvassra, rsra val alapvet mveleteit, s rjunk hozzjuk egyenrtk C++ fggvnyeket, amelyek a megfelel C fggvnyeket hvjk meg, de hiba esetn kivtelt vltanak ki. 6. (*2.5) rjunk egy teljes Vector sablont Range s Size kivtelekkel. 7. (*1) rjunk egy ciklust, ami kiszmtja a 14.12[6]-beli Vector sszegt a Vector mretnek lekrdezse nlkl. Ez mirt nem j tlet? 8. (*2.5) Gondoljuk meg, mi lenne, ha egy Exception osztlyt hasznlnnk az sszes kivtelknt hasznlt osztly seknt (bzisosztlyaknt). Hogyan nzne ki? Hogyan lehetne hasznlni? Mire lenne j? Milyen htrnyok szrmaznnak abbl a megktsbl, hogy ezt az osztlyt kell hasznlni? 9. (*1) Adott a kvetkez fggvny:
int main() { /* ... */ }

Vltoztassuk meg gy, hogy minden kivtelt elkapjon, hibazenett alaktsa azokat, majd meghvja az abort()-ot. Vigyzat: a 14.9 pontbeli call_from_C() fggvny nem teljesen kezel minden esetet. 10. (*2) rjunk visszahvsok (callback) megvalstsra alkalmas osztlyt vagy sablont. 11. (*2.5) rjunk egy Lock osztlyt, amely valamely rendszer szmra a konkurens hozzfrst tmogatja.

Forrs: http://www.doksi.hu

15
Osztlyhierarchik
Az absztrakci szelektv tudatlansg. (Andrew Koenig) Tbbszrs rklds A tbbrtelmsg feloldsa rklds s using deklarcik Ismtld bzisosztlyok Virtulis bzisosztlyok A tbbszrs rklds hasznlata Hozzfrs-szablyozs Vdett tagok Bzisosztlyok elrse Futsi idej tpusinformci dynamic_cast Statikus s dinamikus konverzi typeid Kiterjesztett tpusinformci A futsi idej tpusinformci helyes s helytelen hasznlata Tagra hivatkoz mutatk Szabad tr Virtulis konstruktorok Tancsok Gyakorlatok

15.1. Bevezets s ttekints


Ez a fejezet a szrmaztatott osztlyoknak s a virtulis fggvnyeknek a ms nyelvi elemekkel, pldul a hozzfrs-szablyozssal, a nvfeloldssal, a szabad tr kezelsvel, a konstruktorokkal, a mutatkkal s a tpuskonverzikkal val klcsnhatst trgyalja. t f rszbl ll: 15.2 Tbbszrs rklds 15.3 Hozzfrs-szablyozs

Forrs: http://www.doksi.hu

512

Absztrakcis mdszerek

15.4 Futsi idej tpusazonosts 15.5 Tagokra hivatkoz mutatk 15.6 A szabad tr hasznlata Az osztlyokat ltalban bzisosztlyok hljbl hozzuk ltre. Mivel a legtbb ilyen hl hagyomnyosan fa szerkezet, az osztlyokbl ll hlkat vagy osztlyhlkat (class lattice) gyakran nevezik osztlyhierarchinak (class hierarchy) is. Az osztlyokat clszer gy megtervezni, hogy a felhasznlknak ne kelljen indokolatlan mrtkben azzal trdnik, hogy egy osztly milyen mdon pl fel ms osztlyokbl. gy pldul a virtulis fggvnyek meghvsi mdja biztostja, hogy ha egy f() fggvnyt meghvunk egy objektumra, akkor mindig ugyanaz a fggvny hajtdik vgre, fggetlenl attl, hogy a hierarchia melyik osztlya deklarlta a fggvnyt a meghvshoz. Ez a fejezet az osztlyhlk sszelltsnak s az osztlytagok elrsnek mdjairl, illetve az osztlyhlk fordtsi s futsi idej bejrsnak eszkzeirl szl.

15.2. Tbbszrs rklds


Ahogy a 2.5.4 s 12.3 pontokban lttuk, egy osztlynak tbb kzvetlen bzisosztlya is lehet, azaz tbb osztlyt is megadhatunk a : jel utn az osztly deklarcijban. Vegynk egy szimulcis programot, ahol a prhuzamos tevkenysgeket a Task (Feladat) osztllyal, az adatgyjtst s -megjelentst pedig a Displayed (Megjelents) osztllyal brzoljuk. Ekkor olyan szimullt egyedeket hatrozhatunk meg, mint a Satellite (Mhold):
class Satellite : public Task, public Displayed { // ... };

Tbb kzvetlen bzisosztly hasznlatt szoks szerint tbbszrs rkldsnek (multiple inheritance) nevezik. Az egyszeres rkldsnl (single inheritance) csak egy kzvetlen bzisosztly van. A Satellite-okra sajt mveleteiken kvl a Task-ok s Displayed-ek mveleteinek unija is alkalmazhat:
void f(Satellite& { s.draw(); s.delay(10); s.transmit(); } s) // Displayed::draw() // Task::delay() // Satellite::transmit()

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

513

Hasonlan, ha egy fggvny Task vagy Displayed paramtert vr, akkor adhatunk neki egy Satellite-ot is:
void highlight(Displayed*); void suspend(Task*); void g(Satellite* p) { highlight(p); // mutat tadsa a Satellite Displayed rszre suspend(p); // mutat tadsa a Satellite Task rszre }

A program ltrehozsa nyilvn valamilyen egyszer eljrs alkalmazst kveteli meg a fordtprogramtl, hogy a Task-ot vr fggvnyek ms rszt lssk egy Satellite-nak, mint a Displayed-et vrk. A virtulis fggvnyek a megszokott mdon mkdnek:
class Task { // ... virtual void pending() = 0; }; class Displayed { // ... virtual void draw() = 0; }; class Satellite : public Task, public Displayed { // ... void pending(); // fellrja a Task::pending() fggvnyt void draw(); // fellrja a Displayed::draw() fggvnyt };

Ez biztostja, hogy Satellite::draw(), illetve Satellite::pending() fog meghvdni, ha egy Satellite-ot Displayed-knt, illetve Task-knt kezelnk. Jegyezzk meg, hogy ha csak egyszeres rkldst hasznlhatnnk, akkor ez a krlmny korltozn a programozt a Satellite, Displayed s Task osztlyok megvalstsnak megvlasztsban. Egy Satellite vagy Task, vagy Displayed lehetne, de nem mindkett (hacsak a Task nem a Displayed-bl szrmazik vagy fordtva). Ezen lehetsgek mindegyike cskkenti a rugalmassgot. Mi szksge lehet brkinek egy Satellite osztlyra? Nos, brmilyen meglep, a Satellite pldt a valsgbl mertettk. Volt s taln mg mindig van egy olyan program, amely a tbbszrs rklds lersra itt hasznlt minta szerint plt fel. A program mholdakat,

Forrs: http://www.doksi.hu

514

Absztrakcis mdszerek

fldi llomsokat stb. magba foglal hrkzlsi rendszerek szerkezetnek tanulmnyozsra szolglt. Egy ilyen szimulci birtokban meg tudjuk vlaszolni a forgalmi adatokra vonatkoz krdseket, meg tudjuk hatrozni, mi trtnik, ha egy fldi llomst vihar akadlyoz, mholdas s fldi kapcsolatok elnyeit-htrnyait tudjuk elemezni stb. A klnbz krlmnyek utnzsa szmos megjelentsi s hibakeressi mveletet ignyel, s szksg van a Satellite-okhoz hasonl objektumok, illetve rszegysgeik llapotnak trolsra is, az elemzs, illetve a hibakeress s -elhrts cljbl.

15.2.1. A tbbrtelmsg feloldsa


Kt bzisosztlynak lehetnek azonos nev tagfggvnyei is:
class Task { // ... virtual debug_info* get_debug(); }; class Displayed { // ... virtual debug_info* get_debug(); };

Ha egy Satellite-ot hasznlunk, akkor ezeket a fggvnyeket egyrtelmen kell megneveznnk:


void f(Satellite* sp) { debug_info* dip = sp->get_debug(); dip = sp->Task::get_debug(); dip = sp->Displayed::get_debug(); }

// hiba: tbbrtelm // rendben // rendben

Az explicit megnevezs azonban zavar, ezrt ltalban legjobb elkerlni az ilyen problmkat. Ennek legegyszerbb mdja, ha a szrmaztatott osztlyban ksztnk egy j fggvnyt:
class Satellite : public Task, public Displayed { // ... debug_info* get_debug() { // fellrja a Task::get_debug() s // Displayed::get_debug() fggvnyeket

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

515

};

debug_info* dip1 = Task::get_debug(); debug_info* dip2 = Displayed::get_debug(); return dip1->merge(dip2);

Ezltal a Satellite bzisosztlyaira vonatkoz informcik felhasznlst helyhez ktttk. Mivel a Satellite::get_debug() elfedi mindkt bzisosztlynak get_debug() fggvnyt, a Satellite::get_debug() hvdik meg, valahnyszor get_debug()-ot hvunk meg egy Satellite objektumra. A Telstar::draw minstett nv a Telstar-ban vagy valamelyik bzisosztlyban megadott draw-ra vonatkozhat:
class Telstar : public Satellite { // ... void draw() { draw(); Satellite::draw(); Displayed::draw(); Satellite::Displayed::draw(); } };

// hopp!: rekurzv hvs // megtallja a Displayed::draw-t // felesleges ktszeri minsts

Vagyis ha a Satellite::draw nem a Satellite osztlyban bevezetett draw-t jelenti, akkor a fordtprogram vgignzi a bzisosztlyokat, vagyis Task::draw-t s Displayed::draw-t keres. Ha csak egyet tall, akkor azt fogja hasznlni, ha tbbet vagy egyet sem, a Satellite::draw ismeretlen vagy tbbrtelm lesz.

15.2.2. rklds s using deklarcik


A tlterhels feloldsra nem kerl sor klnbz osztlyok hatkrn keresztl (7.4), a klnbz bzisosztlyok fggvnyei kztti tbbrtelmsgek feloldsa pedig nem trtnik meg a paramtertpus alapjn. Egymssal alapveten nem rokon osztlyok egyestsekor, pldul a Task s Displayed osztlyok Satellite-t val sszegyrsnl az elnevezsekben megmutatkoz hasonlsg ltalban nem jelent kzs clt. Amikor az ilyen nevek tkznek, ez gyakran meglepetsknt ri a felhasznlt:

Forrs: http://www.doksi.hu

516

Absztrakcis mdszerek

class Task { // ... void debug(double p); }; class Displayed { // ... void debug(int v); };

// informci kirsa csak akkor, ha a priorits // alacsonyabb p-nl

// minl nagyobb 'v,' annl tbb hibakeressi informci rdik ki

class Satellite : public Task, public Displayed { // ... }; void g(Satellite* p) { p->debug(1); p->Task::debug(1); p->Displayed::debug(1);

// hiba, tbbrtelm: Displayed::debug(int) vagy // Task::debug(double) ? // rendben // rendben

De mi van akkor, ha a klnbz bzisosztlyokban tudatos tervezsi dnts kvetkeztben szerepelnek azonos nevek, s a felhasznl szmra kvnatos lenne a paramtertpus alapjn vlasztani kzlk? Ebben az esetben a fggvnyeket a using deklarcival (8.2.2) hozhatjuk kzs hatkrbe:
class A { public: int f(int); char f(char); // ... }; class B { public: double f(double); // ... }; class AB: public A, public B { public: using A::f; using B::f; char f(char); // elfedi A::f(char)-t AB f(AB); };

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

517

void g(AB& ab) { ab.f(1); ab.f('a'); ab.f(2.0); ab.f(ab); }

// A::f(int) // AB::f(char) // B::f(double) // AB::f(AB)

A using deklarcik lehetv teszik, hogy a bzis- s szrmaztatott osztlyok fggvnyeibl tlterhelt fggvnyek halmazt lltsuk el. A szrmaztatott osztlyban megadott fggvnyek elfedik (hide) a bzisosztly fggvnyeit, amelyek egybknt elrhetek lennnek. A bzisosztly virtulis fggvnyei ugyangy fellrhatk (override), mint egybknt (15.2.3.1). Egy osztlydeklarci using deklarcijnak (8.2.2) egy bzisosztly tagjra kell vonatkoznia. Egy osztly tagjra vonatkoz using deklarci nem szerepelhet az osztlyon, annak szrmaztatott osztlyn, illetve annak tagfggvnyein kvl, a using direktvk (8.2.3) pedig nem szerepelhetnek egy osztly definicijban s nem vonatkozhatnak osztlyra. A using deklarcik nem szolglhatnak kiegszt informci elrsre sem, csak az egybknt is hozzfrhet informcik knyelmesebb hasznlatt teszik lehetv (15.3.2.2).

15.2.3. Ismtld bzisosztlyok


Azltal, hogy tbb bzisosztly lehet, elfordulhat, hogy egy osztly ktszer fordul el a bzisosztlyok kztt. Pldul ha mind a Task, mind a Displayed a Link (Kapcsolat) osztlybl szrmazott volna, a Satellite-oknak kt Link-je lenne:
struct Link { Link* next; }; class Task : public Link { // a Link-et a Task-ok listjhoz (az temez listhoz) hasznljuk // ... }; class Displayed : public Link { // a Link-et a Displayed objektumok listjhoz (a megjelentsi listhoz) hasznljuk // ... };

Forrs: http://www.doksi.hu

518

Absztrakcis mdszerek

Ez nem gond. Kt kln Link objektum szolgl a listk brzolsra s a kt lista nem zavarja egymst. Termszetesen a Link osztly tagjaira nem hivatkozhatunk a ktrtelmsg veszlye nlkl (15.2.3.1). Egy Satellite objektumot gy rajzolhatunk le: Link Link

Task Satellite

Displayed

Ha a kzs bzisosztlyt nem szabad kt kln objektummal brzolni, virtulis bzisosztlyokat (15.2.4) alkalmazhatunk. A Link-hez hasonlan tbbszr szerepl bzisosztlyok olyan elemek, amelyeket nem szabad a kzvetlenl rkl osztlyon kvl hasznlni. Ha egy ilyen osztlyra olyan pontrl kell hivatkozni, ahonnt annak tbb pldnya is lthat, a tbbrtelmsg elkerlse rdekben a hivatkozst minsteni kell:
void mess_with_links(Satellite* p) { p->next = 0; p->Link::next = 0; p->Task::Link::next = 0; p->Displayed::Link::next = 0; // ... }

// hiba: tbbrtelm (melyik Link?) // hiba: tbbrtelm (melyik Link?) // rendben // rendben

Ez pontosan ugyanaz az eljrs, mint amit a tagokra val tbbrtelm hivatkozsok feloldsra hasznlunk (15.2.1).

15.2.3.1. Fellrs A tbbszr szerepl bzisosztlyok valamely virtulis fggvnyt a szrmaztatott osztly (egyetlen) fggvnye fellrhatja (fellbrlhatja, override). Egy objektumnak sajt magt egy fjlbl kiolvasni vagy oda visszarni val kpessgt pldul gy brzolhatjuk:
class Storable { public: virtual const char* get_file() = 0; virtual void read() = 0;

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

519

};

virtual void write() = 0; virtual ~Storable() { }

Termszetesen tbb felhasznl pthet erre, hogy olyan osztlyokat rjon, amelyek fggetlenl vagy egytt hasznlva jobban kidolgozott osztlyokat adnak. Pldul lellthatunk s jraindthatunk egy szimulcit, ha mentjk az alkotelemeket s ksbb visszalltjuk azokat. Ezt az tletet gy valsthatjuk meg:
class Transmitter : public Storable { public: void write(); // ... }; class Receiver : public Storable { public: void write(); // ... }; class Radio : public Transmitter, public Receiver { public: const char* get_file(); void read(); void write(); // ... };

A fellr fggvny ltalban meghvja a bzisosztlybeli vltozatokat s a szrmaztatott osztlyra jellemz tennivalkat vgzi el:
void Radio::write() { Transmitter::write(); Receiver::write(); // kirja a Radio-ra jellemz adatokat }

Az ismtld bzisosztlyokrl szrmaztatott osztlyokra val tpuskonverzit a 15.4.2 pont rja le. Arrl, hogyan lehet az egyes write() fggvnyeket a szrmaztatott osztlyok kln fggvnyeivel fellrni, a 25.6 pont szl.

Forrs: http://www.doksi.hu

520

Absztrakcis mdszerek

15.2.4. Virtulis bzisosztlyok


Az elz pont Radio pldja azrt mkdik, mert a Storable osztlyt biztonsgosan, knyelmesen s hatkonyan lehet tbbszrzni. Ez azonban az olyan osztlyok esetben rendszerint nem igaz, amelyek j ptkvei ms osztlyoknak. A Storable osztlyt pldul gy is meghatrozhatnnk, mint ami tartalmazza az objektum mentsre hasznlt fjl nevt:
class Storable { public: Storable(const char* s); virtual void read() = 0; virtual void write() = 0; virtual ~Storable(); private: const char* store; Storable(const Storable&); Storable& operator=(const Storable&);

};

A Storable ezen ltszlag csekly mdostsa utn meg kell vltoztatnunk a Radio szerkezett is. Az objektum sszes rsze a Storable azonos pldnyn kell, hogy osztozzk; klnben szksgtelenl nehz feladat lenne az objektum tbbszri trolsnak megakadlyozsa. A virtulis bzisosztlyok (virtual base class) ezt a megosztst segtik. A szrmaztatott osztly minden virtulis bzisosztlyt ugyanaz a (megosztott) objektum brzolja:
class Transmitter : public virtual Storable { public: void write(); // ... }; class Receiver : public virtual Storable { public: void write(); // ... }; class Radio : public Transmitter, public Receiver { public: void write(); // ... };

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

521

brval: Storable

Receiver Radio

Transmitter

Hasonltsuk ssze ezt az brt a Satellite objektum 15.2.3-beli rajzval, hogy lssuk a klnbsget a kznsges s a virtulis rklds kztt. Az rkldsi grfban egy adott nev osztly minden virtulisknt megadott bzisosztlyt az osztly egyetlen objektuma brzolja, a nem virtulis bzisosztlyokat viszont sajt rszobjektumuk.

15.2.4.1. Virtulis bzisosztlyok programozsa Amikor a programoz fggvnyeket kszt egy olyan osztly szmra, amelynek virtulis bzisosztlya van, nem tudhatja, hogy a bzisosztlyt meg kell-e osztani egyb szrmaztatott osztlyokkal, ami gondot jelenthet, ha egy szolgltatst gy kell megvalstani, hogy a bzisosztly egy adott fggvnynek meghvsra pontosan egyszer kerljn sor, pldul mert a nyelv elrja, hogy egy virtulis bzisosztly konstruktora csak egyszer futhat le. A virtulis bzisosztly konstruktort a teljes objektum, azaz a legtvolabbi szrmaztatott osztly konstruktora hvja meg (automatikusan vagy kzvetlenl):
class A { // ... }; class B { public: B(); // ... }; class C { public: C(int); }; // nincs konstruktor

// alaprtelmezett konstruktor

// nincs alaprtelmezett konstruktor

Forrs: http://www.doksi.hu

522

Absztrakcis mdszerek

class D : virtual public A, virtual public B, virtual public C { D() { /* ... */ } // hiba: C-nek nincs alaprtelmezett konstruktora D(int i) : C(i) { /* ... */ }; // rendben // ... };

A virtulis bzisosztly konstruktora a szrmaztatott osztlyok konstruktora eltt hvdik meg. Szksg esetn a programoz ezt a mkdst utnozhatja is, ha a virtulis bzisosztly fggvnyt csak a legtvolabbi szrmaztatott osztlybl hvja meg. Tegyk fel, hogy van egy alapvet Window osztlyunk, amely ki tudja rajzolni tartalmt:
class Window { // alapkd virtual void draw(); };

Az ablakokat emellett tbbfle mdon dszthetjk s szolgltatsokkal egszthetjk ki:


class Window_with_border : public virtual Window { // a szegly kdja void own_draw(); // a szegly megjelentse void draw(); }; class Window_with_menu : public virtual Window { // a men kdja void own_draw(); // a men megjelentse void draw(); };

Az own_draw() fggvnyeknek nem kell virtulisaknak lennik, mert egy virtulis draw() fggvnybl akarjuk meghvni azokat, ami pontosan ismeri az objektum tpust, amelyre meghvtk. Ebbl egy mkdkpes Clock osztlyt llthatunk ssze:
class Clock : public Window_with_border, public Window_with_menu { // az ra kdja void own_draw(); // az ralap s a mutatk megjelentse void draw(); };

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

523

brval: Window

Window_with_border Clock

Window_with_menu

A draw() fggvnyeket most mr gy rhatjuk meg az own_draw() fggvnyek felhasznlsval, hogy brmelyik draw() meghvsa pontosan egyszer hvja meg a Window::draw()-t, fggetlenl attl, milyen fajta Window-ra hvtk meg:
void Window_with_border::draw() { Window::draw(); own_draw(); // a szegly megjelentse } void Window_with_menu::draw() { Window::draw(); own_draw(); // a men megjelentse } void Clock::draw() { Window::draw(); Window_with_border::own_draw(); Window_with_menu::own_draw(); own_draw(); // az ralap s a mutatk megjelentse }

A virtulis bzisosztlyokrl szrmaztatott osztlyokra val konverzit a 15.4.2 pont rja le.

15.2.5. A tbbszrs rklds hasznlata


A tbbszrs rklds legegyszerbb s legnyilvnvalbb felhasznlsa kt egybknt egymssal rokonsgban nem ll osztly sszeragasztsa egy harmadik osztly rszeknt. A Task s Displayed osztlyokbl a 15.2 pontban sszerakott Satellite osztly is ilyen.

Forrs: http://www.doksi.hu

524

Absztrakcis mdszerek

A tbbszrs rklds ilyen mdon val hasznlata egyszer, hatkony s fontos de nem tl rdekes, hiszen alapjban vve csak a tovbbt fggvnyek megrstl kmli meg a programozt. Az eljrs nem befolysolja szmotteven a program ltalnos szerkezett s alkalmasint tkzhet azzal a kvnalommal, hogy a megvalsts rszletei maradjanak rejtve. Egy mdszernek azonban nem kell okosnak lennie ahhoz, hogy hasznos legyen. A tbbszrs rklds hasznlata absztrakt osztlyok ksztsre mr nagyobb jelentsg, annyiban, hogy befolysolja a program tervezsnek mdjt. A 12.4.3-beli BB_ival_slider osztly egy plda erre:
class BB_ival_slider : public Ival_slider // fellet , protected BBslider // megvalsts { // az 'Ival_slider' s a 'BBslider' ltal ignyelt fggvnyek megvalstsa // a 'BBslider' szolgltatsainak hasznlata };

Ebben a pldban a kt bzisosztly logikailag klnbz szerepet jtszik. Az egyik egy nyilvnos absztrakt osztly, amely a felletet nyjtja, a msik pedig egy vdett (protected) konkrt osztly, amely a megvalstsrl gondoskodik. A ktfle szerep az osztlyok stlusban s az alkalmazott hozzfrsi szablyokban tkrzdik. A tbbszrs rklds szerepe itt lnyegbevg, mert a szrmaztatott osztlynak mind a fellet, mind a megvalsts virtulis fggvnyeit fell kell rnia. A tbbszrs rklds lehetv teszi a testvrosztlyok szmra, hogy egyetlen kzs s jelentette fggs bevezetse nlkl osztozhassanak adatokon. Ez az eset, amikor az gynevezett kr alak rklds (diamond-shaped inheritance) lp fel. (Lsd a Radio (15.2.4) s a Clock (15.2.4.1) pldkat.) Ha a bzisosztly nem ismtelhet, akkor virtulis (s nem kznsges) bzisosztlyra van szksg. Az n vlemnyem az, hogy a kr alak rklsi hl akkor kezelhet a legjobban, ha vagy a virtulis bzisosztly vagy a belle kzvetlenl szrmaz osztlyok absztraktak. Vegyk pldul jra a 12.4 pont Ival_box osztlyait. Ott vgl az sszes Ival_box osztlyt absztraktt tettk, hogy kifejezzk szerepket, vagyis hogy kizrlag felletek. Ez lehetv tette, hogy a lnyegi programkd minden rszt a megfelel megvalst osztlyokba rejtsk, s az egyes rszeken val osztozs is csak a megvalsts cljra hasznlt ablakoz rendszer klasszikus hierarchijban trtnt. Persze lenne rtelme annak, hogy a Popup_ival_slider-t megvalst osztly nagy rsze kzs legyen a sima Ival_slider-t megvalst osztlyval, gy az adatbekr mezk kezelsn

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

525

kvl azonosak lennnek. Ekkor az is termszetes lenne, hogy az elll csszka-osztlyban elkerljk az Ival_slider objektumok ismtldst. Ehhez az Ival_slider-t virtuliss tesszk:
class BB_ival_slider : public virtual Ival_slider, protected BBslider { /* ... */ }; class Popup_ival_slider : public virtual Ival_slider { /* ... */ }; class BB_popup_ival_slider : public virtual Popup_ival_slider, protected BB_ival_slider { /* ... */ };

brval: Ival_slider BBslider

Popup_ival_slider

BB_ival_slider

BB_popup_ival_slider

Knnyen elkpzelhetjk a Popup_ival_slider-bl szrmaz tovbbi felleteket s az azokbl s a BB_popup_ival_slider-bl szrmaz tovbbi megvalst osztlyokat. Ha az tletet vgigvisszk, akkor a program fellett kpez absztrakt osztlyokbl val minden szrmaztatst virtuliss tesznk. Ez valban a leglogikusabb, legltalnosabb, s legrugalmasabb megoldsnak tnik. Hogy mirt nem tettem ezt, annak egyrszt a hagyomnyok figyelembe vtele az oka, msrszt a virtulis bzisosztlyok megvalstsnak legnyilvnvalbb s leggyakoribb mdszerei annyira hely- s idignyesek, hogy kiterjedt hasznlatuk egy osztlyon bell nem vonz. Mieltt ez a trignyben s futsi idben mrt kltsg visszariasztana bennnket egy ms szemszgbl vonz szerkezet vlasztstl, gondoljuk meg, hogy az Ival_box-ot brzol objektum ltalban csak egy mutatt tartalmaz a virtulis tblra. Ahogy a 15.2.4 pontban lttuk, egy vltozkat nem tartalmaz absztrakt osztly veszly nlkl ismtelhet. gy a virtulis bzisosztly helyett kznsgeset alkalmazhatunk:
class BB_ival_slider : public Ival_slider, protected BBslider { /* ... */ }; class Popup_ival_slider : public Ival_slider { /* ... */ }; class BB_popup_ival_slider : public Popup_ival_slider, protected BB_ival_slider { /* ... */ };

Forrs: http://www.doksi.hu

526

Absztrakcis mdszerek

brval:

Ival_slider

Ival_slider

BBslider

Popup_ival_slider

BB_ival_slider

BB_popup_ival_slider

Ez valsznleg megvalsthat, optimalizlt vltozata az elzekben bemutatott, bevallottan vilgosabb szerkezetnek.

15.2.5.1. A virtulis bzisosztlyok fggvnyeinek fellrsa A szrmaztatott osztlyok fellrhatjk (override) kzvetlen vagy kzvetett virtulis bzisosztlyaik virtulis fggvnyeit. Kt klnbz osztly akr a virtulis bzisosztly klnbz fggvnyeit is fellrhatja, gy tbb szrmaztatott osztly jrulhat hozz a virtulis bzisosztly ltal adott fellet megvalstshoz. A Window osztlynak lehetnek pldul set_color() s prompt() fggvnyei. Ekkor a Window_with_border fellbrlhatja a set_color()-t, mint a sznellenrz sma rszt, a Window_with_menu pedig a prompt()ot, mint a felhasznli fellet kezelsnek rszt:
class Window { // ... virtual void set_color(Color) = 0; virtual void prompt() = 0; };

// httrszn belltsa

class Window_with_border : public virtual Window { // ... void set_color(Color); // httrszn kezelse }; class Window_with_menu : public virtual Window { // ... void prompt(); // felhasznli tevkenysgek kezelse };

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

527

class My_window : public Window_with_menu, public Window_with_border { // ... };

Mi trtnik, ha klnbz szrmaztatott osztlyok ugyanazt a fggvnyt rjk fell? Ez csak akkor megengedett, ha az egyik szrmaztatott osztly minden olyan osztly rkse, amely fellrja a fggvnyt, vagyis egy fggvnynek az sszeset fell kell rnia. A My_window pldul fellrhatja a prompt()-t, hogy a Window_with_menu-belinl jobb vltozatot adjon:
class My_window : public Window_with_menu, public Window_with_border { // ... void prompt();// a felhasznli tevkenysgek kezelst nem hagyjuk a bzisosztlyra };

brval:

Window { set_color(), prompt() }

Window_with_border { set_color() }

Window_with_menu { prompt() }

My_window { prompt() }

Ha kt osztly fellr egy bzisosztlybeli fggvnyt, de a kt fggvny egyike nem rja fell a msikat, akkor az osztlyhierarchia hibs. Ilyenkor nem lehet virtulis fggvnytblt pteni, mert a teljes objektumra vonatkoz fggvnyhvs ktrtelm lenne. Pldul ha a 15.2.4. pontbeli Radio nem adta volna meg a write() fggvnyt, akkor a Receiver s Transmitter osztlyokbeli write() deklarcik a Radio-ban hibt okoztak volna. Mint a Radio esetben is, az ilyen konfliktust a fellr fggvnynek a legtvolabbi szrmaztatott osztlybl val meghvsval oldhatjuk meg. Az olyan osztlyokat, amelyek egy virtulis bzisosztly nmelyik (de nem mindegyik) fggvnynek megvalstst tartalmazzk, gyakran mixin-nek nevezik.

Forrs: http://www.doksi.hu

528

Absztrakcis mdszerek

15.3. Hozzfrs-szablyozs
Egy osztlytag lehet privt (private), vdett (protected) vagy nyilvnos (public): Ha privt, a nevt csak a tagfggvnyekben s a deklarl osztly bartaiban (friend-jeiben) lehet felhasznlni. Ha vdett, a nevt csak a deklarl osztly s bartai tagfggvnyeiben, valamint az osztlybl szrmaztatott osztlyok s bartaik tagfggvnyeiben lehet felhasznlni. Ha nyilvnos, a nevt mindenhol fel lehet hasznlni. Ez azt a nzetet tkrzi, hogy egy osztlyt hromfle fggvny rhet el: az osztlyt megvalst fggvnyek (azaz a bartok s a tagok), egy szrmaztatott osztlyt megvalst fggvnyek (azaz a szrmaztatott osztlyok bartai s a tagok), s az egyb fggvnyek. Ezt gy brzolhatjuk: ltalnos felhasznlk szrmaztatott osztly tagfggvnyei s bartai sajt tagfggvnyek s bartok

nyilvnos: vdett: privt:

A hozzfrsi szablyok egynteten vonatkoznak a nevekre. Hogy a nv mit jell, az rdektelen a hozzfrs szempontjbl. Ez azt jelenti, hogy ugyangy lehetnek privt tagfggvnyek, tpusok, llandk stb., mint privt adattagok. Pldul egy hatkony nem tolakod (non-intrusive, 16.2.1) listaosztlynak valsznleg szksge van az elemeket nyilvntart adatszerkezetekre. Az ilyen informci legjobb, ha privt:

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

529

template<class T> class List { private: struct Link { T val; Link* next; }; struct Chunk { enum { chunk_size = 15 }; Link v[chunk_size]; Chunk* next; }; Chunk* allocated; Link* free; Link* get_free(); Link* head; public: class Underflow { }; // kivtelosztly void insert(T); T get(); // ...

};

template<class T> void List<T>::insert(T val) { Link* lnk = get_free(); lnk->val = val; lnk->next = head; head = lnk; } template<class T> List<T>::Link* List<T>::get_free() { if (free == 0) { // j Chunk lefoglalsa s Link-jeinek a szabad listra helyezse } Link* p = free; free = free->next; return p; } template<class T> T List<T>::get() { if (head == 0) throw Underflow(); Link* p= head; head = p->next; p->next = free; free = p; return p->val;

Forrs: http://www.doksi.hu

530

Absztrakcis mdszerek

A List<T> hatkrbe azltal lpnk be, hogy a tagfggvnyben List<T>::-t runk. Mivel a get_free() visszatrsi rtkt elbb emltjk, mint a List<T>::get_free() nevet, a Link rvidts helyett a teljes List<T>::Link nevet kell hasznlnunk. A nem tag fggvnyeknek a bart (friend) fggvnyek kivtelvel nincs ilyen hozzfrsk:
void would_be_meddler(List<T>* p) { List<T>::Link* q = 0; // hiba: List<T>::Link privt // ... q = p->free; // hiba: List<T>::free privt // ... if (List<T>::Chunk::chunk_size > 31) { // hiba: List<T>::Chunk::chunk_size privt // ... } }

Az osztlyok (class) tagjai alaprtelmezs szerint privtok (private), a struktrk (struct) tagjai nyilvnosak (public, 10.2.8).

15.3.1. Vdett tagok


A vdett (protected) tagok hasznlatnak bemutatsra vegyk a 15.2.4.1 pontbeli Window pldt. Az own_draw() fggvnyek (akarattal) nem teljes kr szolgltatst adnak. Arra a clra terveztk ket, hogy csak a szrmaztatott osztlyok szmra szolgljanak ptkvekl, az ltalnos felhasznls szmra nem biztonsgosak, nem knyelmesek. Msfell a draw() mveletek az ltalnos felhasznlst szolgljk. Ezt a klnbsget a Window osztly felletnek kt, egy vdett s egy nyilvnos felletre val sztvlasztsval lehet kifejezni:
class Window_with_border { public: virtual void draw(); // ... protected: void own_draw(); // egyb kirajzol kd private: // brzols stb. };

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

531

A szrmaztatott osztly a bzisosztly vdett tagjai kzl csak a sajt osztlyba tartoz objektumokat tudja elrni:
class Buffer { protected: char a[128]; // ... }; class Linked_buffer : public Buffer { /* ... */ }; class Cyclic_buffer : public Buffer { // ... void f(Linked_buffer* p) { a[0] = 0; // rendben: Cyclic_buffer sajt vdett tagjt ri el p->a[0] = 0; // hiba: ms tpus vdett tagjt prbltuk elrni } };

Ez megakadlyozza az olyan hibkat, amelyek azltal lphetnnek fel, hogy az egyik szrmaztatott osztly sszezavarja a msik szrmaztatott osztly adatait.

15.3.1.1. A vdett tagok hasznlata Az adatok elrejtsnek egyszer privt/nyilvnos modellje jl szolglja a konkrt tpusokat (10.3). De ha szrmaztatott osztlyokat hasznlunk, akkor ktfle felhasznlja lesz egy osztlynak: a szrmaztatott osztlyok s a nagykznsg. A mveleteket megvalst tagok s bartok ezen felhasznlk rdekben kezelik az objektumokat. A privt/nyilvnos modell lehetv teszi a megvalsts s az ltalnos felhasznls pontos megklnbztetst, de a szrmaztatott osztlyok megfelel kezelst nem. A protected-knt deklarlt tagokkal sokkal knnyebb visszalni, mint a privtknt bevezetettekkel. Ezrt a tagok vdettknt val megadsa ltalban tervezsi hiba. Ha jelents mennyisg adatot gy helyeznk el egy kzs osztlyban, hogy az sszes szrmaztatott osztly hasznlhatja azokat, az adatok srlhetnek. Mg rosszabb, hogy a vdett tagokat csakgy, mint a nyilvnosakat nem knny tszervezni, mert nincs j mdszer az sszes hasznlat feldertsre; gy a vdett tagok megneheztik a program mdostst. Szerencsre nem kell felttlenl vdett tagokat hasznlni; az osztlyokra a privt az alaprtelmezett hozzfrsi kategria s ltalban ez a jobb vlaszts. Az n tapasztalatom az, hogy az sszes szrmaztatott osztly ltal kzvetlenl hasznlhat, jelents mennyisg adat kzs osztlyban val elhelyezse helyett mindig addik ms megolds.

Forrs: http://www.doksi.hu

532

Absztrakcis mdszerek

Jegyezzk meg, hogy ezek a kifogsok nem rvnyesek a vdett tagfggvnyekre; a protected minstvel remekl adhatunk meg a szrmaztatott osztlyokban hasznlhat mveleteket. A 12.4.2 pontbeli Ival_slider is plda erre. Ha ebben a pldban a megvalst osztly privt lett volna, a tovbbi rkls lehetetlenn vlt volna. A tagok elrhetsgre pldkat a C.11.1 tartalmaz.

15.3.2. Bzisosztlyok elrse


A tagokhoz hasonlan egy bzisosztly is lehet nyilvnos, vdett vagy privt:
class X : public B { /* ... */ }; class Y : protected B { /* ... */ }; class Z : private B { /* ... */ };

A nyilvnos rkls a szrmaztatott osztlyt a bzisosztly egy altpusv (subtype) teszi; ez az rkls legltalnosabb formja. A vdett s a privt rkls a megvalsts mdjnak jellsre hasznlatos. A vdett rkls olyan osztlyhierarchikban a leghasznosabb, ahol jellemzen tovbbi rkls trtnik (a 12.4.2 pontbeli Ival_slider j plda erre). A privt bzisosztlyok akkor a leghasznosabbak, amikor egy osztlyt a fellet korltozsval hatrozunk meg, ami ltal ersebb garancik adhatk. A mutatkra vonatkoz Vector pldul rtk-ellenrzssel bvti ki Vector<void*> bzisosztlyt (13.5). Ha azt is biztostani szeretnnk, hogy a Vec-hez (3.7.2) val minden hozzfrs ellenrztt legyen, bzisosztlyt privtknt kell megadnunk, gy a Vec-ek nem konvertldnak nem ellenrztt vector-r:
template <class T > class Vec : private Vector <T> { /* ... */ };

Az osztly hozzfrsi szintjt nem muszj explicit megadni. Ilyenkor az alaprtelmezett hozzfrse class esetn privt, struct esetn nyilvnos lesz:
class XX : B { /* ... */ }; // B privt bzisosztly struct YY : B { /* ... */ }; // B nyilvnos bzisosztly

Az olvashatsg szempontjbl azonban legjobb kirni a hozzfrsi szintet megad kulcsszt. A bzisosztly hozzfrsi szintje az osztly tagjainak elrhetsge mellett a szrmaztatott osztlyra hivatkoz mutatknak vagy referenciknak a bzisosztly tpusra val konvertlhatsgt is jelzi. Vegynk egy B bzisosztlybl szrmaz D osztlyt: Ha B privt bzisosztly, akkor nyilvnos s vdett tagjai csak D tagfggvnyeibl s bartaibl rhetk el. Csak D bartai s tagjai konvertlhatnak egy D* mutatt B*-g.

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

533

Ha B vdett bzisosztly, akkor nyilvnos s vdett tagjai csak D tagfggvnyeibl s bartaibl, valamint a D-bl szrmaz osztlyok tagfggvnyeibl s bartaibl rhetk el. Csak D tagfggvnyei s bartai, valamint a D-bl szrmaz osztlyok tagfggvnyei s bartai vgezhetnek konverzit D*-rl B*-ra. Ha B nyilvnos bzisosztly, nyilvnos tagjai brhol hasznlhatk. Ezenkvl vdett tagjai D tagfggvnyeibl s bartaibl, valamint a D-bl szrmaz osztlyok tagfggvnyeibl s bartaibl rhetk el. Brmely fggvny vgezhet D*-rl B*-ra val konverzit. Ez alapveten azonos a tagok elrhetsgi szablyaival (15.3). A bzisosztlyok elrhetsgt ugyanazon szempontok szerint adjuk meg, mint a tagokt. A BBwindow-t pldul azrt adtam meg az Ival_slider vdett bzisosztlyaknt (12.4.2), mert a BBwindow inkbb az Ival_slider megvalstsnak, mint felletnek rsze. A BBwindow-t azonban nem rejthettem el teljesen gy, hogy privt bzisosztlly teszem, mert az Ival_slider-bl tovbbi osztlyokat akartam szrmaztatni s azoknak el kellett rnik a megvalstst. A bzisosztlyok elrhetsgre a C.11.2 mutat pldkat.

15.3.2.1. A tbbszrs rklds s az elrhetsg Ha egy nevet vagy bzisosztlyt egy tbbszrs rkldsi hl tbb tvonaln is elrhetnk, akkor abban az esetben lesz elrhet, ha valamelyik t mentn elrhet:
struct B { int m; static int sm; // ... }; class D1 : public virtual B { /* ... */ } ; class D2 : public virtual B { /* ... */ } ; class DD : public D1, private D2 { /* ... */ }; DD* pd = new DD; B* pb = pd; int i1 = pd->m; // rendben: elrhet D1-en keresztl // rendben: elrhet D1-en keresztl

Ha egy bizonyos elemet tbb t mentn is elrhetnk, attl mg egyrtelmen, azaz tbbrtelmsgi hiba nlkl hivatkozhatunk r:
class X1 : public B { /* ... */ } ; class X2 : public B { /* ... */ } ; class XX : public X1, public X2 { /* ... */ };

Forrs: http://www.doksi.hu

534

Absztrakcis mdszerek

XX* pxx = new XX; int i1 = pxx->m; int i2 = pxx->sm;

// hiba, tbbrtelm: XX::X1::B::m vagy XX::X2::B::m // rendben: csak egy B::sm van egy XX-ben

15.3.2.2. A using deklarcik s az elrhetsg A using deklarcik nem szolglhatnak tbb informci elrsre, csak az egybknt is hozzfrhet adatok knyelmesebb hasznlatt teszik lehetv. Msrszt viszont, ha egy informci elrhet, akkor a hozzfrsi jog ms felhasznlk fel tovbbadhat:
class B { private: int a; protected: int b; public: int c; }; class D : public B { public: using B::a; // hiba: B::a privt using B::b; // B::b nyilvnosan elrhet D-n keresztl };

Ha egy using deklarci privt vagy vdett rkldssel jr egytt, akkor a bzisosztly ltal rendesen felknlt szolgltatsok egy rszhez felletet adhat:
class BB : private B { public: using B::b; using B::c; }; // hozzfrst ad a B::b s B::c nevekhez, de a B::a-hoz nem

Lsd mg a 15.2.2 pontot.

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

535

15.4. Futsi idej tpusinformci


A 12.4 pontban lert Ival_box-ok valszer hasznlata lenne, ha tadnnk azokat egy kpernykezel rendszernek, majd az visszaadn ket a programnak, valahnyszor valamilyen tevkenysg trtnt. Sok felhasznli fellet mkdik gy. Egy felhasznli felletet kezel rendszer azonban nem felttlenl tud a mi Ival_box-ainkrl. A rendszer fellett a rendszer sajt osztlyai s objektumai nyelvn adjk meg, nem a mi alkalmazsunk osztlyainak nyelvn. Ez szksgszer s rendjn is val, de azzal a kellemetlen kvetkezmnnyel jr, hogy informcit vesztnk a rendszernek tadott s ksbb neknk visszaadott objektumok tpusrl. Az elveszett adatok visszanyershez valahogy meg kell tudnunk krdezni az objektumtl a tpust. Brmilyen mveletet akarunk is vgezni az objektummal, alkalmas tpus, az objektumra hivatkoz mutatra vagy referencira van szksgnk. Kvetkezskppen egy objektum tpusnak futsi idben val lekrdezshez a legnyilvnvalbb s leghasznosabb mvelet az, amely rvnyes mutatt ad vissza, ha az objektum a vrt tpus, illetve nullpointert, ha nem. Pontosan ezt teszi a dynamic_cast opertor. Pldul tegyk fel, hogy a rendszer a my_event_handler()-t arra a BBwindow-ra hivatkoz mutatval hvja meg, amellyel egy tevkenysg trtnt. Ekkor az Ival_box osztly do_something() fggvnyt hasznlva meghvhatnnk programunkat:
void my_event_handler(BBwindow* pw) { if (Ival_box* pb = dynamic_cast<Ival_box*>(pw)) pb->do_something(); else { // hopp! nem vrt esemny } }

// Vajon pw egy Ival_box-ra mutat?

A folyamatot gy is magyarzhatjuk, hogy a dynamic_cast fordt a felhasznli felletet kezel rendszer sajtos nyelvrl az alkalmazs nyelvre. Fontos szrevenni, hogy mi nem nyert emltst ebben a pldban: az objektum tnyleges tpusa. Az objektum az Ival_box egy bizonyos fajtja lesz, mondjuk Ival_slider, amelyet a BBwindow egy bizonyos tpusa valst meg, mondjuk a BBslider. Az objektum tnyleges tpusnak kidertse s megemltse se nem szksges, se nem kvnatos a rendszer s a program kztti ezen prbeszdben. Ltezik fellet a prbeszd lnyegnek lersra, egy jl megtervezett fellet pedig elrejti a lnyegtelen rszleteket.

Forrs: http://www.doksi.hu

536

Absztrakcis mdszerek

Rajzban a
pb = dynamic_cast<Ival_box*>(pw)

utasts hatst gy brzolhatjuk: pw BBwindow Ival_box pb

BBslider

Ival_slider

BB_ival_slider

A pw-bl s pb-bl kiindul nyilak az tadott objektumra hivatkoz mutatkat jellik, mg a tbbi nyl az tadott objektum klnbz rszei kztti rkldsi viszonyokat brzolja. A tpusinformcik futsi idben val hasznlatt hagyomnyosan futsi idej tpusinformcinak (run-time type information) hvjk s gyakran RTTI-nek rvidtik. A bzisosztlyrl szrmaztatott osztlyra trtn konverzit gyakran lefel trtn vagy szrmaztatott irny konverzinak (downcast) hvjk, mert az rklsi fk a hagyomnyos brzols szerint a gykrtl lefel nnek. Ehhez hasonlan a szrmaztatott osztlyrl bzisosztlyra trtn konverzi neve felfel trtn konverzi, vagy bzisirny konverzi (upcast). A bzisosztlyrl testvrre pldul BBwindow-rl Ival_box-ra val konverzit keresztbe trtn konverzinak (crosscast) hvjk.

15.4.1. Dynamic_cast
A dynamic_cast (dinamikus tpusknyszerts) opertor kt paramtert vr, egy <> kz rt tpust s egy () kz rt mutatt vagy referencit. Vegyk elszr a mutat esett:
dynamic_cast<T *>(p)

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

537

Ha a p a T * tpusba, vagy olyan D * tpusba tartozik, ahol T bzisosztlya D-nek, akkor az eredmny ugyanaz, mintha a p-t egy T * vltoznak adtuk volna rtkl:
class BB_ival_slider : public Ival_slider, protected BBslider { // ... }; void f(BB_ival_slider* p) { Ival_slider* pi1 = p; // rendben Ival_slider* pi2 = dynamic_cast<Ival_slider*>(p);

// rendben

BBslider* pbb1 = p; // hiba: BBslider vdett bzisosztly BBslider* pbb2 = dynamic_cast<BBslider*>(p); // rendben: pbb2 rtke 0 lesz

Ez nem tl rdekes. Azt azonban j tudni, hogy a dynamic_cast nem engedi meg a vdett vagy privt bzisosztlyok vdelmnek vletlen megsrtst. A dynamic_cast clja azon esetek kezelse, amelyeknl a fordtprogram nem tudja a konverzi helyessgt megtlni:
dynamic_cast<T *>(p)

A fenti kd megvizsglja a p ltal mutatott objektumot (ha van ilyen). Ha az objektum T osztly vagy van egy egyrtelm T tpus se, akkor a dynamic_cast egy, az objektumra hivatkoz T * tpus mutatt ad vissza, ms esetben 0-t. Ha p rtke 0, a dynamic_cast<T *>(p) eredmnye 0 lesz. Jegyezzk meg, hogy a konverzi csak egyrtelmen azonostott objektumoknl mkdik. Lehet olyan pldkat hozni, ahol a konverzi nem sikerl s 0-t ad, mert a p ltal mutatott objektumnak tbb T tpus bzisosztlyt kpvisel rszobjektuma van (15.4.2). A dynamic_cast-nak a lefel vagy keresztbe trtn konvertlshoz tbbalak (polimorf) mutatra vagy hivatkozsra van szksge:
class My_slider: public Ival_slider { // tbbalak bzisosztly (Ival_slider rendelkezik // virtulis fggvnyekkel) // ... }; class My_date : public Date { }; // ... // nem tbbalak bzisosztly (Date nem rendelkezik // virtulis fggvnyekkel)

Forrs: http://www.doksi.hu

538

Absztrakcis mdszerek

void g(Ival_box* pb, Date* pd) { My_slider* pd1 = dynamic_cast<My_slider*>(pb); My_date* pd2 = dynamic_cast<My_date*>(pd); }

// rendben // hiba: Date nem tbbalak

Az a megkts, hogy a mutatnak tbbalaknak kell lennie, egyszersti a dynamic_cast megvalstst, mert megknnyti az objektum tpusnak trolshoz szksges informci helynek megtallst. ltalnos megolds, hogy a tpust jelz mutatt az objektum virtulis fggvnytbljba (2.5.5) helyezik, ezltal egy tpus-informci objektumot fznek az objektumhoz:

My_slider: ... vptr ...

vtbl: type_info: "My_slider" bzisosztlyok type_info: "Ival_slider"

A szaggatott nyl az eltolst (offset) jelli, amelynek segtsgvel a teljes objektum kezdete megtallhat, ha csak egy tbbalak rszobjektumra hivatkoz mutat adott. Vilgos, hogy a dynamic_cast hatkonyan felhasznlhat; csak nhny, a bzisosztlyt ler type_info objektumot kell sszehasonltani, nincs szksg hosszadalmas keressre vagy karakterlncok sszehasonltsra. A dynamic_cast-nak tbbalak tpusokra val korltozsa logikai szempontbl nzve is rtelmes. Ha egy objektumnak nincs virtulis fggvnye, akkor pontos tpusnak ismerete nlkl nem kezelhet biztonsgosan, ezrt gyelni kell, hogy az ilyen objektum ne kerljn olyan krnyezetbe, ahol nem ismeretes a pontos tpusa. Ha azonban a tpusa ismert, nincs szksg dynamic_cast-ra. A dynamic_cast cltpusa nem kell, hogy tbbalak legyen, ezrt egy konkrt tpust tbbalakba csomagolhatunk, mondjuk, hogy egy objektumokat kezel ki- s bemeneti rendszeren keresztl tovbbtsuk (25.4.1), majd ksbb kicsomagoljuk belle a konkrt tpust:

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

539

class Io_obj { virtual Io_obj* clone() = 0; };

// bzisosztly-objektum az I/O rendszer szmra

class Io_date : public Date, public Io_obj { }; void f(Io_obj* pio) { Date* pd = dynamic_cast<Date*>(pio); // ... }

Egy tbbalak objektum kezdcmt egy void*-ra val dinamikus tpusknyszertssel hatrozhatjuk meg:
void g(Ival_box* pb, Date* pd) { void* pd1 = dynamic_cast<void*>(pb); void* pd2 = dynamic_cast<void*>(pd); }

// rendben // hiba: Date nem tbbalak

Ez azonban csak nagyon alacsony szint fggvnyekkel val egyttmkds cljra hasznos.

15.4.1.1. Referencik dinamikus tpusknyszertse Egy objektum tbbalak (polymorph) viselkedshez akkor frnk hozz, ha mutatn vagy referencin t kezeljk. A dynamic_cast mvelet sikertelensgt 0-val jelzi. Ez referencikra se nem kivitelezhet, se nem kvnatos. Ha egy mutatrl, mint eredmnyrl van sz, akkor fel kell kszlnnk arra a lehetsgre, hogy az eredmny 0 lesz, azaz a mutat nem mutat semmilyen objektumra. Ezrt egy dynamic_cast mvelet eredmnyt mindig ellenriznnk kell. A p mutatn vgzett dynamic_cast<T*>(p) egy krdsknt foghat fel: a p ltal mutatott objektum T tpus? Msrszt viszont jogosan ttelezhetjk fel, hogy egy referencia mindig egy objektumra vonatkozik. Kvetkezskppen egy r referencia esetn a dynamic_cast<T&>(r) nem krds, hanem llts: a r ltal mutatott objektum T tpus. A dynamic_cast mvelet eredmnyt maga a dynamic_cast-ot megvalst kd ellenrzi automatikusan, s ha a dynamic_cast operandusa nem a vrt tpus hivatkozs, akkor bad_cast kivtelt vlt ki:

Forrs: http://www.doksi.hu

540

Absztrakcis mdszerek

void f(Ival_box* p, Ival_box& r) { if (Ival_slider* is = dynamic_cast<Ival_slider*>(p)) { // Vajon p egy Ival_slider-re mutat? // 'is' hasznlata } else { // *p nem slider } Ival_slider& is = dynamic_cast<Ival_slider&>(r); // 'is' hasznlata // az r egy Ival_slider-re hivatkozik!

A sikertelen dinamikus mutat- illetve referencia-talaktsok eredmnynek eltrsben a mutatk, illetve a referencik kztti alapvet klnbsg tkrzdik. Ha egy felhasznl vdekezni akar a referencia-talakts sikertelensge ellen, akkor egy megfelel kivtelkezelre van szksge:
void g() { try {

f(new BB_ival_slider,*new BB_ival_slider);

f(new BBdial,*new BBdial); } catch (bad_cast) { // ... }

// a paramterek Ival_box-knt // addnak t // a paramterek Ival_box-knt addnak t

// 14.10

Az f() fggvny els meghvsa sikeresen fog visszatrni, mg a msodik bad_cast kivtelt vlt ki, amit g() elkap. A 0 rtk ellenrzse elhagyhat, gy alkalmasint vletlenl el is fog maradni. Ha ez aggasztja az olvast, akkor rhat egy olyan konverzis fggvnyt, amely sikertelensg esetn a 0 rtk visszaadsa helyett kivtelt vlt ki (15.8[1]).

15.4.2. Osztlyhierarchik bejrsa


Ha csak egyszeres rkldst hasznlunk, az osztly s bzisosztlyai egyetlen bzisosztlyban gykerez ft alkotnak. Ez egyszer, de gyakran tl ers megszortst jelent. Tbbszrs rklds hasznlata esetn nincs egyetlen gykr. Ez nmagban nem bonyoltja nagyon a helyzetet, de ha egy osztly tbbszr fordul el a hierarchiban, akkor nmi vatossggal kell az adott osztly objektumra vagy objektumokra hivatkoznunk.

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

541

Termszetesen a hierarchikat igyeksznk annyira egyszernek venni de nem egyszerbbnek , amennyire programunk megengedi. De ha mr kialakult egy bonyolultabb hierarchia, hamarosan szksgnk lesz annak bejrsra (vagyis vgignzsre), hogy alkalmas osztlyt talljunk, amelyet felletknt hasznlhatunk. Ez az igny kt esetben merlhet fel. Nha kifejezetten meg akarunk nevezni egy bzisosztlyt vagy annak egy tagjt (pldul 15.2.3 s 15.2.4.1). Mskor egy, a bzisosztlyt vagy annak egy szrmaztatott osztlyt megjelent objektumra hivatkoz mutatra van szksgnk, ha adott egy mutat a teljes objektumra vagy annak valamely rszobjektumra (15.4 s 15.4.1). Itt azt tekintjk t, hogyan lehet tpusknyszertst (cast) hasznlva a kvnt tpus mutathoz jutni. Hogy szemlltessk az elrhet mdszereket s a rjuk vonatkoz szablyokat, vegynk egy tbbszr szerepl s virtulis bzisosztlyt egyarnt tartalmaz hlt:
class Component : public virtual Storable { /* ... */ }; class Receiver : public Component { /* ... */ }; class Transmitter : public Component { /* ... */ }; class Radio : public Receiver, public Transmitter { /* ... */ };

brval: Storable

Component

Component

Receiver

Transmitter

Radio Itt a Radio objektumnak kt Component osztly rszobjektuma van. Ezrt a Radio-n belli, Storable-rl Component-re val dinamikus tpusknyszerts tbbrtelm lesz s nullt ad. Ilyenkor egyszeren nem lehet tudni, melyik Component-re gondolt a programoz:
void h1(Radio& r) { Storable* ps = &r; // ... Component* pc = dynamic_cast<Component*>(ps); // pc = 0 }

Forrs: http://www.doksi.hu

542

Absztrakcis mdszerek

Ez a tbbrtelmsg fordtsi idben ltalban nem derthet fel:


void h2(Storable* ps) // ps-rl nem tudjuk, hogy Component-re mutat-e { Component* pc = dynamic_cast<Component*>(ps); // ... }

A tbbrtelmsgnek ez a fajta feldertse csak virtulis bzisosztlyoknl szksges. Kznsges bzisosztlyok s lefel (azaz a szrmaztatott osztly fel trtn; 15.4) konverzi esetn a kvnt tpus rszobjektum mindig egyrtelm (ha ltezik). Ezzel egyenrtk tbbrtelmsg lp fel felfel (azaz a bzisosztly fel trtn) konverzi esetn s ezek a tbbrtelmsgek fordtsi idben kiderlnek.

15.4.2.1. Statikus s dinamikus konverzi A dynamic_cast mvelet tbbalak bzisosztlyrl szrmaztatott osztlyra vagy testvrosztlyra val talaktst tud vgezni (15.4.1). A static_cast (6.2.7) nem vizsglja a kiindul objektum tpust, gy nem kpes ezekre:
void g(Radio& r) { Receiver* prec = &r; // a Receiver a Radio kznsges bzisosztlya Radio* pr = static_cast<Radio*>(prec); // rendben, nincs ellenrzs pr = dynamic_cast<Radio*>(prec); // rendben, futsi idej ellenrzs Storable* ps = &r; // a Storable a Radio virtulis bzisosztlya pr = static_cast<Radio*>(ps); // hiba: virtulis bzisosztlyrl nem lehet talaktani pr = dynamic_cast<Radio*>(ps); // rendben, futsi idej ellenrzs

A dynamic_cast-nak tbbalak operandusra van szksge, mert egy nem tbbalak objektum nem trol olyan informcit, amelynek alapjn meg lehetne keresni azon objektumokat, amelyeknek se (bzisa). Olyan objektum is lehet pldul virtulis bzisosztly, amelynek a memriban val elhelyezkedst egy msik nyelv, pldul a Fortran vagy a C hatrozza meg. Ezekre vonatkozan csak statikus adatok llnak rendelkezsre, de a dynamic_cast megvalstshoz szksges informcit a futsi idej tpusinformci tartalmazza. Mirt akarna valaki static_cast-ot hasznlni egy osztlyhierarchia bejrsra? A dynamic_cast nmileg nveli a futsi idt (15.4.1), de ennl jelentsebb ok, hogy milli sornyi kd van a dynamic_cast bevezetse eltti idkbl. Az ilyen kdok ms mdokon

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

543

biztostjk az alkalmazott tpustalaktsok helyessgt, gy a dynamic_cast-tal vgeztetett ellenrzs feleslegesnek tnik. Az ilyen ltalban C stlus tpuskonverzival (6.2.7) rdott kdban azonban gyakran maradhatnak rejtett hibk, gy, hacsak lehet, hasznljuk a biztonsgosabb dynamic_cast-ot. A fordtprogram nem ttelezhet fel semmit egy void* mutat ltal mutatott memriaterletrl. Ebbl kvetkezik, hogy az objektum tpusa fell rdekld dynamic_cast nem kpes void*-rl konvertlni. Ehhez static_cast kell:
Radio* f(void* p) { Storable* ps = static_cast<Storable*>(p); return dynamic_cast<Radio*>(ps); }

// Bzzunk a programozban!

Mind a dynamic_cast, mind a static_cast tiszteletben tartja a const minstst s a hozzfrsi korltozsokat:
class Users : private set<Person> { /* ... */ }; void f(Users* pu, const Receiver* pcr) { static_cast<set<Person>*>(pu); dynamic_cast<set<Person>*>(pu); static_cast<Receiver*>(pcr); dynamic_cast<Receiver*>(pcr); Receiver* pr = const_cast<Receiver*>(pcr); // ...

// hiba: nem frhet hozz // hiba: nem frhet hozz // hiba: const minsts nem vsz el // hiba: const minsts nem vsz el // rendben

Privt bzisosztlyra nem lehet konvertlni, const-ot nem konstanss konvertlni pedig csak const_cast-tal lehet (6.2.7), s mg akkor is csak gy kapunk helyes eredmnyt, ha az objektumot eredetileg nem const-knt deklarltuk (10.2.7.1).

15.4.3. Osztlyobjektumok felptse s megsemmistse


Egy valamilyen osztlyba tartoz objektum tbb, mint egyszeren a memria egy rsze (4.9.6). Az osztlyobjektumokat konstruktoraik ptik fel a nyers memribl s destruktoraik lefutsval vlnak jra nyers memriv. Az objektum felptse alulrl felfel, megsemmistse fellrl lefel trtnik, s az osztlyobjektum olyan mrtkben ltez

Forrs: http://www.doksi.hu

544

Absztrakcis mdszerek

objektum, amennyire felptse, illetve megsemmistse megtrtnt. Ez tkrzdik a futsi idej tpusazonostsra (RTTI), a kivtelkezelsre (14.4.7) s a virtulis fggvnyekre vonatkoz szablyokban. Nem blcs dolog az objektumfelpts vagy -megsemmists sorrendjre tmaszkodni, de a sorrendet megfigyelhetjk, ha virtulis fggvnyeket, dynamic_cast-ot vagy typeid-t (15.4.4) hvunk akkor, amikor az objektum mg nincs kszen. Pldul ha a 15.4.2 pontbeli hierarchia Component konstruktora egy virtulis fggvnyt hv, akkor a Storable vagy Component-beli vltozatot fogja meghvni, nem a Receiver, Transmitter vagy Radio-belit. Az objektum ltrehozsnak ezen pontjn az objektum mg nem Radio; csak egy rszben felptett objektum. Ennek fnyben legjobb elkerlni a virtulis fggvnyeknek konstruktorbl vagy destruktorbl val meghvst.

15.4.4. Typeid s kiterjesztett tpusinformci


A dynamic_cast opertor az objektumok tpusra vonatkoz, futsi idben jelentkez informciigny legnagyobb rszt kielgti. Fontos tulajdonsga, hogy biztostja a felhasznl kd helyes mkdst a programoz ltal hasznlt osztlyokbl szrmaz osztlyokkal is. gy a dynamic_cast a virtulis fggvnyekhez hasonlan megrzi a rugalmassgot s bvthetsget. Nha azonban alapvet fontossg tudni az objektum pontos tpust. Pldul tudni szeretnnk az objektum osztlynak nevt vagy memriakiosztst. A typeid opertor ezt az operandusa tpust jelz objektum visszaadsval tmogatja. Ha a typeid() fggvny lenne, valahogy gy adhatnnk meg:
class type_info; const type_info& typeid(type_name) throw(); // l-deklarci const type_info& typeid(expression) throw(bad_typeid); // l-deklarci

Azaz a typeid() egy standard knyvtrbeli, a <typeinfo> fejllomnyban definilt type_info nev tpusra val referencit ad vissza. Ha operandusknt egy tpusnevet kap, a typeid() az azt brzol type_info-ra val referencival tr vissza, ha kifejezst, a kifejezs ltal jellt objektumot brzol type_info-ra fog hivatkozni. A typeid() leginkbb egy referencival vagy mutatval jellt objektum tpusnak lekrdezsre hasznlatos:
void f(Shape& r, Shape* p) { typeid(r); // az r ltal hivatkozott objektum tpusa typeid(*p); // a p ltal mutatott objektum tpusa typeid(p); // a mutat tpusa, vagyis Shape* (nem gyakori, leginkbb tveds) }

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

545

Ha a mutat vagy referencia operandus rtke 0, a typeid() bad_typeid kivtelt vlt ki. A type_info megvalsts-fggetlen rsze gy nz ki:
class type_info { public: virtual ~type_info(); bool operator==(const type_info&) const; bool operator!=(const type_info&) const; bool before(const type_info&) const; const char* name() const; private: type_info(const type_info&); type_info& operator=(const type_info&); // ... };

// tbbalak // sszehasonlthat // rendezs // a tpus neve // msols megakadlyozsa // msols megakadlyozsa

A before() fggvny lehetv teszi a type_info objektumok rendezst. A meghatrozott rendezsi sorrendnek nincs kze az rklsi viszonyokhoz. Nem biztos, hogy a rendszer minden egyes tpust egyetlen type_info objektum kpviseli. Dinamikus csatols knyvtrak hasznlata esetn pldul valban nehz a tbb type_info objektumot elkerl megvalsts elksztse. Ezrt a == mvelettel az egyenlsget a type_info objektumok s nem az azokra hivatkoz mutatk esetben vizsgljuk. Nha tudni akarjuk egy objektum pontos tpust, hogy valamilyen szabvnyos mveletet vgezznk az egsz objektumon (s nem csak annak egy sn). Idelis esetben az ilyen mveletek virtulis fggvnyek formjban llnak rendelkezsre, gy nem szksges a pontos tpus ismerete. Egyes esetekben azonban nem ttelezhet minden egyes kezelt objektumra vonatkoz kzs fellet, gy a megolds tja a pontos tpuson keresztl vezet (15.4.4.1). Egy msik, sokkal egyszerbb hasznlat az osztly nevnek diagnosztikai kimenet cljra val lekrdezse:
#include<typeinfo> void g(Component* p) { cout << typeid(*p).name(); }

Forrs: http://www.doksi.hu

546

Absztrakcis mdszerek

Az osztlyok nevnek szveges brzolsa az adott nyelvi vltozattl fgg. C stlus karakterlncokkal trtnik, amelyek a rendszerhez tartoz memriarszben vannak, ezrt ne prbljuk meg a delete[ ] mveletet alkalmazni rjuk.

15.4.4.1. Kiterjesztett tpusinformci Egy objektum pontos tpusnak meghatrozsa ltalban csak az els lps a r vonatkoz rszletesebb informcik megszerzse s hasznlata fel. Gondoljuk meg, hogy egy program vagy programozst segt eszkz hogyan tudna futsi idben tpusokrl szl informcit adni a felhasznlknak. Tegyk fel, hogy van egy eszkznk, amely minden felhasznlt osztlyrl megmondja az objektum memriakiosztst. Ezeket a lerkat egy map-be tehetem, hogy annak alapjn a felhasznli kd megtallhassa a memriakiosztsi informcit:
map<string, Layout> layout_table; void f(B* p) { Layout& x = layout_table[typeid(*p).name()]; // x hasznlata }

Valaki ms egszen eltr informcit adhat:


struct TI_eq { bool operator()(const type_info* p, const type_info* q) { return *p==*q; } }; struct TI_hash { int operator()(const type_info* p); }; // hastrtk kiszmtsa (17.6.2.2) // 17.6

hash_map<const type_info*,Icon,hash_fct,TI_hash,TI_eq> icon_table; void g(B* p) { Icon& i = icon_table[&typeid(*p)]; // i hasznlata }

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

547

A typeid-ekhez informci rendelsnek ez a mdja tbb programoz vagy eszkz szmra teszi lehetv, hogy a tpusokhoz egymstl teljesen fggetlen informcikat rendeljenek: layout_table: "T" ... icon_table: ... &typeid(T) ...

Az objektum memriakiosztsa

A tpus ikonos brzolsa

Ez nagyon fontos, mert annak valsznsge, hogy valaki informciknak olyan halmazval tud elllni, amely egymagban minden felhasznl ignyeit kielgti, a nullval egyenl.

15.4.5. Az RTTI helyes s helytelen hasznlata


Csak szksg esetn hasznljunk explicit futsi idej tpusinformcit. A statikus (fordtsi idben trtn) ellenrzs biztonsgosabb, olcsbb s alkalmasint jobban szerkesztett programokhoz vezet. A futsi idej tpusinformcit pldul arra hasznlhatjuk, hogy egy rosszul lczott switch utastst rjunk:
// a futsi idej tpusinformci helytelen hasznlata void rotate(const Shape& r) { if (typeid(r) == typeid(Circle)) { // nem csinlunk semmit } else if (typeid(r) == typeid(Triangle)) { // hromszg forgatsa } else if (typeid(r) == typeid(Square)) { // ngyzet forgatsa } // ... }

A dynamic_cast-nak a typeid helyett val hasznlata alig javtana ezen a kdon.

Forrs: http://www.doksi.hu

548

Absztrakcis mdszerek

Sajnos ez nem egy lgbl kapott plda; ilyen kdot tnyleg rnak. A C-hez, a Pascalhoz, a Modula-2-hz, vagy az Adhoz hasonl nyelveken nevelkedett programoz majdnem ellenllhatatlan ksrtst rez, hogy a programot switch utastsok halmazaknt ptse fel. Ennek a ksrtsnek ltalban ellen kell llni. Futsi idej tpusazonosts helyett inkbb virtulis fggvnyeket (2.5.5, 12.2.6) hasznljunk a legtbb olyan eset kezelsre, amikor a tpuson alapul futsi idej megklnbztets szksges. Az RTTI helyes hasznlata sokszor merl fel akkor, amikor a kdban valamilyen szolgltatst egy bizonyos osztlyhoz kapcsolunk s a felhasznl rkldssel akar tovbbi szolgltatsokat hozzadni. A 15.4 pontbeli Ival_box hasznlata ennek egy pldja. Ha a felhasznl hajland s kpes a knyvtri osztlyok pldul a BBwindow mdostsra, akkor az RTTI hasznlata elkerlhet; klnben viszont szksges. De ha a felhasznl hajland is a knyvtri osztlyok mdostsra, az ilyen mdosts problmkhoz vezethet. Pldul szksgess vlhat a virtulis fggvnyek l-megvalstsa olyan osztlyok esetben, amelyeknl azok nem szksgesek vagy nem rtelmesek. Ezt a problmt a 24.4.3 nmileg rszletesebben trgyalja. Az RTTI-nek egy egyszer, objektumokat kezel ki- s bemeneti rendszer elksztsre szolgl hasznlatt a 25.4.1 rja le. Azok szmra, akik a Smalltalkon vagy a Lispen, esetleg ms, nagymrtkben a dinamikus tpusellenrzsre pt nyelveken nevelkedtek, csbt dolog az RTTI-t tlsgosan ltalnos tpusokkal egytt hasznlni. Vegyk ezt a pldt:
// a futsi idej tpusinformci helytelen hasznlata class Object { /* ... */ }; // tbbalak

class Container : public Object { public: void put(Object*); Object* get(); // ... }; class Ship : public Object { /* ... */ }; Ship* f(Ship* ps, Container* c) { c->put(ps); // ... Object* p = c->get(); if (Ship* q = dynamic_cast<Ship*>(p)) { return q; }

// futsi idej ellenrzs

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

549

else { } }

// valami mst csinlunk (ltalban hibakezelst vgznk)

Itt az Object osztly szksgtelen s mesterklt. Tlsgosan ltalnos, mert az adott alkalmazsban nem felel meg semmilyen elvonatkoztatsi szintnek s a programozt egy megvalsts-szint fogalom hasznlatra knyszerti. Az ilyen jelleg problmkat gyakran jobban oldja meg, ha kizrlag egy adott tpus mutatt tartalmaz trol sablonokat hasznlunk:
Ship* f(Ship* ps, list<Ship*>& c) { c.push_front(ps); // ... return c.pop_front(); }

Virtulis fggvnyekkel egytt hasznlva gy majdnem minden esetet megoldhatunk.

15.5. Tagra hivatkoz mutatk


Sok osztlynak van egyszer, nagyon ltalnos fellete, amelyet sokfle hasznlatra szntak. Pldul sok objektumorientlt felhasznli fellet hatroz meg egy sor krst, amelyre minden, a kpernyn megjelentett objektumnak tudnia kell vlaszolni. Radsul az ilyen krsek kzvetett vagy kzvetlen mdon programoktl rkezhetnek. Vegyk ezen elv egy egyszer vltozatt:
class Std_interface { public: virtual void start() = 0; virtual void suspend() = 0; virtual void resume() = 0; virtual void quit() = 0; virtual void full_size() = 0; virtual void small() = 0; }; virtual ~Std_interface() {}

Forrs: http://www.doksi.hu

550

Absztrakcis mdszerek

Minden mvelet pontos jelentst az az objektum definilja, amelyre alkalmazzk. Gyakran a krst kiad szemly vagy program s a fogad objektum kztt egy szoftverrteg van. Idelis esetben az ilyen kztes rtegeknek nem kell semmit tudniuk az egyes mveletekrl (resume(), full_size() stb.). Ha tudnnak, a kztes rtegeket fel kellene jtani, valahnyszor a mveletek halmaza megvltozik. Kvetkezskppen az ilyen kztes rtegek csupn tovbbtanak valamely, az alkalmazand mveletre vonatkoz adatot a krs forrstl a fogadhoz. Ennek egyszer mdja az alkalmazand mveletet jell karakterlnc kldse. Pldul a suspend() meghvsa cljbl a suspend (felfggeszts) szveget kldhetnnk. Valakinek azonban ltre kell hoznia a karakterlncot, valakinek pedig meg kell fejtenie, melyik mvelethez tartozik, ha egyltaln tartozik valamelyikhez. Ez gyakran tlsgosan kzvetettnek s fradsgosnak tnik. Ehelyett kldhetnnk csak egy, a mveletet jell egsz rtket. Mondjuk a 2 jelenthetn a suspend-et. De amg egy egsz szmot a szmtgpek knyelmesen kezelnek, az emberek szmra ez meglehetsen zavar lehet, radsul mg mindig meg kell rnunk a kdot, amely meghatrozza, hogy a 2 a suspend()-et jelenti s meg kell hvnunk a suspend()-et. A C++ nyelv lehetv teszi az osztlyok tagjainak kzvetett elrst. A tagra hivatkoz mutat olyan rtk, amely az osztly egy tagjt azonostja. Gondolhatunk r gy, mint egy, az adott osztlyhoz tartoz objektumban lev tag helyre, de a fordt termszetesen figyelembe veszi az adattagok, a virtulis s nem virtulis fggvnyek stb. kztti klnbsget. Vegyk az Std_interface-t. Ha meg akarjuk hvni a suspend()-et valamely objektumra anlkl, hogy kzvetlenl megneveznnk, akkor egy olyan mutatra lesz szksgnk, ami az Std_interface::suspend() tagra mutat. Ugyancsak szksgnk lesz a suspend() rvn felfggesztend objektumra hivatkoz mutatra vagy referencira. Vegynk egy igen egyszer pldt:
typedef void (Std_interface::* Pstd_mem)(); void f(Std_interface* p) { Pstd_mem s = &Std_interface::suspend; p->suspend(); } (p->*s)(); // kzvetlen hvs // hvs tagra hivatkoz mutatn keresztl // tagra hivatkoz mutat

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

551

Egy tagra hivatkoz mutatt (pointer to member) gy kapunk, hogy a cmkpz & opertort egy teljes nev (teljesen minstett, fully qualified) osztlytagra alkalmazzuk (pldul &Std_interface::suspend()). Az X osztly egy tagjra hivatkoz mutat tpus vltozkat az X::* formban deklarlhatjuk. A C szintaxis ttekinthetsgnek hinyt ltalban a typedef alkalmazsval ellenslyozzk, az X::* forma viszont lthatan nagyon szpen megfelel a hagyomnyos * deklartornak. Egy m tagra hivatkoz mutatt egy objektummal kapcsolatban hasznlhatunk. A kapcsolatot a ->* s .* opertorokkal fejezhetjk ki. Pldul a p->*m az m-et a p ltal mutatott objektumhoz kti, az obj.*m pedig az obj objektumhoz. Az eredmny az m tpusnak megfelelen hasznlhat, de a ->* vagy .* mvelet eredmnyt nem lehet ksbbi hasznlatra flretenni. Termszetesen ha tudnnk, melyik tagot akarjuk meghvni, kzvetlenl meghvnnk azt, a tagra hivatkoz mutatkkal val bajlds helyett. A kznsges fggvnymutatkhoz hasonlan a tagfggvnyekre hivatkoz mutatkat akkor hasznljuk, amikor a tag nevnek ismerete nlkl akarunk egy fggvnyre hivatkozni. A tagra hivatkoz mutat azonban nem egyszeren egy mutat egy memriaterletre, mint egy vltoz cme vagy egy fggvnymutat, inkbb egy adatszerkezeten belli eltolsra (offszetre) vagy egy tmbbeli indexre hasonlt. Amikor egy tagra hivatkoz mutatt a megfelel tpus objektumra hivatkoz mutatval prostjuk, olyasvalami keletkezik, ami egy bizonyos objektum egy bizonyos tagjt azonostja. Ezt rajzzal gy brzolhatjuk: vtbl: s p

X::start X::suspend

Mivel egy virtulis tagra hivatkoz mutat (a fenti pldban s) egyfajta eltols, nem fgg az objektum helytl a memriban, ezrt biztonsgosan tadhat egy msik cmtrnek (address space), feltve, hogy az objektum elhelyezkedse a kettben azonos. A kznsges fggvnyekre hivatkoz mutatkhoz hasonlan a nem virtulis tagfggvnyekre hivatkoz mutatkat sem lehet msik cmtrnek tadni.

Forrs: http://www.doksi.hu

552

Absztrakcis mdszerek

Jegyezzk meg, hogy a fggvnymutatn keresztl meghvott fggvny lehet virtulis is. Pldul amikor egy fggvnymutatn keresztl meghvjuk a suspend()-et, akkor azon objektum tpusnak megfelel suspend()-et kapjuk, amelyre a fggvnymutatt alkalmaztuk. Ez a fggvnymutatknak igen lnyeges tulajdonsga. Egy tolmcsol (tovbbt, interpreter) fggvny egy tagra hivatkoz mutat segtsgvel meghvhat egy karakterlnccal (vagyis szveggel) megadott fggvnyt:
map<string,Std_interface*> variable; map<string,Pstd_mem> operation; void call_member(string var, string oper) { (variable[var]->*operation[oper])(); }

// var.oper()

A tagfggvnyekre hivatkoz mutatk legfontosabb hasznlatt a 3.8.5 s 18.4 pontbeli mem_fun() fggvnyben vizsglhatjuk meg. A statikus tagok nem tartoznak egy bizonyos objektumhoz, gy egy statikus tagra hivatkoz mutat csupn egy kznsges mutat:
class Task { // ... static void schedule(); }; void (*p)() = &Task::schedule; void (Task::* pm)() = &Task::schedule; // rendben // hiba: egyszer mutatt adtunk rtkl // tagra hivatkoz mutatnak

Az adattagokra hivatkoz mutatkat a C.12 pont rja le.

15.5.1. Bzis- s szrmaztatott osztlyok


Egy szrmaztatott osztly legalbb azokkal a tagokkal rendelkezik, amelyeket a bzisosztlybl rkl, de gyakran tbbel. Ez azt jelenti, hogy egy bzisosztly egy tagjra hivatkoz mutatt biztonsgosan rtkl adhatunk egy szrmaztatott osztlytagra hivatkoz mutatnak, de fordtva nem. Ezt a tulajdonsgot gyakran kontravariancinak (contravariance) hvjk:

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

553

class text : public Std_interface { public: void start(); void suspend(); // ... virtual void print(); private: vector s; }; void (Std_interface::* pmi)() = &text::print; void (text::*pmt)() = &Std_interface::start; // hiba // rendben

A fel nem cserlhetsg ezen szablya ltszlag ellenkezik azzal a szabllyal, hogy egy szrmaztatott osztlyra hivatkoz mutatt rtkl adhatunk a bzisosztlyra hivatkoz mutatnak. Valjban azonban mindkt szably azrt van, hogy biztostsa az alapvet garancit arra, hogy egy mutat soha nem mutat olyan objektumra, amelynek nincsenek meg a mutat tpusa ltal grt tulajdonsgai. Ebben az esetben az Std_interface::* brmilyen Std_interface-re alkalmazhat, s a legtbb ilyen objektum vlhetleg nem text tpus lesz. Ezrt aztn nincs meg a text::print tagjuk, amellyel pmi-nek kezdrtket prbltunk adni. A kezdeti rtkads megtagadsval a fordtprogram egy futsi idej hibtl ment meg bennnket.

15.6. A szabad tr
Az operator new() s az operator delete() definilsval t lehet venni a memriakezelst egy osztlytl (6.2.6.2). Ezen globlis fggvnyek kicserlse azonban nem a flnkeknek val, hiszen elfordulhat, hogy valaki az alaprtelmezett viselkedsre szmt vagy ezen fggvnyek valamilyen ms vltozatt mr meg is rta. Gyakran jobb megkzelts ezeket a mveleteket egy bizonyos osztlyra megrni. Ez az osztly tbb szrmaztatott osztly alapja is lehet. Tegyk fel, hogy a 12.2.6-beli Employee osztlyt s annak minden leszrmazottjt szeretnnk egyedi memriafoglalval (alloktorral) s -felszabadtval (dealloktorral) elltni:
class Employee { // ... public: // ...

Forrs: http://www.doksi.hu

554

Absztrakcis mdszerek

};

void* operator new(size_t); void operator delete(void*, size_t);

Az operator new() s az operator delete() tagfggvnyek automatikusan statikusak lesznek, ezrt nincs this mutatjuk s nem vltoztatnak meg egy objektumot, csak olyan trterletet adnak, amelyet a konstruktorok feltlthetnek s a destruktorok kitakarthatnak.
void* Employee::operator new(size_t s) { // lefoglal 's' bjtnyi memrit s r hivatkoz mutatt ad vissza } void Employee::operator delete(void* p, size_t s) { if (p) { // trls csak ha p!=0; lsd 6.2.6, 6.2.6.2 // feltesszk, hogy 'p' 's' bjt memrira mutat, amit az Employee::operator new() foglalt le // felszabadtjuk a memrit } }

Az eddig rejtlyes size_t paramter haszna most vilgoss vlik. Ez ugyanis a tnylegesen trlend objektum mrete. Ha egy sima Employee-t trlnk, akkor paramterrtkknt sizeof(Employee)-t; ha egy Manager-t, akkor sizeof(Manager)-t kapunk. Termszetesen az adott osztly egyedi memriafoglalja trolhatja az ilyen informcit (mint ahogy az ltalnos clnak is meg kell ezt tennie) s nem trdhet az operator delete() fggvny size_t paramtervel. Ez azonban nehezebb teszi egy ltalnos cl memriafoglal sebessgnek s memriafogyasztsnak javtst. Honnan tudja a fordtprogram a delete() opertort a megfelel mretettel elltni? Knnyen, amg a delete mveletnek tadott tpus azonos az objektum tnyleges tpusval. Ez azonban nincs mindig gy:
class Manager : public Employee { int level; // ... }; void f() { Employee* p = new Manager; delete p; }

// problms (a pontos tpus ismerete elvsz)

Ekkor a fordtprogram nem fogja tudni a pontos mretet. A tmb trlshez hasonlan itt is a programoz segtsgre van szksg; az Employee bzisosztlyban meg kell adni egy virtulis destruktort:

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

555

class Employee { public: void* operator new(size_t); void operator delete(void*, size_t); virtual ~Employee(); // ... };

Akr egy res destruktor is megteszi:


Employee::~Employee() { }

A memria felszabadtst a (mretet ismer) destruktor vgzi. St ha az Employee-ben van destruktor, akkor ez biztostja, hogy minden belle szrmaztatott osztlynak lesz destruktora (s gy tudja a mretet), akkor is, ha a szrmaztatott osztlyban nem szerepel felhasznli destruktor:
void f() { Employee* p = new Manager; delete p; // most mr j (az Employee tbbalak) }

A memria lefoglalsa egy (a fordtprogram ltal ltrehozott) hvssal trtnik:


Employee::operator new(sizeof(Manager))

A felszabadtsrl szintn egy (a fordtprogram ltal ltrehozott) hvs gondoskodik:


Employee::operator delete(p,sizeof(Manager))

Vagyis ha olyan memriafoglal/felszabadt prt akarunk rni, amely szrmaztatott osztlyokkal is jl mkdik, akkor vagy virtulis destruktort kell rni a bzisosztlyban, vagy nem szabad felhasznlni a felszabadtban a size_t paramtert. Termszetesen meg lehetett volna gy tervezni a nyelvet, hogy ne legyen szksg ilyen megfontolsokra, de ez csak a kevsb biztonsgos rendszerekben lehetsges optimalizlsok elnyeirl val lemonds rn trtnhetett volna.

Forrs: http://www.doksi.hu

556

Absztrakcis mdszerek

15.6.1. Memriafoglals tmbk szmra


Az operator new() s az operator delete() fggvnyek lehetv teszik, hogy a programoz vgezze az egyes objektumok szmra a memriafoglalst s -felszabadtst. Az operator new[ ]() s az operator delete[ ]() pontosan ugyanezt a szerepet jtssza a tmbk esetben:
class Employee { public: void* operator new[ ](size_t); void operator delete[ ](void*); // ... }; void f(int s) { Employee* p = new Employee[s]; // ... delete[ ] p; }

Itt a szksges memrit a


Employee::operator new[ ](sizeof(Employee)*s+delta)

hvs fogja biztostani, ahol a delta az adott fordttl fgg minimlis tbblet. A memrit az albbi hvs szabadtja fel:
Employee::operator delete[ ](p); // felszabadt s*sizeof(Employee)+delta bjtot

Az elemek s szmt (illetve a delta-t) a rendszer megjegyzi. Ha a delete[ ]()-et egy- helyett ktparamteres formban adtuk volna meg, a hvsban a msodik paramter s*sizeof(Employee) +delta lett volna.

15.6.2. Virtulis konstruktorok


Miutn virtulis destruktorokrl hallottunk, nyilvnval a krds: lehetnek-e a konstruktorok is virtulisak?. A rvid vlasz az, hogy nem. A kicsit hosszabb: nem, de a kvnt hats knnyen elrhet. Egy objektum ltrehozshoz a konstruktornak tudnia kell a ltrehozand objektum pontos tpust. Ezrt a konstruktor nem lehet virtulis. Radsul a konstruktor nem egszen olyan, mint a kznsges fggvnyek. Pldul olyan mdokon mkdik egytt a memriakezel eljrsokkal, ahogy a kznsges fggvnyek nem. Ezrt nincs konstruktorra hivatkoz mutat.

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

557

Mindkt megszorts megkerlhet egy konstruktort meghv s a ltrehozott objektumot visszaad fggvny ksztsvel. Ez kedvez, mert gyakran hasznos lehet, ha a pontos tpus ismerete nlkl tudunk j objektumot ltrehozni. Az Ival_box_maker osztly (12.4.4) pontosan erre a clra szolglt. Itt az tlet egy msik vltozatt mutatom be, ahol egy osztly objektumai a felhasznliknak kpesek sajt maguk msolatt tadni:
class Expr { public: Expr(); Expr(const Expr&);

// alaprtelmezett konstruktor // msol konstruktor

};

virtual Expr* new_expr() { return new Expr(); } virtual Expr* clone() { return new Expr(*this); } // ...

Mivel a new_expr()-hez s a clone()-hoz hasonl fggvnyek virtulisak s kzvetett ton objektumokat hoznak ltre, gyakran hvjk ket virtulis konstruktornak, br az elnevezs nmileg flrevezet. Mindegyik egy konstruktort hasznl, hogy megfelel objektumot hozzon ltre. Egy szrmaztatott osztly fellrhatja a new_expr() s/vagy clone() fggvnyt, hogy egy sajt tpus objektumot adjon vissza:
class Cond : public Expr { public: Cond(); Cond(const Cond&); Cond* new_expr() { return new Cond(); } Cond* clone() { return new Cond(*this); } // ...

};

Ez azt jelenti, hogy egy Expr osztly objektumhoz a felhasznl pontosan ugyanolyan tpus objektumot tud ltrehozni:
void user(Expr* p) { Expr* p2 = p->new_expr(); // ... }

A p2-hz rendelt mutat megfelel, br ismeretlen tpus.

Forrs: http://www.doksi.hu

558

Absztrakcis mdszerek

A Cond::new_expr() s a Cond::clone() visszatrsi tpusa Cond* s nem Expr* , ezrt egy Cond informciveszts nlkl lemsolhat (klnozhat):
void user2(Cond* pc, Expr* pe) { Cond* p2 = pc->clone(); Cond* p3 = pe->clone(); // ... }

// hiba

A fellr fggvnyek tpusa ugyanaz kell, hogy legyen, mint a fellrt virtulis fggvny, de a visszatrsi rtk tpusa kevsb szigoran kttt. Vagyis ha az eredeti visszatrsi rtk B* volt, akkor a fellr fggvny visszatrsi rtke lehet D* is, feltve, hogy B nyilvnos bzisosztlya D-nek. Ugyangy egy B& visszatrsi rtk D&-ra mdosthat. Jegyezzk meg, hogy a paramtertpusok hasonl mdostsa tpushibhoz vezetne (lsd 15.8[12]).

15.7. Tancsok
[1] Ha tulajdonsgok unijt akarjuk kifejezni, hasznljunk kznsges tbbszrs rkldst. 15.2, 15.2.4. [2] A tulajdonsgoknak a megvalst kdtl val elvlasztsra hasznljunk tbbszrs rkldst. 15.2.5. [3] Hasznljunk virtulis bzisosztlyt, ha egy hierarchia nmely (de nem mindegyik) osztlyra nzve kzs dolgot akarunk kifejezni. 15.2.5. [4] Kerljk a tpusknyszertst (cast). 15.4.5. [5] Ha az adott osztlyhierarchia bejrsa elkerlhetetlen, hasznljuk a dynamic_cast-ot. 15.4.1. [6] A typeid helyett rszestsk elnyben a dynamic_cast-ot. 15.4.4. [7] A protected-del szemben rszestsk elnyben a private-et. 15.3.1.1. [8] Adattagokat ne adjunk meg vdettknt. 15.3.1.1. [9] Ha egy osztly definilja az operator delete()-et, akkor legyen virtulis destruktora. 15.6. [10] A konstruktor vagy destruktor futsa alatt ne hvjunk virtulis fggvnyt. 15.4.3. [11] Ritkn s lehetleg csak fellr virtulis fggvnyekben hasznljuk a tagnevek explicit minstst felolds cljra. 15.2.1.

Forrs: http://www.doksi.hu

15. Osztlyhierarchik

559

15.8. Gyakorlatok
1. (*1) rjunk olyan ptr_cast sablont, amely ugyangy mkdik, mint a dynamic_cast, de a 0-val val visszatrs helyett bad_cast kivtelt vlt ki. 2. (*2) rjunk olyan programot, amely egy objektum ltrehozsa kzben az RTTIhez viszonytva mutatja be a konstruktorhvsok sorrendjt. Hasonlan mutassuk be az objektum lebontst. 3. (*3.5) rjuk meg a Reversi/Othello trsasjtk egy vltozatt. Minden jtkos lehessen l szemly vagy szmtgp. Elszr a program helyes mkdsre sszpontostsunk, s csak azutn arra, hogy a program annyira okos legyen, hogy rdemes legyen ellene jtszani. 4. (*3) Javtsunk a 15.8[3]-beli jtk felhasznli felletn. 5. (*3) Ksztsnk egy grafikus objektumosztlyt egy mvelethalmazzal, amely alapjn grafikus objektumok egy knyvtra szmra kzs bzisosztly lehet. Nzznk bele egy grafikus knyvtrba, hogy ott milyen mveletek vannak. Hozzunk ltre egy adatbzis-objektum osztlyt egy mvelethalmazzal, amely alapjn elhihet, hogy kzs bzisosztlya az adatbzisban mezk soraknt trolt objektumoknak. Vizsgljunk meg egy adatbzis-knyvtrat, hogy ott milyen mveletek vannak. Hatrozzunk meg egy grafikus adatbzis-objektumot tbbszrs rkldssel s anlkl, s hasonltsuk ssze a kt megolds elnyeit s htrnyait. 6. (*2) rjuk meg a 15.6.2-beli clone() mvelet egy olyan vltozatt, amely a paramterben megkapott Arena-ba (10.4.11) teszi a lemsolt objektumot. Ksztsnk egy egyszer Arena-t mint az Arena-bl szrmaz osztlyt. 7. (*2) Anlkl, hogy belenznnk a knyvbe, rjunk le annyi C++-kulcsszt, amennyit csak tudunk. 8. (*2) rjunk szabvnyos C++-programot, amelyben legalbb tz klnbz kulcssz szerepel egyms utn, gy, hogy nincs azonostkkal, opertorokkal vagy rsjelekkel elvlasztva. 9. (*2.5) Rajzoljuk le a 15.2.4-beli Radio objektum memriakiosztsnak egy lehetsges vltozatt. Magyarzzuk el, hogyan lehetne egy virtulis fggvnyt meghvni. 11. (*3) Gondoljuk meg, hogyan lehet a dynamic_cast-ot megvalstani. Ksztsnk egy dcast sablont, amely gy viselkedik, mint a dynamic_cast, de csak az ltalunk meghatrozott fggvnyekre s adatokra tmaszkodik. Gondoskodjunk arrl, hogy a rendszert a dcast vagy az elzleg megadott osztlyok megvltoztatsa nlkl lehessen j osztlyokkal bvteni. 12. (*2) Tegyk fel, hogy a fggvnyparamterek tpus-ellenrzsi szablyai a visszatrsi rtkre vonatkozakhoz hasonlan enyhtettek, teht egy Derived* paramter fggvny fellrhat egy Base*-ot. rjunk egy programot, ami konverzi nlkl elrontana egy Derived tpus objektumot. rjuk le a paramtertpusokra vonatkoz fellrsi szablyok egy biztonsgos enyhtst.

Forrs: http://www.doksi.hu

Harmadik rsz A standard knyvtr

Ebben a rszben a C++ standard knyvtrt mutatjuk be. Megvizsgljuk a knyvtr szerkezett s azokat az alapvet mdszereket, amelyeket az egyes elemek megvalstshoz hasznltak. Clunk az, hogy megrtsk, hogyan kell hasznlni ezt a knyvtrat, illetve szemlltessk azokat az ltalnos mdszereket, amelyeket tervezskor s programozskor hasznlhatunk. Ezenkvl bemutatjuk azt is, hogyan bvthetjk a knyvtrat, pontosabban a rendszer fejleszti hogyan kpzeltk el a tovbbfejlesztst.

Fejezetek 16. A knyvtr szerkezete s a trolk 17. Szabvnyos trolk 18. Algoritmusok s fggvnyobjektumok 19. Bejrk s memriafoglalk 20. Karakterlncok 21. Adatfolyamok 22. Szmok

Forrs: http://www.doksi.hu

...s te, Marcus, oly sok mindent adtl nekem, m n adok neked most egy jtancsot. Sok ember lgy. Hagyd el a rgi jtkot, hogy mindig csak ugyanaz a Marcus Cocoza vagy. Tlontl sokat vesszdtl mr Marcus Cocozval, mg vgl valsggal a rabszolgja lettl. Szinte semmit sem teszel anlkl, hogy ne mrlegelnd, vajon milyen hatssal lesz Marcus Cocoza boldogsgra s tekintlyre. Szntelen flelemben lsz, hogy Marcus Cocoza esetleg valami ostobasgot kvet el, vagy elunja magt. Deht mit szmt mindez valjban? Az emberek az egsz vilgon ostobasgokat mvelnek... Szeretnm ha felszabadulnl, ha szved jra megtalln bkjt. Mostantl fogva ne csak egy, hanem tbb ember lgy, annyi, amennyit csak el tudsz gondolni... (Karen Blixen: lmodozk; Kertsz Judit fordtsa)

Forrs: http://www.doksi.hu

16
A knyvtr szerkezete s a trolk
jdonsg volt. Egyedi volt. Egyszer volt. Biztos, hogy sikeres lesz! (H. Nelson) Tervezsi szempontok a standard knyvtrhoz A knyvtr szerkezete Szabvnyos fejllomnyok Nyelvi tmogats A trolk szerkezete Bejrk Bzisosztllyal rendelkez trolk Az STL troli vector Bejrk Elemek elrse Konstruktorok Mdostk Listamveletek Mret s kapacits vector<bool> Tancsok Gyakorlatok

16.1. A standard knyvtr


Minek kell szerepelnie a C++ standard knyvtrban? A programoz szempontjbl az tnik idelisnak, ha egy knyvtrban megtall minden olyan osztlyt, fggvnyt, s sablont, ami rdekes, jelents s elgg ltalnos. A krds azonban most nem az, hogy egy knyvtrban mi legyen benne, hanem az, hogy a standard knyvtr milyen elemeket tartalmazzon. Az elbbi krds esetben sszer megkzelts, hogy minden elrhet legyen, az utbbi esetben azonban ez nem alkalmazhat. A standard knyvtr olyan eszkz, amelyet minden C++-vltozat tartalmaz, gy minden programoz szmthat r.

Forrs: http://www.doksi.hu

564

A standard knyvtr

A C++ standard knyvtra a kvetkez szolgltatsokat nyjtja: 1. Tmogatja a nyelv lehetsgeinek hasznlatt, pldul a memriakezelst (6.2.6) s a futsi idej tpusinformci (RTTI, 15.4) kezelst. 2. Informcikat ad az adott nyelvi vltozat egyedi tulajdonsgairl, pldul megadja a legnagyobb float rtket (22.2). 3. Elrhetv teszi azokat a fggvnyeket, amelyeket nem lehet a nyelven bell optimlisan elkszteni minden rendszer szmra (pldul sqrt(), 22.3 vagy memmove(), 19.4.6). 4. Olyan magas szint szolgltatsokat nyjt, amelyekre a programoz ms rendszerre tvihet (hordozhat) programok ksztsekor tmaszkodhat, pldul listkat (17.2.2), asszociatv tmbket (map) (17.4.1), rendez fggvnyeket (18.7.1) s bemeneti/kimeneti adatfolyamokat (21. fejezet). 5. Keretet biztost a knyvtr elemeinek tovbbfejlesztsre, pldul szablyokkal s eszkzkkel segti, hogy a felhasznl ugyanolyan bemeneti/kimeneti felletet biztosthasson sajt tpusaihoz, mint a knyvtr a beptett tpusokhoz. 6. Tovbbi knyvtrak kzs alapjul szolgl. Nhny tovbbi eszkzt pldul a vletlenszm-ellltkat (22.7) csak azrt helyeztek a standard knyvtrba, mert gy hasznlatuk knyelmes s hagyomnyosan itt a helyk. A knyvtr tervezsekor leginkbb az utols hrom szerepkrt vettk figyelembe. Ezek a szerepek ersen sszefggnek. A hordozhatsg pldul olyan ltalnos fogalom, amely fontos tervezsi szempont minden egyedi cl knyvtr esetben. A kzs troltpusok (pldul a listk vagy asszociatv tmbk) pedig igen jelentsek a kln fejlesztett knyvtrak knyelmes egyttmkdsnek biztostsban. Az utols pont klnsen fontos a tervezs szempontjbl, mert ezzel korltozhat a standard knyvtr hatkre s gtat szabhatunk a szolgltatsok znnek. A karakterlnc- s listakezel lehetsgek pldul a standard knyvtrban kaptak helyet. Ha ez nem gy trtnt volna, akkor a kln fejlesztett knyvtrak csak a beptett tpusok segtsgvel mkdhetnnek egytt egymssal. Ugyanakkor a mintailleszt s a grafikai lehetsgek nem szerepelnek itt, mert br tagadhatatlanul szles krben hasznlatosak nem kimondottan a kln fejlesztett knyvtrak kztti egyttmkdst szolgljk. Ha egy lehetsg nem felttlenl szksges a fenti szerepkrk teljestshez, akkor azt megvalsthatjuk kln, a standard knyvtron kvl is. Azzal, hogy kihagyunk egy szolgltatst a standard knyvtrbl, azt a lehetsget is nyitva hagyjuk a tovbbi knyvtrak szmra, hogy ugyanazt az tletet ms-ms formban valstsk meg.

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

565

16.1.1. Tervezsi korltozsok


A standard knyvtrak szerepkreibl szmos korltozs kvetkezik a knyvtr szerkezetre nzve. A C++ standard knyvtra ltal knlt szolgltatsok tervezsekor az albbi szempontokat tartottk szem eltt: 1. Komoly segtsget jelentsen lnyegben minden tanul s profi programoz szmra, kzjk rtve a tovbbi knyvtrak fejlesztit is. 2. Kzvetve vagy kzvetlenl minden programoz felhasznlhassa az sszes olyan clra, ami a knyvtr hatskrbe esik. 3. Elg hatkony legyen ahhoz, hogy a ksbbi knyvtrak ellltsban komoly vetlytrsa legyen a sajt kezleg ellltott fggvnyeknek, osztlyoknak s sablonoknak. 4. Mentes legyen az eljrsmd szablyozstl, vagy lehetsget adjon r, hogy a felhasznl paramterknt hatrozza meg az eljrsmdot. 5. Legyen primitv, a matematikai rtelemben. Egy olyan sszetev, amely kt, gyengn sszefgg feladatkrt tlt be, kevsb hatkony, mint kt nll komponens, amelyeket kimondottan az adott szerep betltsre fejlesztettek ki. 6. Legyen knyelmes, hatkony s elg biztonsgos a szoksos felhasznlsi terleteken. 7. Nyjtson teljeskr szolgltatsokat ahhoz, amit vllal. A standard knyvtr fontos feladatok megvalstst is rhagyhatja ms knyvtrakra, de ha egy feladat teljestst vllalja, akkor elegend eszkzt kell biztostania ahhoz, hogy az egyes felhasznlknak s fejlesztknek ne kelljen azokat ms mdszerekkel helyettestenik. 8. Legyen sszhangban a beptett tpusokkal s mveletekkel s biztasson azok hasznlatra. 9. Alaprtelmezs szerint legyen tpusbiztos (mindig helyesen kezelje a klnbz tpusokat). 10. Tmogassa az ltalnosan elfogadott programozsi stlusokat. 11. Legyen bvthet gy, hogy a felhasznl ltal ltrehozott tpusokat hasonl formban kezelhessk, mint a beptett s a standard knyvtrban meghatrozottakat. Rossz megolds pldul, ha egy rendez fggvnybe beptjk az sszehasonltsi mdszert, hiszen ugyanazok az adatok ms-ms szempont szerint is rendezhetk. Ezrt a C standard knyvtrnak qsort() fggvnye paramterknt veszi t az sszehasonltst vgz fggvnyt, s nem valamilyen rgztett mveletre (pldul a < opertorra) hivatkozik (7.7). Msrszrl ebben a megoldsban minden egyes sszehasonlts alkalmval meg kell hv-

Forrs: http://www.doksi.hu

566

A standard knyvtr

nunk egy fggvnyt, ami tbbletterhet jelent a qsort() eljrst felhasznl tovbbi eljrsokban. Szinte minden adattpus esetben knnyen elvgezhetjk az sszehasonltst, anlkl, hogy fggvnyhvssal rontannk a rendszer teljestmnyt. Jelents ez a teljestmnyromls? A legtbb esetben valsznleg nem. Egyes algoritmusok esetben azonban ezek a fggvnyhvsok adjk a vgrehajtsi id jelents rszt, ezrt ilyenkor a felhasznlk ms megoldsokat fognak keresni. Ez a problmt a 13.4 pontban oldottuk meg, ahol bemutattuk, hogyan adhatunk meg sszehasonltsi felttelt egy sablonparamterrel. A plda szemlltette, hogy a hatkonysg s az ltalnossg kt egymssal szemben ll kvetelmny. Egy szabvny knyvtrnak azonban nem csak meg kell valstania feladatait, hanem elg hatkonyan kell azokat elvgeznie ahhoz, hogy a felhasznlknak eszbe se jusson sajt eljrsokat rni az adott clra. Ellenkez esetben a bonyolultabb eszkzk fejleszti knytelenek kikerlni a standard knyvtr szolgltatsait, hiszen csak gy maradhatnak versenykpesek. Ez pedig nem csak a knyvtrak fejlesztinek okoz sok tbbletmunkt, hanem azon felhasznlk lett is megnehezti, akik platformfggetlen programokat szeretnnek kszteni vagy tbb, kln fejlesztett knyvtrat szeretnnek hasznlni. A primitvsg s a knyelmessg a szoksos felhasznlsi terleteken kvetelmnyek is szemben llnak egymssal. Egy szabvny knyvtrban az els kvetelmny azonnal kizr minden optimalizlsi lehetsget arra nzve, hogy felkszljnk a gyakori esetekre. Az olyan sszetevk azonban, amelyek ltalnos, de nem primitv szolgltatsokat nyjtanak, szerepelhetnek a standard knyvtrban a primitvek mellett, de nem helyettk. A klcsns kizrs nem akadlyozhat bennnket abban, hogy mind a profi, mind az alkalmi programoz lett egyszerbb tegyk. Azt sem engedhetjk meg, hogy egy sszetev alaprtelmezett viselkedse homlyos vagy veszlyes legyen.

16.1.2. A standard knyvtr szerkezete


A standard knyvtr szolgltatsait az std nvtrben definiltk s fejllomnyok segtsgvel rhetjk el azokat. Ezek a fejllomnyok jelzik a knyvtr legfontosabb rszeit, gy felsorolsukbl ttekintst kaphatunk a knlt eszkzkrl. Ezek szerint nzzk vgig a knyvtrat a most kvetkez fejezetekben is. Ezen alfejezet tovbbi rszben a fejllomnyokat soroljuk fel, szerepeik szerint csoportostva. Mindegyikrl adunk egy rvid lerst is s megemltjk, hol tallhat rszletes elemzsk. A csoportostst gy vlasztottuk meg, hogy illeszkedjen a standard knyvtr szerkezethez. Ha a szabvnyra vonatkoz hivatkozst adunk meg (pldul s.18.1), akkor az adott lehetsget itt nem vizsgljuk rszletesen.

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

567

Ha egy szabvnyos fejllomny neve c betvel kezddik, akkor az egy szabvnyos C knyvtrbeli fejllomny megfelelje. Minden <X.h> fejllomnyhoz, amely a C standard knyvtrnak rszeknt neveket ad meg a globlis nvtrben, ltezik egy <cX> megfelel, amely ugyanazon neveket az std nvtrbe helyezi (9.2.2).

Trolk <vector> <list> <deque> <queue> <stack> <map> <set> <bitset> T tpus elemek egydimenzis tmbje T tpus elemek ktirny listja T tpus elemek ktvg sora T tpus elemekbl kpzett sor T tpus elemekbl kpzett verem T tpus elemek asszociatv tmbje T tpus elemek halmaza logikai rtkek tmbje 16.3 17.2.2 17.2.3 17.3.2 17.3.1 17.4.1 17.4.3 17.5.3

A multimap s multiset asszociatv trolkat sorrendben a <map>, illetve a <set> llomnyban tallhatjuk meg. A priority_queue osztly a <queue> fjlban szerepel.

ltalnos eszkzk <utility> <functional> <memory> <ctime> opertorok s prok fggvnyobjektumok memriafoglalk a trolkhoz C stlus dtum- s idkezels 17.1.4, 17.4.1.2 18.4 19.4.4 s.20.5

A <memory> fejllomny tartalmazza az auto_ptr sablont is, amely elssorban arra hasznlhat, hogy simbb tegyk a mutatk s kivtelek egyttmkdst (14.4.2).

Bejrk <iterator> bejrk s kezelsk 19. fejezet

Forrs: http://www.doksi.hu

568

A standard knyvtr

A bejrk (iterator) lehetv teszik, hogy a szabvnyos algoritmusokat ltalnosan hasznlhassuk a szabvnyos trolkban s ms hasonl tpusokban (2.7.2, 19.2.1).

Algoritmusok <algorithm> <cstdlib> ltalnos algoritmusok bsearch() qsort() 18.fejezet 18.11

Egy szokvnyos ltalnos algoritmus egyformn alkalmazhat brmilyen tpus elemek brmilyen sorozatra (3.8, 18.3). A C standard knyvtrban szerepl bsearch() s qsort() fggvnyek csak a beptett tmbkre hasznlhatk, melyek elemeihez a felhasznl nem hatrozhat meg sem msol konstruktort, sem destruktort (7.7).

Ellenrzsek, diagnosztika <exception> <stdexcept> <cassert> <cerrno> kivtelosztly szabvnyos kivtelek hibaellenrz makr (felttelezett rtk biztostsa) C stlus hibakezels 14.10 14.10 24.3.7.2 20.4.1

A kivtelkezelsre tmaszkod hibaellenrzst a 24.3.7.1 pontban vizsgljuk meg.

Karakterlncok <string> <cctype> <cwtype> <cstring> <cwchar> <cstdlib> T tpus elemekbl ll karakterlnc karakterek osztlyozsa szles karakterek osztlyozsa C stlus karakterlncokat kezel fggvnyek C stlus szles karakterlncok kezelse C stlus karakterlncokat kezel fggvnyek 20. fejezet 20.4.2 20.4.2 20.4.1 20.4 20.4.1

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

569

A <cstring> fejllomnyban szerepelnek az strlen(), strcpy() stb. fggvnyek. A <cstdlib> adja meg az atof() s atoi() fggvnyeket, amelyek a C stlus karakterlncokat alaktjk szmrtkekre.

Ki- s bemenet <iosfwd> <iostream> <ios> <streambuf> <istream> <ostream> <iomanip> <sstream> <cstdlib> <fstream> <cstdio> <cwchar> elzetes deklarcik az I/O szolgltatsokhoz szabvnyos bemeneti adatfolyamok objektumai s mveletei bemeneti adatfolyamok bzisosztlyai tmeneti tr adatfolyamokhoz bemeneti adatfolyam sablon kimeneti adatfolyam sablon adatfolyam-mdostk (manipultorok) adatfolyamok karakterlncbl/karakterlncba karakterosztlyoz fggvnyek adatfolyamok fjlokbl/fjlokba a printf() fggvnycsald printf() szolgltatsok szles karakterekre 21.1 21.2.1 21.2.1 21.6 21.3.1 21.2.1 21.4.6.2 21.5.3 20.4.2 21.5.1 21.8 21.8

Az adatfolyam-mdostk (manipulator) olyan objektumok, melyek segtsgvel az adatfolyamok llapott megvltoztathatjuk (21.4.6). (Pldul mdosthatjuk a lebegpontos szmok kimeneti formtumt.)

Nemzetkzi szolgltatsok <locale> <clocale> kulturlis eltrsek brzolsa 21.7 kulturlis eltrsek brzolsa C stlusban 21.7

A locale az olyan kulturlis eltrsek meghatrozsra szolgl, mint a dtumok rsmdja, a pnzegysgek jellsre hasznlt szimblumok vagy a karakterlncok rendezsre vonatkoz szablyok, melyek a klnbz termszetes nyelvekben s kultrkban jelentsen eltrhetnek.

Forrs: http://www.doksi.hu

570

A standard knyvtr

A programnyelvi elemek tmogatsa <limits> <climits> <cfloat> <new> <typeinfo> <exception> <cstddef> <cstdarg> <csetjmp> <cstdlib> <ctime> <csignal> numerikus rtkhatrok C stlus, numerikus, skalr rtkhatrokat megad makrk C stlus, numerikus, lebegpontos rtkhatrokat megad makrk dinamikus memriakezels futsi idej tpusazonosts tmogatsa kivtelkezels tmogatsa C knyvtrak nyelvi tmogatsa vltoz hosszsg paramterlistval rendelkez fggvnyek kezelse C stlus verem-visszatekers programbefejezs rendszerra C stlus szignlkezels 22.2 22.2.1 22.2.1 16.1.3 15.4.1 14.10 6.2.1 7.6 s.18.7 9.4.1.1 D.4.4.1 D.4.4.1

A <cstddef> fejllomny hatrozza meg azt a tpust, amit a sizeof() fggvny visszaad (size_t), a mutatknak egymsbl val kivonsakor keletkez rtk tpust (ptrdiff_t) s a hrhedt NULL makrt (5.1.1).

Numerikus rtkek <complex> <valarray> <numeric> <cmath> <cstdlib> komplex szmok s mveletek numerikus vektorok s mveletek ltalnostott numerikus mveletek ltalnos matematikai mveletek C stlus vletlenszmok 22.5 22.4 22.6 22.3 22.7

A hagyomnyt kvetve az abs(), s div() fggvny a <cstdlib> fejllomnyban tallhat, br matematikai fggvnyek lvn jobban illennek a <cmath> fjlba. (22.3) A felhasznlk s a knyvtrak fejleszti nem bvthetik vagy szkthetik a szabvnyos fejllomnyokban szerepl deklarcik krt. A fejllomnyok jelentst gy sem mdosthatjuk, hogy megprblunk makrkat megadni az llomnyok beszerkesztse eltt vagy megvltoztatjuk a deklarcik jelentst azzal, hogy sajt deklarcikat ksztnk a fejllo-

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

571

mny krnyezetben (9.2.3). Brmely program, amely nem tartja be ezeket a jtkszablyokat, nem nevezheti magt a szabvnyhoz igazodnak, s az ilyen trkkket alkalmaz programok ltalban nem vihetk t ms rendszerre. Lehet, hogy ma mkdnek, de az adott krnyezet legkisebb vltozsa hasznlhatatlann teheti ket. Tartzkodjunk az ilyen szemfnyvesztstl. Ahhoz, hogy a standard knyvtr valamely lehetsget hasznlhassuk, a megfelel fejllomnyt be kell ptennk (#include). Nem jelent a szabvnynak megfelel megoldst, ha a megfelel deklarcikat sajt kezleg adjuk meg programunkban. Ennek oka az, hogy bizonyos nyelvi vltozatok a beptett szabvnyos fejllomnyok alapjn optimalizljk a fordtst, msok pedig a standard knyvtr szolgltatsait optimalizljk, attl fggen, milyen fejllomnyokat hasznltunk. ltalban igaz, hogy a fejlesztk olyan formban hasznlhatjk a szabvnyos fejllomnyokat, amelyre a programozk nem kszlhetnek fel s programjaik ksztsekor nem is kell tudniuk ezek szerkezetrl. Ezzel szemben a programoz specializlhatja a szolgltats-sablonokat, pldul a swap() sablon fggvnyt (16.3.9), gy ezek jobban igazodhatnak a nem szabvnyos knyvtrakhoz s a felhasznli tpusokhoz.

16.1.3. Nyelvi elemek tmogatsa


A standard knyvtrnak egy kis rsze a nyelvi elemek tmogatsval foglalkozik, azaz olyan szolgltatsokat nyjt, amelyekre a program futtatshoz azrt van szksg, mert a nyelv eszkzei ezektl fggen mkdnek. Azon knyvtri fggvnyeket, amelyek a new s delete opertorok kezelst segtik, a 6.2.6, a 10.4.11, a 14.4.4 s a 15.6 pontban mutattuk be. Ezek a <new> fejllomnyban szerepelnek. A futsi idej tpusazonosts elssorban a type_info osztlyt jelenti, amely a <typeinfo> fejllomnyban tallhat s a 15.4.4 pont rt le. A szabvnyos kivtelek osztlyait a 14.10 pontban vizsgltuk, helyk a <new>, a <typeinfo>, az <ios>, az <exception>, illetve az <stdexcept> fejllomnyban van. A program elindtsrl s befejezsrl a 3.2, a 9.4 s a 10.4.9 pontban volt sz.

Forrs: http://www.doksi.hu

572

A standard knyvtr

16.2. A trolk szerkezete


A trol (container) olyan objektum, amely ms objektumokat tartalmaz. Pldakppen megemlthetjk a listkat (list), a vektorokat (vector) s az asszociatv tmbket (map). ltalban lehetsgnk van r, hogy a trolkban objektumokat helyezznk el vagy eltvoltsuk azokat onnan. Termszetesen ez az tlet szmtalan formban megvalsthat. A C++ standard knyvtrnak trolit kt felttel szem eltt tartsval fejlesztettk ki: biztostsk a lehet legnagyobb szabadsgot a felhasznlnak j, egyni trolk fejlesztsben, ugyanakkor nyjtsanak hasonl kezeli felletet. Ez a megkzelts lehetv teszi, hogy a trolk hatkonysga a lehet legnagyobb legyen s a felhasznlk olyan programot rhassanak, amelyek fggetlenek az ppen hasznlt trol tpustl. A trolk tervezi ltalban vagy csak az egyik, vagy csak a msik felttellel foglalkoznak. A standard knyvtrban szerepl trolk s algoritmusok bemutatjk, hogy lehetsges egyszerre ltalnos s hatkony eszkzket ltrehozni. A kvetkezkben kt hagyomnyos troltpus erssgeit s gyengesgeit mutatjuk be, gy megismerkedhetnk a szabvnyos trolk szerkezetvel.

16.2.1. Egyedi cl trolk s bejrk


A legkzenfekvbb megkzelts egy vektor s egy lista megvalstsra az, hogy mindkettt olyan formban ksztjk el, ami legjobban megfelel a tervezett cloknak:
template<class T> class Vector { public: explicit Vector(size_t n); T& operator[ ](size_t); // ... // optimlis // kezdetben n darab, T() rtk elemet trol // indexels

};

template<class T> class List { public: class Link { /* ... */ }; List(); void put(T*); T* get(); }; // ...

// optimlis

// kezdetben res // az aktulis elem el helyezs // az aktulis elem megszerzse

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

573

Mindkt osztly olyan mveleteket knl, amely idelis felhasznlsukhoz, s mindkettben szabadon kivlaszthatjuk a megfelel brzolst, anlkl, hogy ms troltpusokkal foglalkoznnk. Ez lehetv teszi, hogy a mveletek megvalstsa kzel optimlis legyen. Klnsen fontos, hogy a leggyakrabban hasznlt mveletek (teht a put() a List esetben, illetve az operator[ ]() a Vector esetben) egszen rvidek s knnyen optimalizlhatak (pl. helyben (inline) kifejthetk) legyenek. A trolk egyik leggyakoribb felhasznlsi mdja, hogy egyms utn vgiglpkednk a trolban trolt elemeken. Ezt nevezzk a trol bejrsnak, a feladat megvalstshoz pedig ltalban ltrehozunk egy bejr (iterator) osztlyt, amely megfelel az adott trol tpusnak. (11.5 s 11.14[7]) Bizonyos esetekben, amikor a felhasznl bejr egy trolt, nem is akarja tudni, hogy az adatokat tnylegesen egy listban vagy egy vektorban troljuk. Ezekben a helyzetekben a bejrsnak nem szabad attl fggnie, hogy a List vagy a Vector osztlyt hasznltuk. Az idelis az, ha mindkt esetben pontosan ugyanazt a programrszletet hasznlhatjuk. A megoldst a bejr (iterator) osztly jelenti, amely biztost egy krem a kvetkezt mveletet, amely minden trol szmra megvalsthat. Pldul:
template<class T> class Itor { // kzs fellet (absztrakt osztly 2.5.4, 12.3) public: // 0 visszaadsval jelezzk, hogy nincs tbb elem virtual T* first() = 0; virtual T* next() = 0; // mutat az els elemre // mutat a kvetkez elemre

};

Ezt az osztlyt ksbb elkszthetjk kln a Vector s kln a List osztlyhoz:


template<class T> class Vector_itor : public Itor<T> { // vektor-megvalsts Vector<T>& v; size_t index; // az aktulis elem indexrtke public: Vector_itor(Vector<T>& vv) : v(vv), index(0) { } T* first() { return (v.size()) ? &v[index=0] : 0; } T* next() { return (++index<v.size()) ? &v[index] : 0; } }; template<class T> class List_itor : public Itor<T> { List<T>& lst; List<T>::Link p; // lista-megvalsts // az aktulis elemre mutat

Forrs: http://www.doksi.hu

574

A standard knyvtr

public: List_itor(List<T>&); T* first(); T* next(); };

Grafikus formban (szaggatott vonallal jelezve a megvalsts a felhasznlsval kapcsolatot): Vector List

Itor Vector_itor List_itor

A kt bejr (iterator) bels szerkezete jelentsen eltr, de ez a felhasznl szmra lthatatlan marad. Ezek utn brmit bejrhatunk, amire az Itor osztlyt meg tudjuk valstani. Pldul:
int count(Itor<char>& ii, char term) { int c = 0; for (char* p = ii.first(); p; p=ii.next()) if (*p==term) c++; return c; }

Van azonban egy kis problma. A bejrn elvgzend mvelet lehet rendkvl egyszer, mgis mindig vgre kell hajtanunk egy (virtulis) fggvnyhvst. A legtbb esetben ez a teljestmnyromls nem jelent nagy vesztesget az egyb tevkenysgek mellett. A nagyteljestmny rendszerekben azonban gyakran ppen egy egyszer trol gyors bejrsa jelenti a ltfontossg feladatot, s a fggvnyek meghvsa sokkal kltsgesebb lehet, mint egy egsz szmokkal vgzett sszeads vagy egy mutatrtk-szmts (ami a Vector s a List esetben megvalstja a next() fggvnyt). Ezrt a fenti modell nem hasznlhat, vagy legalbbis nem tkletes egy szabvnyos knyvtr szolgltatsaknt.

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

575

Ennek ellenre a trol-bejr szemllet nagyon jl hasznlhat sok rendszer esetben. vekig ez volt programjaim kedvenc megoldsa. Az elnyket s a htrnyokat az albbiakkal foglalhatjuk ssze: + Az nll trolk (container) egyszerek s hatkonyak. + A trolknak nem kell hasonltaniuk egymsra. A bejr (iterator) s a beburkol (wrapper) osztlyok (25.7.1) segtsgvel a kln fejlesztett trolkat is egysges formban rhetjk el. + A kzs felhasznli felletet a bejrk biztostjk (nem egy ltalnos troltpus, 16.2.2). + Ugyanahhoz a trolhoz tbb bejrt is definilhatunk a klnbz ignyeknek megfelelen. + A trolk alaprtelmezs szerint tpusbiztosak s homognek (azaz a trol minden eleme ugyanolyan tpus). Heterogn trolkat gy hozhatunk ltre, hogy olyan mutatkbl hozunk ltre egy homogn trolt, amelyek azonos ssel rendelkez objektumokra mutatnak. + A trolk nem tolakodk (non-intrusive), (azaz nem ignylik, hogy a trol tagjai egy kzs stl szrmazzanak, vagy valamilyen hivatkoz mezvel rendelkezzenek). A nem tolakod trolk jl hasznlhatak a beptett tpusok, vagy a mi hatskrnkn kvl ltrehozott adatszerkezetek esetben. - Minden bejrn keresztli hozzfrs egy virtulis fggvnyhvssal rontja a rendszer hatkonysgt. Az egyszer hozzfrsi eljrsokhoz kpest ez a mdszer komoly idvesztesget is jelenthet. - A bejr-osztlyok hierarchija knnyen ttekinthetetlenn vlhat. - Semmilyen kzs alapot nem tallhatunk a klnbz trolk kztt, mg kevsb a trolkban trolt objektumok kztt. Ez megnehezti az olyan ltalnos feladatokra val felkszlst, mint a perzisztencia (persistence) biztostsa vagy az objektum be- s kivitel. A + jellel az elnyket, - jellel a htrnyokat soroltuk fel. A bejrk ltal biztostott rugalmassgra kln felhvnnk a figyelmet. Egy olyan kzs felhasznli fellet, mint az Itor, akkor is megvalsthat, amikor a trol (esetnkben a Vector s a List) megtervezse s elksztse mr rg megtrtnt. Amikor a trolt tervezzk, ltalban konkrt formban gondolkodunk, pldul egy tmbt vagy egy listt ksztnk. Csak ksbb ltjuk meg az elvont brzols azon lehetsgeit, amelyek egy adott krnyezetben egyarnt alkalmazhatak a tmbkre s a listkra is. Ezt a ksei elvonatkoztatst tulajdonkppen tbbszr is elvgezhetjk. Pldul kpzeljk el, hogy egy halmazt szeretnnk ltrehozni. A halmaz teljesen ms jelleg elvont brzols,

Forrs: http://www.doksi.hu

576

A standard knyvtr

mint az Itor, de ugyanazzal a mdszerrel, amelyet az Itor esetben alkalmaztunk, kszthetnk egy halmaz jelleg felletet is a Vector s a List osztlyhoz: Vektor List

Set

Itor

Vector_set

Vector_itor

List_set

List_itor

Ezrt a ksei elvonatkoztats az absztrakt osztlyok segtsgvel lehetv teszi, hogy ugyanazokat a fogalmakat tbb, klnbz formban is brzolhassuk, s ez akkor is igaz, ha az egyes megvalstsokban semmilyen hasonlsg sincs. A listk s a vektorok esetben mg tallhatunk nhny nyilvnval hasonlsgot, de az Itor felletet akr egy istream szmra is knnyen elkszthetnnk. Elmletileg a listban szerepl utols kt pont jelenti a szemllet igazi htrnyt. Ez azt jelenti, hogy ez a megkzelts mg akkor sem lehet idelis megolds egy szabvnyos knyvtrban, ha a bejrk fggvnyhvsaibl illetve a hasonl trol-felletekbl ered teljestmnyromlst sikerlt is kikszblnnk (amire bizonyos helyzetekben lehetsg van). A nem tolakod (non-intrusive) trolk nhny esetben egy kicsit lassabbak s kicsit tbb helyet ignyelnek, mint a tolakod trolk. n magam ezt nem tartom gondnak; ha mgis az lenne, az Itor-hoz hasonl bejrkat tolakod trolkhoz is elkszthetjk (16.5[11]).

16.2.2. Trolk kzs ssel


Egy tolakod trol elkszthet anlkl is, hogy sablonokat (template) hasznlnnk vagy brmely ms mdon paramtereket adnnk meg a tpushoz:
struct Link { Link* pre; Link* suc; // ... };

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

577

class List { Link* head; Link* curr; public: Link* get(); void put(Link*); // ... };

// az aktulis elem // az aktulis elem eltvoltsa s visszaadsa // beszrs az aktulis elem el

A List itt nem ms, mint Link tpus objektumok listja, azaz olyan objektumok trolsra kpes, amelyek a Link adatszerkezetbl szrmaznak:
class Ship : public Link { /* ... */ }; void f(List* lst) { while (Link* po = lst->get()) { if (Ship* ps = dynamic_cast<Ship*>(po)) { // a Ship-nek tbbalaknak kell lennie // (15.4.1) // a ship hasznlata } else { // hopp, valami mst csinlunk } } }

A Simula ebben a stlusban hatrozta meg szabvnyos trolit, teht ezt a megoldst tekinthetjk az objektumorientlt programozst tmogat nyelvek eredeti szemlletnek. Napjainkban minden objektum kzs bzisosztlynak neve Object vagy valami hasonl. Az Object osztly ltalban szmos ms szolgltatst is nyjt azon kvl, hogy sszekapcsolja a trolkat. Ez a szemllet gyakran (br nem szksgszeren) kiegszl egy ltalnos troltpussal is:
class Container : public Object { public: virtual Object* get(); // az aktulis elem eltvoltsa s visszaadsa virtual void put(Object*); // beszrs az aktulis elem el virtual Object*& operator[ ](size_t); // indexels // ... };

Forrs: http://www.doksi.hu

578

A standard knyvtr

Figyeljk meg, hogy a Container osztlyban szerepl mveletek virtulisak, gy a klnbz trolk sajt ignyeiknek megfelelen fellrhatjk azokat:
class List : public Container { public: Object* get(); void put(Object*); // ... }; class Vector : public Container { public: Object*& operator[ ](size_t); // ... };

Sajnos azonnal jelentkezik egy problma. Milyen mveleteket kell biztostania a Container osztlynak? Csak azok a fggvnyek szerepelhetnek, amelyeket minden trol meg tud valstani, de az sszes trol mvelethalmaznak metszete nevetsgesen szk fellet. St, az az igazsg, hogy az igazn rdekes esetekben ez a metszet teljesen res. Teht gyakorlatilag a tmogatni kvnt troltpusokban szerepl, lnyeges mveletek unijt kell szerepeltetnnk. A szolgltatsokhoz biztostott felletek ilyen unijt kvr vagy bsges felletnek nevezzk. (fat interface, 24.4.3) Vagy e fellet lersban ksztnk valamilyen alaprtelmezett vltozatot a fggvnyekhez, vagy azokat tisztn virtulisakk (pure virtual) tve arra ktelezzk a szrmaztatott osztlyokat, hogy k fejtsk ki a fggvnyeket. Mindkt esetben sok-sok olyan fggvnyt kapunk, amelyek egyszeren futsi idej hibt vltanak ki:
class Container : public Object { public: struct Bad_op { // kivtelosztly const char* p; Bad_op(const char* pp) :p(pp) { } }; virtual void put(Object*) { throw Bad_op("put-hiba"); } virtual Object* get() { throw Bad_op("get-hiba"); } virtual Object*& operator[ ](int) { throw Bad_op("[ ]"); } // ...

};

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

579

Ha vdekezni szeretnnk azon lehetsggel szemben, hogy egy trol nem tmogatja a get() fggvny hasznlatt, el kell kapnunk valahol a Container::Bad_op kivtelt. Ezek utn a korbbi Ship pldt a kvetkez formban valsthatjuk meg:
class Ship : public Object { /* ... */ }; void f1(Container* pc) { try { while (Object* po = pc->get()) { if (Ship* ps = dynamic_cast<Ship*>(po)) { // a ship hasznlata } else { // hopp, valami mst csinlunk } } } catch (Container::Bad_op& bad) { // hopp, valami mst csinlunk } }

Ez gy tlsgosan bonyolult, ezrt a Bad_op kivtel ellenrzst rdemes mshova helyeznnk. Ha szmthatunk r, hogy a kivteleket mshol kezelik, akkor a pldt az albbi formra rvidthetjk:
void f2(Container* pc) { while (Object* po = pc->get()) { Ship& s = dynamic_cast<Ship&>(*po); // a Ship hasznlata } }

Ennek ellenre az az rzsnk, hogy a futsi idej tpusellenrzs stlustalan s nem is tl hatkony. Ezrt inkbb a statikus tpusellenrzs mellett maradunk:
void f3(Itor<Ship>* i) { while (Ship* ps = i->next()) { // a Ship hasznlata } }

Forrs: http://www.doksi.hu

580

A standard knyvtr

A troltervezs objektumok kzs bzisosztllyal megkzeltsnek elnyeit s htrnyait az albbiakkal foglalhatjuk ssze (nzzk meg a 16.5[10] feladatot is): A klnbz trolkon vgzett mveletek virtulis fggvnyhvst eredmnyeznek. - Minden trolt a Container osztlybl kell szrmaztatnunk. Ennek kvetkeztben kvr felletet kell ksztennk, s nagy mrtk elreltsra, illetve futsi idej tpusellenrzsre van szksg. Egy kln fejlesztett trolt egy ltalnos keretbe szortani a legjobb esetben is krlmnyes (16.5[12]). + A kzs Container bzisosztly egyszer megoldst knl olyan trolk fejlesztshez, amelyek hasonl mveleteket biztostanak. - A trolk heterognek s alaprtelmezs szerint nem tpusbiztosak. (Mindssze arra szmthatunk, hogy az elemek tpusa Object *.) Ha szksg van r, sablonok (template) segtsgvel hozhatunk ltre tpusbiztos s homogn trolkat. - A trolk tolakodak (ami esetnkben azt jelenti, hogy minden elemnek az Object osztlybl kell szrmaznia). Beptett tpusokba tartoz objektumokat, illetve a mi hatskrnkn kvl meghatrozott adatszerkezeteket kzvetlenl nem helyezhetnk el bennk. - A trolbl kiemelt elemekre megfelel tpuskonverzit kell alkalmaznunk, mieltt hasznlhatnnk azokat. + A Container s az Object osztly olyan szolgltatsok kialaktshoz hasznlhat, amelyek minden trolra vagy minden objektumra alkalmazhatk. Ez nagymrtkben leegyszersti az olyan ltalnos szolgltatsok megvalstst, mint a perzisztencia biztostsa vagy az objektumok ki- s bevitele. Ugyangy, mint korbban (16.2.1), a + az elnyket, a - a htrnyokat jelli. Az egymstl fggetlen trolkhoz s bejrkhoz viszonytva a kzs bzisosztllyal rendelkez objektumok tlete feleslegesen sok munkt hrt a felhasznlra, jelents futsi idej terhelst jelent s korltozza a trolban elhelyezhet objektumok krt. Radsul sok osztly esetben az Object osztlybl val szrmaztats a megvalsts rszleteinek felfedst jelenti, ezrt ez a megkzelts nagyon tvol ll az idelis megoldstl egy szabvnyos knyvtr esetben. Ennek ellenre az ezen megolds ltal knlt ltalnossgot s rugalmassgot nem szabad albecslnnk. Szmtalan vltozatt, szmtalan programban sikeresen felhasznltk mr. Ennek a megkzeltsnek azokon a terleteken van jelentsge, ahol a hatkonysg kevsb fontos, mint az egyszersg, amelyet az egyszer Container fellet s az objektum kis bevitelhez hasonl szolgltatsok biztostanak. -

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

581

16.2.3. A standard knyvtr troli


A standard knyvtr troli (container) s bejri (iterator) amelyet gyakran neveznk STL (Standard Template Library) keretrendszernek, (3.10) olyan megkzeltsknt foghatk fel, amely a korbban bemutatott kt hagyomnyos modell elnyeit a lehet legjobban kiaknzza. Az STL azonban egyik mdszert sem alkalmazza kzvetlenl; clja az, hogy egyszerre hatkony s ltalnos algoritmusokat knljon, mindenfle megalkuvs nlkl. A hatkonysg rdekben az alkotk a gyakran hasznlt adatelr fggvnyek esetben elvetettk a nehezen optimalizlhat virtulis fggvnyeket, gy nem adhattak szabvnyos felletet a trolk s a bejrk szmra absztrakt osztly formjban. Ehelyett mindegyik troltpus tmogatja az alapvet mveleteknek egy szabvnyos halmazt. A kvr felletek problmjnak (16.2.2, 24.4.3) elkerlse rdekben az olyan mveletek, amelyek nem minden trolban valsthatk meg hatkonyan, nem szerepelnek ebben a kzs halmazban. Az indexels pldul alkalmazhat a vector esetben, de nem hasznlhat a list trolkra. Ezenkvl minden troltpus sajt bejrkat biztost, amelyek a szoksos bejrmveleteket teszik elrhetv. A szabvnyos trolk nem kzs bzisosztlybl szrmaznak, hanem minden trol nllan tartalmazza a szabvnyos trolfelletet. Ugyangy nincs kzs bejr-s sem. A szabvnyos trolk s bejrk hasznlatakor nem trtnik futsi idej tpusellenrzs, sem kzvetlen (explicit), sem automatikus (implicit) formban. A minden trolra kiterjed kzs szolgltatsok biztostsa igen fontos s bonyolult feladat. Az STL ezt a memriafoglalk (alloktor, allocator) segtsgvel valstja meg, amelyeket sablonparamterknt adunk meg (19.4.3), ezrt nincs szksg kzs bzisosztlyra. Mieltt a rszleteket megvizsglnnk s konkrt pldkat mutatnnk be, foglaljuk ssze az STL szemllete ltal biztostott elnyket s htrnyokat: + Az nll trolk egyszerek s hatkonyak (nem egszen olyan egyszerek, mint a tnyleg teljesen fggetlen trolk, de ugyanolyan hatkonyak). + Mindegyik trol biztostja a szabvnyos mveleteket szabvnyos nven s jelentssel. Ha szksg van r, az adott troltpusnak megfelel tovbbi mveletek is megtallhatk. Ezenkvl, a becsomagol vagy beburkol (wrapper) osztlyok (25.7.1) segtsgvel a kln fejlesztett trolk is beilleszthetk a kzs keretrendszerbe. (16.5[14]) + A tovbbi kzs hasznlati formkat a szabvnyos bejrk biztostjk. Minden trol biztost bejrkat, amelyek lehetv teszik bizonyos mveletek vgrehajtst szabvnyos nven s jelentssel. Minden bejr-tpus kln ltezik minden troltpushoz, gy ezek a bejrk a lehet legegyszerbbek s a lehet leghatkonyabbak.

Forrs: http://www.doksi.hu

582

A standard knyvtr

+ A klnbz szksgletek kielgtsre minden trolhoz ltrehozhatunk sajt bejrkat vagy ltalnos felleteket, a szabvnyos bejrk mellett. + A trolk alaprtelmezs szerint tpusbiztosak s homognek (azaz az adott trol minden eleme ugyanolyan tpus). Heterogn trolkat gy kszthetnk, hogy egy olyan homogn trolt hozunk ltre, amely kzs bzisosztlyra hivatkoz mutatkat tartalmaz. + A trolk nem tolakodak (azaz a trol tagjainak nem kell egy adott bzisosztlybl szrmazniuk, vagy hivatkoz mezket tartalmazniuk). A nem tolakod trolk a beptett tpusok esetben hasznlhatk jl, illetve az olyan adatszerkezetekhez, melyeket a mi hatskrnkn kvl hatroztak meg. + Az ltalnos keretrendszer lehetv teszi tolakod trolk hasznlatt is. Termszetesen ez az elemek tpusra nzve komoly megktseket jelenthet. + Minden trol hasznl egy paramtert, az gynevezett memriafoglalt (allocator), amely olyan szolgltatsok kezelshez nyjt segtsget, amelyek minden trolban megjelennek. Ez nagymrtkben megknnyti az olyan ltalnos feladatok megvalstst, mint a perzisztencia biztostsa vagy az objektum ki- s bevitel.(19.4.3) - A trolk s bejrk nem rendelkeznek olyan szabvnyos, futsi idej brzolssal, amelyet pldul fggvnyparamterknt tadhatnnk (br a szabvnyos trolk s bejrk esetben knnyen ltrehozhatnnk ilyet, ha erre az adott programban szksgnk van, 19.3). Ugyangy, mint eddig (16.2.1) a + az elnyket, a - a htrnyokat jelli. A trolknak (container) s a bejrknak (iterator) teht nincs rgztett, ltalnos brzolsa. Ehelyett minden trol ugyanazt a szabvnyos felletet biztostja szabvnyos mveletek formjban. Ennek kvetkeztben a trolkat egyformn kezelhetjk s fel is cserlhetjk. A bejrkat is hasonlan hasznlhatjuk. Ez az id- s trhasznlati hatkonysgot csak kevss rontja, a felhasznl viszont kihasznlhatja az egysgessget, mind a trolk szintjn (a kzs bzisosztlybl szrmaz trolk esetben), mind a bejrk szintjn (a specializlt trolk esetben). Az STL megkzeltse ersen pt a sablonok (template) hasznlatra. Ahhoz, hogy elkerljk a felesleges kdismtlseket, gyakran van szksg arra, hogy mutatkat tartalmaz trolkban rszlegesen specializlt vltozatok ksztsvel kzsen hasznlhat sszetevket hozunk ltre (13.5).

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

583

16.3. A vektor
Itt a vector-t gy rjuk le, mint a teljes szabvnyos trolk egyik pldjt. Ha mst nem mondunk, a vector-ra vonatkoz lltsok vltoztats nlkl alkalmazhatk az sszes tbbi szabvnyos trolra is. A 17. fejezet foglalkozik azokkal a lehetsgekkel, amelyek kizrlag a list, a set, a map vagy valamelyik msik trolra vonatkoznak. Azokat a lehetsgeket, amelyeket kifejezetten a vector vagy egy hasonl trol valst meg, csak bizonyos mrtkig rszletezzk. Clunk az, hogy megismerjk a vector lehetsges felhasznlsi terleteit s megrtsk a standard knyvtr globlis szerkezetben elfoglalt szerept. A 17.1 pontban ttekintjk a standard trolk tulajdonsgait s az ltaluk knlt lehetsgeket. Az albbiakban a vector trolt klnbz szempontok szerint mutatjuk be: a tagtpusok, a bejrk, az elemek elrse, a konstruktorok, a veremmveletek, a listamveletek, a mret s kapacits, a segdfggvnyek, illetve a vector<bool> szempontjbl.

16.3.1. Tpusok
A szabvnyos vector egy sablon (template), amely az std nvtrhez tartozik s a <vector> fejllomnyban tallhat. Elszr is nhny szabvnyos tpusnevet definil:
template <class T, class A = allocator<T> > class std::vector { public: // tpusok typedef T value_type; typedef A allocator_type; typedef typename A::size_type size_type; typedef typename A::difference_type difference_type; // elemtpus // memriakezel-tpus

typedef megvalsts_fgg1 iterator; typedef megvalsts_fgg2 const_iterator; typedef std::reverse_iterator<iterator> reverse_iterator; typedef std::reverse_iterator<const_iterator> const_reverse_iterator; typedef typename A::pointer pointer; typedef typename A::const_pointer const_pointer; typedef typename A::reference reference; typedef typename A::const_reference const_reference; }; // ...

// T* // const T*

// elemmutat // hivatkozs elemre

Forrs: http://www.doksi.hu

584

A standard knyvtr

Minden szabvnyos trol definilja ezeket a tpusokat, mint sajt tagjait, de a sajt megvalstsnak legmegfelelbb formban. A trol elemeinek tpust az els sablonparamter hatrozza meg, amit gyakran neveznk rtktpusnak (value_type) is. A memriafoglal tpusa (allocator_type) amelyet (nem ktelezen) a sablon msodik paramterben adhatunk meg azt hatrozza meg, hogy a value_type hogyan tart kapcsolatot a klnbz memriakezel eljrsokkal. Ezen bell, a memriafoglal adja meg azokat a fggvnyeket, amelyeket a trol az elemek trolsra szolgl memria lefoglalsra s felszabadtsra hasznl. A memriafoglalkrl a 19.4 pontban lesz sz rszletesen. ltalban a size_type hatrozza meg azt a tpust, amelyet a trol indexelshez hasznlunk, mg a difference_type annak az rtknek a tpust jelli, amelyet kt bejr klnbsgnek kpzsekor kapunk. A legtbb trol esetben ezek a size_t, illetve a ptrdiff_t tpust jelentik (6.2.1) A bejrkat a 2.7.2 pontban mutattuk be s a 19. fejezetben foglalkozunk velk rszletesen. gy kpzelhetjk el ket, mint egy trol valamelyik elemre hivatkoz mutatt. Minden trol ler egy iterator nev tpust, amellyel az elemekre mutathatunk. Rendelkezsnkre ll egy const_iterator tpus is, amelyet akkor hasznlunk, ha nincs szksg az elemek mdostsra. Ugyangy, mint a mutatk esetben, itt is mindig hasznljuk a biztonsgosabb const vltozatot, hacsak nem felttlenl a msik lehetsgre van szksgnk. A vector bejrinak konkrt tpusa a megvalststl fgg, a legnyilvnvalbb megolds egy hagyomnyosan definilt vector esetben a T*, illetve a const T*. A visszafel halad bejrk tpust a vector szmra a szabvnyos reverse_iterator sablon segtsgvel hatroztk meg. (19.2.5) Ez az elemek sorozatt fordtott sorrendben szolgltatja. A 3.8.1 pontban bemutattuk, hogy ezen tpusok segtsgvel a felhasznl gy rhat trolkat hasznl programrszleteket, hogy a tnylegesen hasznlt tpusokrl semmit sem tud, st, olyan kdot is rhat, amely minden szabvnyos trol esetben hasznlhat:
template<class C> typename C::value_type sum(const C& c) { typename C::value_type s = 0; typename C::const_iterator p = c.begin(); // kezds az elejn while (p!=c.end()) { // folytats a vgig s += *p; // elem rtknek megszerzse ++p; // p a kvetkez elemre fog mutatni } return s; }

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

585

A typename hasznlata egy sablonparamter tagjnak neve eltt elg furcsn nz ki, de a fordtprogram nem akad fenn rajta, ugyanis nincs ltalnos mdszer arra, hogy egy sablonparamter valamelyik tagjrl eldntsk, hogy az tpusnv-e. (C.13.5) Ugyangy, mint a mutatk esetben, az eltagknt hasznlt * a bejr indirekcijt jelenti (2.7.2, 19.2.1), mg a ++ a bejr nvelst vgzi.

16.3.2. Bejrk
Ahogy az elz alfejezetben mr bemutattuk, a programozk a bejrkat arra hasznlhatjk, hogy bejrjk a trolt az elemek tpusnak pontos ismerete nlkl. Nhny tagfggvny teszi lehetv, hogy elrjk az elemek sorozatnak valamelyik vgt:
template <class T, class A = allocator<T> > class vector { public: // ... // iterators: iterator begin(); const_iterator begin() const; iterator end(); const_iterator end() const; reverse_iterator rbegin(); const_reverse_iterator rbegin() const; reverse_iterator rend(); const_reverse_iterator rend() const; }; // ... // az els elemre mutat // az "utols utni" elemre mutat // htulrl az els elemre mutat // htulrl az "utols utni" elemre mutat

A begin()/end() pr a trol elemeit a szoksos sorrendben adja meg, azaz elsknt a nulladik elemet (vagyis az elst) kapjuk, majd sorban a kvetkezket; az rbegin()/rend() prnl viszont fordtott sorrendben, azaz az n-1. elem utn kvetkezik az n-2., majd az n-3., s gy tovbb. Pldul ha egy iterator hasznlatval ilyen sorozatot kapunk: begin() end()

Forrs: http://www.doksi.hu

586

A standard knyvtr

akkor a reverse_iterator a kvetkez elemsorozatot adja (19.2.5): rbegin() rend()

gy lehetsgnk van olyan algoritmusok ksztsre, amelyeknek fordtott sorrendben van szksge az elemek sorozatra. Pldul:
template<class C> typename C::iterator find_last(C& c, typename C::value_type v) { typename C::reverse_iterator ri = find(c.rbegin(),c.rend(),v); if (ri == c.rend()) return c.end(); // a c.end() jelzi, hogy "nem tallhat" typename C::iterator i = ri.base(); return --i; }

Egy reverse_iterator esetben az ri.base() fggvny egy olyan bejrt ad vissza, amely az ri ltal kijellt hely utn kvetkez elemre mutat. (19.2.5) A visszafel halad bejrk nlkl egy ciklust kellett volna ksztennk:
template<class C> typename C::iterator find_last(C& c, typename C::value_type v) { typename C::iterator p = c.end(); // keress a vgtl visszafel while (p!=c.begin()) if (*--p==v) return p; return c.end(); // a c.end() jelzi, hogy "nem tallhat" }

A visszafel halad bejr is kznsges bejr, teht rhattuk volna ezt is:
template<class C> typename C::iterator find_last(C& c, typename C::value_type v) { typename C::reverse_iterator p = c.rbegin(); // a sorozat tnzse visszafel while (p!=c.rend()) { if (*p==v) { typename C::iterator i = p.base(); return --i; } ++p; // vigyzzunk: nvels, nem cskkents (--) } return c.end(); // a c.end() jelzi, hogy "nem tallhat" }

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

587

Figyeljnk r, hogy a C::reverse_iterator tpus nem ugyanaz, mint a C::iterator.

16.3.3. Az elemek elrse


A vector igen fontos tulajdonsga a tbbi trolval sszehasonltva, hogy az egyes elemeket brmilyen sorrendben knnyen s hatkonyan elrhetjk:
template <class T, class A = allocator<T> > class vector { public: // ... // hozzfrs az elemekhez reference operator[ ](size_type n); // nem ellenrztt hozzfrs const_reference operator[ ](size_type n) const; reference at(size_type n); const_reference at(size_type n) const; reference front(); const_reference front() const; reference back(); const_reference back() const; }; // ... // ellenrztt hozzfrs // els elem // utols elem

Az indexelshez az operator[ ]() vagy az at() fggvnyt hasznljuk. A [ ] opertor ellenrizetlen hozzfrst biztost, mg az at() indexhatr-ellenrzst is vgez s out_of_range kivtelt vlt ki, ha a megadott index nem a megfelel tartomnyba esik:
void f(vector<int>& v, int i1, int i2) try { for(int i = 0; i < v.size(); i++) { // a tartomny mr ellenrztt, itt a nem ellenrztt v[i]-t kell hasznlnunk } v.at(i1) = v.at(i2); // hozzfrskor a tartomny ellenrzse

// ... } catch(out_of_range) { // hopp, kicssztunk a tartomnyon kvlre }

Forrs: http://www.doksi.hu

588

A standard knyvtr

Ez a plda bemutat egy hasznos tletet is: ha az indexhatrokat mr valamilyen mdon ellenriztk, akkor az ellenrizetlen indexel opertort teljes biztonsggal hasznlhatjuk. Ellenkez esetben viszont rdemes az at() fggvnyt alkalmaznunk. Ez a klnbsg fontos lehet olyan programoknl, ahol a hatkonysgra is figyelnnk kell. Ha a hatkonysg nem jelents szempont vagy nem tudjuk egyrtelmen eldnteni, hogy a tartomny ellenrztte, akkor biztonsgosabb ellenrztt [ ] opertorral rendelkez vektort hasznljunk (pldul a Vec-et, 3.7.2), vagy legalbbis ellenrztt bejrt (19.3). A tmbk alaprtelmezett hozzfrsi mdja ellenrizetlen. Biztonsgos (ellenrztt) szolgltatsokat megvalsthatunk egy gyors alaprendszer felett, de gyors szolgltatst lass alaprendszer felett nem. A hozzfrsi mveletek reference vagy const_reference tpus rtket adnak vissza, attl fggen, hogy konstans objektumra alkalmaztuk-e azokat. Az elemek elrsre a referencia megfelel tpus. A vector<X> legegyszerbb s legkzenfekvbb megvalstsban a reference egyszeren X&, mg a const_reference megfelelje a const X&. Ha egy indexhatron kvli elemre prblunk hivatkozni, az eredmny meghatrozhatatlan lesz:
void f(vector<double>& v) { double d = v[v.size()]; list<char> lst; char c = lst.front();

// nem meghatrozhat: rossz indexrtk // nem meghatrozhat: a lista res

A szabvnyos sorozatok kzl csak a vector s a deque (17.2.3) tmogatja az indexelst. Ennek oka az, hogy a rendszer tervezi nem akartk a felhasznlkat alapveten rossz hatsfok mveletek bevezetsvel megzavarni. Az indexels pldul nem alkalmazhat a list tpusra (17.2.2), mert veszlyesen rossz hatsfok eljrs lenne (konkrtabban: O(n)). A front() s back() tagfggvnyek sorrendben az els, illetve az utols elemre hivatkoz referencikat adnak vissza. Akkor igazn hasznosak, ha biztosan tudjuk, hogy lteznek ezek az elemek s programunkban valamirt klnsen fontosak. Ennek gyakori esete, amikor egy vektort veremknt (stack, 16.3.5) akarunk hasznlni. Jegyezzk meg, hogy a front() arra az elemre ad hivatkozst, amelyre a begin() egy bejrt. A front() gy is elkpzelhet, mint maga az els elem, mg a begin() inkbb az els elemre hivatkoz mutat. A back() s az end() kztti kapcsolat egy kicsit bonyolultabb: a back() az utols elem, mg az end() egy mutat az utols utni pozcira.

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

589

16.3.4. Konstruktorok
Termszetesen a vector a konstruktoroknak, destruktoroknak s msol mveleteknek is teljes trhzt knlja:
template <class T, class A = allocator<T> > class vector { public: // ... // konstruktorok, stb. explicit vector(const A& = A()); explicit vector(size_type n, const T& val = T(), const A& = A()); // n darab val template <class In> // az In-nek bemeneti bejrnak kell lennie (19.2.1) vector(In first, In last, const A& = A()); // msols [first:last[-bl vector(const vector& x); ~vector(); vector& operator=(const vector& x); template <class In> // az In-nek bemeneti bejrnak kell lennie (19.2.1) void assign(In first, In last); // msols [first:last[-bl void assign(size_type n, const T& val); // n darab val }; // ...

A vector gyors hozzfrst tesz lehetv tetszleges elemhez, de mretnek mdostsa viszonylag bonyolult. Ezrt ltalban a vector ltrehozsakor megadjuk annak mrett is:
vector<Record> vr(10000); void f(int s1, int s2) { vector<int> vi(s1); } vector<double>* p = new vector<double>(s2);

Az ilyen mdon ltrehozott vektorelemeknek az elemek tpusnak megfelel alaprtelmezett konstruktorok adnak kezdrtket, teht a vr vektornak mind a 10 000 elemre lefut a Record() fggvny, a vi si darab elemnek pedig egy-egy int() hvs ad kezdrtket. Gondoljunk r, hogy a beptett tpusok alaprtelmezett konstruktora az adott tpusnak megfelel 0 rtket adja kezdrtkl. (4.9.5, 10.4.2)

Forrs: http://www.doksi.hu

590

A standard knyvtr

Ha egy tpus nem rendelkezik alaprtelmezett konstruktorral, akkor belle nem hozhatunk ltre vektort, hacsak nem adjuk meg az sszes elem kezdrtkt. Pldul:
class Num { // vgtelen pontossg public: Num(long); // nincs alaprtelmezett konstruktor // ... }; vector<Num> v1(1000); vector<Num> v2(1000,Num(0)); // hiba: nincs alaprtelmezett Num // rendben

Mivel egy vector nem trolhat negatv szm elemet, gy mretnek nemnegatv szmnak kell lennie. Ez azon kvetelmnyben jut kifejezsre, hogy a vektor size_type tpusnak unsigned-nak kell lennie. Ez bizonyos rendszerekben nagyobb vektormret hasznlatt teszi lehetv, egyes esetekben viszont meglepetsekhez is vezethet:
void f(int i) { vector<char> vc0(-1); vector<char> vc1(i); } void g() { f(-1); }

// a fordt erre knnyen figyelmeztethet

// becsapjuk f()-et, hogy elfogadjon egy igen nagy pozitv szmot

Az f(-1) hvsban a -1 rtket egy igen nagy pozitv szmra alaktja a rendszer (C.6.3). Ha szerencsnk van, fordtnk figyelmeztet erre. A vector mrett kzvetve is megadhatjuk, gy, hogy felsoroljuk a kezdeti elemhalmazt. Ezt gy valsthatjuk meg, hogy a konstruktornak azon rtkek sorozatt adjuk t, amelyekbl a vektort fel kell pteni:
void f(const list<X>& lst) { vector<X> v1(lst.begin(),lst.end());

// elemek msolsa listbl

char p[ ] = "despair"; vector<char> v2(p,&p[sizeof(p)-1]); // karakterek msolsa C stlus karakterlncbl

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

591

Minden esetben a vector konstruktora szmtja ki a vektor mrett, mikzben a bemeneti sorozatbl tmsolja az elemeket. A vector azon konstruktorait, melyek egyetlen paramtert ignyelnek, az explicit kulcsszval deklarljuk, gy vletlen konverzik nem fordulhatnak el (11.7.1):
vector<int> v1(10); // rendben: 10 egszbl ll vektor vector<int> v2 = vector<int>(10); // rendben: 10 egszbl ll vektor vector<int> v3 = v2; // rendben: v3 v2 msolata vector<int> v4 = 10; // hiba: automatikus konvertls ksrlete 10-rl vector<int>-re

A msol konstruktor s a msol rtkads a vector elemeit msoljk le. Egy sok elembl ll vektor esetben ez hosszadalmas mvelet lehet, ezrt a vektorokat ltalban referenciaknt adjuk t:
void f1(vector<int>&); void f2(const vector<int>&); void f3(vector<int>); void h() { vector<int> v(10000); // ... f1(v); f2(v); f3(v); // hivatkozs tadsa // hivatkozs tadsa // a 10 000 elem j vektorba msolsa az f3() szmra // szoksos stlus // szoksos stlus // szokatlan stlus

Az assign fggvnyek a tbbparamteres konstruktorok prjainak tekinthetk. Azrt van rjuk szksg, mert az = egyetlen, jobb oldali operandust vr, gy ha alaprtelmezett paramterrtket akarunk hasznlni vagy rtkek teljes sorozatt akarjuk tadni, az assign fggvnyre van szksgnk:
class Book { // ... }; void f(vector<Num>& vn, vector<char>& vc, vector<Book>& vb, list<Book>& lb) { vn.assign(10,Num(0)); // Num(0) tz pldnyt trol vektor rtkl adsa vn-nek

Forrs: http://www.doksi.hu

592

A standard knyvtr

char s[ ] = "literl"; vc.assign(s,&s[sizeof(s)-1]); vb.assign(lb.begin(),lb.end()); } // ...

// a "literl" rtkl adsa vc-nek // listaelemek rtkl adsa

Ezek utn egy vector objektumot feltlthetnk tetszleges elemsorozattal, ami tpus szerint megfelel, s ksbb is hozzrendelhetnk az objektumhoz ilyen sorozatokat. Fontos, hogy ezt anlkl rtk el, hogy nagyszm konstruktort illetve konverzis opertort kellett volna definilnunk. Figyeljk meg, hogy az rtkads teljes egszben lecserli a vektor elemeit. Elmletileg az sszes rgi elemet trljk, majd az j elemeket beillesztjk. Az rtkads utn a vector mrete az rtkl adott elemek szmval egyezik meg:
void f() { vector<char> v(10,'x'); v.assign(5,'a'); // ... }

// v.size()==10, minden elem rtke 'x' // v.size()==5, minden elem rtke 'a'

Termszetesen, amit az assign() csinl, az megvalsthat kzvetetten gy is, hogy elszr ltrehozzuk a kvnt vektort, majd ezt adjuk rtkl:
void f2(vector<Book>& vh, list<Book>& lb) { vector<Book> vt(lb.begin(),lb.end()); vh = vt; // ... }

Ez a megolds azonban nem csak csnya, de rossz hatsfok is lehet. Ha egy vektor konstruktorban kt ugyanolyan tpus paramtert hasznlunk, akkor azt ktflekppen is rtelmezhetnnk:
vector<int> v(10,50); // vector(mret,rtk) vagy vector(bejr1,bejr2)? // vector(mret,rtk)!

Az int nem bejr, gy a megvalstsoknak biztostaniuk kell, hogy a megfelel konstruktor fusson le:
vector(vector<int>::size_type, const int&, const vector<int>::allocator_type&);

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

593

A msik lehetsges konstruktor a kvetkez:


vector(vector<int>::iterator, vector<int>::iterator, const vector<int>::allocator_type&);

A knyvtr ezt a problmt gy oldja meg, hogy megfelel mdon tlterheli a konstruktorokat, de hasonlan kezeli az assign() s az insert() (16.3.6) esetben elfordul tbbrtelmsget is.

16.3.5. Veremmveletek
A vektort ltalban gy kpzeljk el, mint egy egysges adatszerkezetet, melyben az elemeket indexelssel rhetjk el. Ezt a szemlletet azonban el is felejthetjk s a vektort a legelvontabb (legltalnosabb) sorozat megtestestjnek tekinthetjk. Ha erre gondolunk s megfigyeljk, hogy a tmbknek, vektoroknak milyen ltalnos felhasznlsi terletei vannak, nyilvnvalv vlik, hogy a vector osztlyban a veremmveletekre is szksg lehet:
template <class T, class A = allocator<T> > class vector { public: // ... // veremmveletek void push_back(const T& x); void pop_back(); // ... // hozzads a vghez // az utols elem eltvoltsa

};

Ezek a fggvnyek a vektort veremnek tekintik s annak vgn vgeznek mveleteket:


void f(vector<char>& s) { s.push_back('a'); s.push_back('b'); s.push_back('c'); s.pop_back(); if (s[s.size()-1] != 'b') error("Lehetetlen!"); s.pop_back(); if (s.back() != 'a') error("Ennek soha nem szabad megtrtnnie!"); }

Forrs: http://www.doksi.hu

594

A standard knyvtr

Amikor meghvjuk a push_back() fggvnyt, az s vektor mrete mindig n egy elemmel s a paramterknt megadott elem a vektor vgre kerl. gy az s[s.size()-1] elem (ami ugyanaz, mint az s.back() ltal visszaadott rtk, 16.3.3) a verembe legutoljra helyezett elem lesz. Attl eltekintve, hogy a vector szt hasznljuk a stack helyett, semmi szokatlant nem mveltnk. A _back uttag azt hangslyozza ki, hogy az elemet a vektor vgre helyezzk, nem pedig az elejre. Egy j elem elhelyezse a vektor vgn nagyon kltsges mvelet is lehet, hiszen annak trolshoz tovbbi memrit kell lefoglalnunk. Az adott nyelvi vltozatnak azonban biztostania kell, hogy az ismtld veremmveletek csak ritkn okozzanak mretnvekedsbl ered teljestmnycskkenst. Figyeljk meg, hogy a pop_back() fggvny sem ad vissza rtket. Egyszeren trli a legutols elemet s ha azt szeretnnk megtudni, hogy a kiemels (pop) eltt milyen rtk szerepelt a verem tetejn, akkor azt kln meg kell nznnk. Ezt a tpus vermet sokan nem kedvelik (2.5.3, 2.5.4), de ez a megolds hatkonyabb lehet s ez tekinthet a szabvnynak is. Mirt lehet szksg veremszer mveletekre egy vektor esetben? Egy nyilvnval indok, hogy a verem megvalstsra ez az egyik lehetsg (17.3.1), de ennl gyakoribb, hogy a tmbt folyamatosan nvekedve akarjuk ltrehozni. Elkpzelhet pldul, hogy pontokat akarunk beolvasni egy tmbbe, de nem tudjuk, sszesen hny pontot kapunk. Ez esetben arra nincs lehetsgnk, hogy mr kezdetben a megfelel mret tmbt hozzuk ltre s utna csak egyszeren belerjuk a pontokat. Ehelyett a kvetkez eljrst kell vgrehajtanunk:
vector<Point> cities; void add_points(Point sentinel) { Point buf; while (cin >> buf) { if (buf == sentinel) return; // j pont ellenrzse cities.push_back(buf); }

Ez a megolds biztostja, hogy a vector igny szerint nvekedhessen. Ha mindssze annyi a teendnk, hogy a pontokat elhelyezzk a vektorban, akkor a cities adatszerkezetet feltlthetjk kzvetlenl egy konstruktor segtsgvel is (16.3.4), ltalban azonban valamilyen mdon mg fel kell dolgoznunk a berkez adatokat s csak fokozatosan tlthetjk fel a vektort a program elrehaladtval. Ilyenkor hasznlhatjuk a push_back() fggvnyt.

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

595

A C programokban ez a leggyakoribb felhasznlsi terlete a realloc() fggvnynek, amely a C standard knyvtrban szerepel. Ezrt a vektor s ltalban minden szabvnyos trol biztost egy ltalnosabb, elegnsabb, de nem kevsb hatkony vltozatot a realloc() helyett. A vektor mrett (size()) a push_back() automatikusan nveli, gy nem fordulhat el, hogy a vektor tlcsordul (mindaddig, amg memria ignyelhet a rendszertl, 19.4.1). Alulcsorduls ellenben elfordulhat:
void f() { vector<int> v; v.pop_back(); v.push_back(7); }

// nem meghatrozhat hats: v llapota nem // meghatrozhat lesz // nem meghatrozhat hats (v llapota nem // meghatrozhat), valsznleg rossz

Az alulcsorduls kvetkezmnye nem meghatrozhat s a legtbb C++-vltozat esetben olyan memriaterlet fellrst eredmnyezi, amely nem tartozik a vektorhoz. Az alulcsordulst ugyangy el kell kerlnnk, mint a tlcsordulst.

16.3.6. Listamveletek
A push_back(), a pop_back() s a back() mvelet (16.3.5) lehetv teszi, hogy a vector osztlyt veremknt hasznljuk. Bizonyos helyzetekben azonban arra is szksgnk lehet, hogy a verem kzepre illessznk be, vagy onnan tvoltsunk el egy elemet:
template <class T, class A = allocator<T> > class vector { public: // ... // listamveletek iterator insert(iterator pos, const T& x); // x felvtele pos el void insert(iterator pos, size_type n, const T& x); // n darab x felvtele pos el template <class In> // az In-nek bemeneti bejrnak kell lennie (19.2.1) void insert(iterator pos, In first, In last); // elemek beillesztse sorozatbl iterator erase(iterator pos); // a pos pozciban lev elem eltvoltsa iterator erase(iterator first, iterator last); // a sorozat trlse void clear(); // minden elem trlse // ...

};

Forrs: http://www.doksi.hu

596

A standard knyvtr

Ezen mveletek mkdsnek bemutatshoz egy gymlcsk (fruit) neveit tartalmaz vektort hozunk ltre. Elszr is meghatrozzuk a vektort, majd feltltjk nhny nvvel:
vector<string> fruit; fruit.push_back("szibarack"); fruit.push_back("alma"); fruit.push_back("kivi"); fruit.push_back("krte"); fruit.push_back("csillaggymlcs"); fruit.push_back("szl");

Ha hirtelen elegnk lesz azokbl a gymlcskbl, melyek neve k betvel kezddik, akkor ezeket a kvetkez eljrssal trlhetjk ki:
sort(fruit.begin(),fruit.end()); vector<string>::iterator p1 = find_if(fruit.begin(),fruit.end(),initial('k')); vector<string>::iterator p2 = find_if(p1,fruit.end(),initial_not('k')); fruit.erase(p1,p2);

Teht rendezzk a vektort, megkeressk az els s az utols gymlcst, melynek neve k betvel kezddik, vgl ezeket trljk a fruit objektumbl. Hogyan kszthetnk olyan fggvnyeket, mint az initial(x) amely eldnti, hogy a kezd karakter x bet-e vagy az initial_not(x), amely akkor ad igaz rtket, ha a kezdbet nem x? Ezzel a krdssel ksbb, a 18.4.2 pontban foglalkozunk rszletesen. Az erase(p1,p2) mvelet p1-tl p2-ig trli az elemeket (a p2 elem marad). Ezt a kvetkezkppen szemlltethetjk: fruit[ ]: p1 csillaggymlcs kivi krte p2 szibarack szl

alma

Az erase(p1,p2) trli a kivi s a krte elemet, teht a vgeredmny a kvetkez lesz: fruit[ ]: alma csillaggymlcs szibarack szl

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

597

Szoks szerint, a programoz ltal megadott sorozat az els feldolgozand elemtl az utols feldolgozott elem utni pozciig tart. Esetleg esznkbe jut az albbi megoldssal prblkozni:
vector<string>::iterator p1 = find_if(fruit.begin(),fruit.end(),initial('k')); vector<string>::reverse_iterator p2 = find_if(fruit.rbegin(),fruit.rend(),initial('k')); fruit.erase(p1,p2+1); // hopp! tpushiba

Azonban a vector<fruit>::iterator s a vector<fruit>::reverse_iterator nem felttlenl azonos tpus, gy nem hasznlhatjuk ket egytt az erase() fggvny hvsakor. Ha egy reverse_iterator rtket egy iterator rtkkel egytt akarunk hasznlni, akkor az elbbit t kell alaktanunk:
fruit.erase(p1,p2.base()); // iterator kinyerse reverse_iterator-bl (19.2.5)

Ha egy vektorbl elemeket trlnk, akkor megvltozik annak mrete s a trlt elem utn szerepl rtkeket a felszabadult terletre kell msolnunk. Pldnkban a fruit.size() rtke 4-re vltozik, s az szibarack, amire eddig fruit[5] nven hivatkozhattunk, most mr fruit[3]-knt lesz elrhet. Termszetesen arra is lehetsgnk van, hogy egyetlen elemet trljnk. Ebben az esetben csak az erre az elemre mutat bejrra van szksg (s nem egy bejr-prra):
fruit.erase(find(fruit.begin(),fruit.end(),"szibarack")); fruit.erase(fruit.begin()+1);

Ez a kt sor trli az szibarack-ot s a csillaggymlcs-t, gy a fruit vektorban mr csak kt elem marad: fruit[ ]: alma szl Arra is lehetsgnk van, hogy egy vektorba elemeket illessznk be. Pldul:
fruit.insert(fruit.begin()+1,"cseresznye"); fruit.insert(fruit.end(),"ribizli");

Forrs: http://www.doksi.hu

598

A standard knyvtr

Az j elem a megadott elem el kerl, az utna kvetkez elemek pedig elmozdulnak, hogy az j elemet beszrhassuk. Az eredmny: fruit[ ]: alma cseresznye szl ribizli Figyeljk meg, hogy az f.insert(f.end(),x) egyenrtk az f.push_back(x) mvelettel. Teljes sorozatokat is beilleszthetnk egy vektorba:
fruit.insert(fruit.begin()+2,citrus.begin(),citrus.end());

Ha a citrus egy msik trol, amely gy nz ki: citrus[ ]: citrom grapefruit narancs lime akkor a kvetkez eredmnyt kapjuk: fruit[ ]: alma cseresznye citrom grapefruit narancs lime szl ribizli Az insert() fggvny a citrus elemeit tmsolja a fruit vektorba. A citrus trol vltozatlan marad. Ktsgtelen, hogy az insert() s az erase() ltalnosabbak, mint azok a mveletek, melyek csak a vektor vgnek mdostst teszik lehetv (16.3.5). ppen emiatt azonban sokkal tbb gonddal is jrhatnak. Pldul ahhoz, hogy az insert() egy j elemet beillesszen, esetleg az sszes korbbi elemet t kell helyeznie a memriban. Ha sokszor hasznlunk teljesen ltalnos beszr s trl mveleteket egy troln, akkor ennek a trolnak esetleg nem is vector-nak, hanem inkbb list-nek kne lennie. A list trol hatkonyan kpes egyttmkdni az insert() s az erase() mveletekkel, de az indexels ez esetben nehzkes (16.3.3). A beszrs s a trls a vector esetben esetleg sok-sok elem thelyezsvel jr, (mg a list vagy az asszociatv trolk pldul a map esetben ez elkerlhet). Ennek kvetkeztben elfordulhat, hogy a vector egyik elemre mutat iterator egy insert() vagy egy erase() mvelet vgrehajtsa utn egy msik, vagy akr egy egyltaln nem ltez elemre mutat. Soha ne prbljunk meg elrni elemeket rvnytelen bejrn keresztl, mert az eredmny

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

599

meghatrozhatatlan lesz s ltalban vgzetes kvetkezmnyekkel jr. Klnsen veszlyes egy olyan bejr hasznlata, amely a beszrs helyt jellte ki, mert az insert() az els paramtert rvnytelenn teszi:
void duplicate_elements(vector<string>& f) { for(vector<string>::iterator p = f.begin(); p!=f.end(); ++p) f.insert(p,*p); }

// Nem!

Erre kell gondolnunk majd a 16.5[15] feladatnl is. A vector adott vltozata az j p elem beszrshoz esetleg az sszes elemet thelyezn, de a p utniakat biztosan. A clear() mvelet a trol sszes elemt trli. Ezrt a c.clear() a c.erase(c.begin(),c.end()) rvidtsnek tekinthet. A c.clear() vgrehajtsa utn a c.size() rtke 0 lesz.

16.3.7. Az elemek kivlasztsa


A legtbb esetben az erase() s az insert() clpontja egy jl meghatrozott hely, pldul a begin() vagy az end() eredmnye, valamilyen keressi eljrs (pldul a find()) ltal visszaadott rtk vagy egy bejrssal megtallt elempozci. Ezekben az esetekben rendelkezsnkre ll egy bejr, amely a kvnt elemet jelli ki. A vector (s a vektorszer trolk) elemeit azonban meghatrozhatjuk indexelssel is. Hogyan llthatunk el egy olyan bejrt, amely az insert() vagy az erase() utastsban megfelel paramter a 7-es sorszm elem kijellshez? Mivel ez a hetedik elem a vektor elejtl szmtva, gy a c.begin()+7 kifejezs jelenti a megoldst. A tovbbi lehetsgek, amelyek a tmbkhz val hasonlsg miatt felmerlhetnek bennnk, gyakran nem mkdnek. Pldul gondoljuk vgig a kvetkez eseteket:
template<class C> void f(C& c) { c.erase(c.begin()+7); // rendben (ha c bejri tmogatjk a + mveletet // (19.2.1)) c.erase(&c[7]); // nem ltalnos c.erase(c+7); // hiba: 7 hozzadsa egy trolhoz nem rtelmezhet c.erase(c.back()); // hiba: c.back() referencia, nem bejr c.erase(c.end()-2); // rendben (utols eltti eltti elem) c.erase(c.rbegin()+2); // hiba: vector::reverse_iterator s vector::iterator // klnbz tpusok c.erase((c.rbegin()+2).base()); // zavaros, de j (19.2.5) }

Forrs: http://www.doksi.hu

600

A standard knyvtr

A legcsalogatbb lehetsg a &c[7], amely a vector legnyilvnvalbb megvalstsban hasznlhat is, hiszen a c[7] egy ltez elem, s ennek cme hasznlhat bejrknt. A c viszont lehet egy olyan trol is, ahol a bejr nem egy egyszer mutat valamelyik elemre. A map index-opertora (17.4.1.3) pldul egy mapped_type& tpus referencit, s nem egy elemre hivatkoz referencit (value_type&) ad vissza. A bejrra nem minden trol esetben hasznlhat a + mvelet. A list pldul mg a c.begin()+7 forma hasznlatt sem tmogatja. Ha felttlenl hetet kell adnunk egy list::iterator objektumhoz, akkor a ++ opertort kell ismtelgetnnk. A c+7 s a c.back() forma egyszeren tpushibt eredmnyez. A trol nem numerikus vltoz, amelyhez hetet hozzadhatnnk, a c.back() pedig egy konkrt elem pldul a krte rtkkel, amely nem hatrozza meg a krte helyt a c trolban.

16.3.8. Mret s kapacits


A vector osztlyt eddig gy prbltuk meg bemutatni, hogy a lehet legkevesebbet szljunk a memriakezelsrl. A vector szksg szerint nvekszik. ltalban ennyit ppen elg tudnunk. Ennek ellenre lehetsgnk van r, hogy kzvetlenl a vector memriahasznlatrl krdezznk, s bizonyos helyzetekben rdemes is lnnk ezzel a lehetsggel. Az ilyen cl mveletek a kvetkezk:
template <class T, class A = allocator<T> > class vector { public: // ... // kapacits size_type size() const; bool empty() const { return size()==0; } size_type max_size() const; void resize(size_type sz, T val = T()); size_type capacity() const; void reserve(size_type n); // ... // elemek szma // a lehetsges legnagyobb vektor mrete // a hozzadott elemeknek val ad kezdrtket

// az elemek szmnak megfelel lefoglalt memria mrete // hely biztostsa sszesen n elem szmra; nem adunk // kezdrtket // length_error kivltsa, ha n>max_size()

};

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

601

A vector minden pillanatban valahny elemet trol. Az ppen trolt elemek szmt a size() fggvnnyel krdezhetjk le s a resize() segtsgvel mdosthatjuk. Teht a programoz meg tudja llaptani a vektor mrett s meg tudja azt vltoztatni, ha a vektor szknek vagy tl nagynak bizonyul:
class Histogram { vector<int> count; public: Histogram(int h) : count(max(h,8)) {} void record(int i); // ... }; void Histogram::record(int i) { if (i<0) i = 0; if (count.size()<=i) count.resize(i+i); count[i]++; }

// sok hely kell

A resize() hasznlata egy vector esetben nagyon hasonlt arra, amikor a C standard knyvtrnak realloc() fggvnyt hasznljuk egy dinamikusan lefoglalt C tmb esetben. Amikor egy vektort tmreteznk, hogy tbb (vagy kevesebb) elemet troljunk benne, elkpzelhet, hogy az sszes elem j helyre kerl a memriban. Ezrt tmretezhet vektorok elemeire hivatkoz mutatkat nem trolhatunk akrmeddig, egy resize() utasts utn ugyanis ezek mr felszabadtott memriaterletre mutatnak. Ehelyett nyilvntarthatunk indexrtkeket. Soha ne felejtsk el, hogy a push_back(), az insert() s az erase() is tmretezi a vektort. Ha a programoz tudja, hogy a vektor mrete a ksbbiekben mennyire nhet, esetleg rdemes a reserve() fggvny segtsgvel elre lefoglalnia a megfelel mret terletet a ksbbi felhasznlshoz:
struct Link { Link* next; Link(Link* n =0) : next(n) {} // ... }; vector<Link> v; void chain(size_t n) { // v feltltse n szm Link-kel, melyek az elz Link-re mutatnak

Forrs: http://www.doksi.hu

602

A standard knyvtr

v.reserve(n); v.push_back(Link(0)); for (int i = 1; i<n; i++) v.push_back(Link(&v[i-1])); // ...

A v.reserve(n) fggvnyhvs biztostja, hogy a v mretnek nvelsekor mindaddig nem lesz szksg memriafoglalsra, amg v.size() meg nem haladja n rtkt. A szksges memriaterlet elzetes lefoglalsnak kt elnye van. Az egyik, hogy mg a legegyszerbb nyelvi vltozat is egyetlen mvelettel lefoglalhat elegend memriaterletet, s nem kell minden lpsben lass memriaignylst vgrehajtania. Ezenkvl a legtbb esetben szmolhatunk egy logikai elnnyel is, amely taln mg fontosabb, mint a hatkonysgi szempont. Amikor egy vector mrete megn, akkor elmletileg minden elem helye megvltozhat a memriban. Ennek kvetkeztben minden olyan jelleg lncols, amilyet az elbbi pldban ltrehoztunk az elemek kztt, csak akkor hasznlhat, ha a reserve() garantlja, hogy az elemek memriabeli helye nem vltozik meg a vektor felptse kzben. Teht bizonyos esetekben a reserve() biztostja programunk helyessgt, amellett, hogy hatkonysgi elnyket is jelent. Ugyanezt a biztonsgot nyjtja az is, ha csak kiszmthat idkznknt fordulhat el, hogy a vector szmra lefoglalt terlet elfogy s egy bonyolult mvelettel t kell helyeznnk az addig trolt elemeket. Ez nagyon fontos lehet olyan programok esetben, melyeknek szigor futsi idej korltozsoknak kell megfelelnik. Fontos megjegyeznnk, hogy a reserve() nem vltoztatja meg a vektor (logikai) mrett, ezrt az j elemeknek sem kell kezdrtket adnia. Teht mindkt szempontbl ellenttesen viselkedik, mint a resize(). Ugyangy, ahogy a size() az ppen trolt elemek szmt adja meg, a capacity() fggvny a lefoglalt memria-egysgek szmrl tjkoztat. gy a c.capacity()-c.size() kifejezs azt adja meg, hogy mg hny elemet hozhatunk ltre jbli memriafoglals nlkl. A vektor mretnek cskkentsvel nem cskkentjk annak kapacitst. Az gy felszabadult terletek teht megmaradnak a vector-ban a ksbbi nvekedst segtend. Ha a felszabadult terletet vissza szeretnnk adni a rendszernek, egy kis trkkhz kell folyamodnunk:
vector <Link> tmp = v; v.swap(tmp); // v msolata alaprtelmezett kapacitssal // most v rendelkezik az alaprtelmezett kapacitssal (16.3.9)

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

603

A vector az elemek trolshoz szksges memriaterletet gy foglalja le, hogy meghvja (a sablonparamterknt megadott) memriafoglal tagfggvnyeit. Az alaprtelmezett memriafoglal, melynek neve allocator (19.4.1), a new opertort hasznlja a memriafoglalshoz, gy egy bad_alloc kivtelt vlt ki, ha mr nincs elg szabad memria. Ms memriafoglalk ms mdszert is kvethetnek (19.4.2). A reserve() s capacity() fggvnyek csak az olyan egysges, tmr trolk esetben hasznlhatk, mint a vector. A list-hez pldul ilyen szolgltats nem ll rendelkezsnkre.

16.3.9. Tovbbi tagfggvnyek


Nagyon sok algoritmus kztk a fontos rendez eljrsok elemek felcserlsvel dolgozik. A csere (13.5.2) legegyszerbb megvalstsa, hogy egyszeren tmsolgatjuk az elemeket. A vector osztlyt azonban ltalban olyan szerkezet brzolja, amely az elemek lerjt (handle) trolja (13.5, 17.1.3). gy kt vector sokkal hatkonyabban is felcserlhet, ha a lerkat cserljk fel. A vector::swap() fggvny ezt valstja meg. Az alaprtelmezett swap() nagysgrendekkel lassabb, mint a fenti eljrs:
template <class T, class A = allocator<T> > class vector { public: // ... void swap(vector&); }; allocator_type get_allocator() const;

A get_allocator() fggvny lehetsget ad a programoznak arra, hogy elrje a vector memriafoglaljt (16.3.1, 16.3.4). Erre a szolgltatsra ltalban akkor van szksgnk, amikor egy, a vektorhoz kapcsold adatnak a programban ugyangy akarunk memrit foglalni, mint a vector-nak magnak (19.4.1)

16.3.10. Segdfggvnyek
Kt vector objektumot az == s a < opertor segtsgvel hasonlthatunk ssze:
template <class T, class A> bool std::operator==(const vector<T,A>& x, const vector<T,A>& y);

Forrs: http://www.doksi.hu

604

A standard knyvtr

template <class T, class A> bool std::operator<(const vector<T,A>& x, const vector<T,A>& y);

A v1 s a v2 vector akkor egyenl, ha v1.size()==v2.size() s v1[n]==v2[n] minden rtelmes n index esetn. Hasonlan, a < a nyelvi elemek szerinti rendezst jelenti, teht a < mveletet a vector esetben a kvetkezkppen hatrozhatjuk meg:
template <class T, class A> inline bool std::operator<(const vector<T,A>& x, const vector<T,A>& y) { return lexicographical_compare (x.begin(),x.end(),y.begin(),y.end()); }

// lsd 18.9

Ez azt jelenti, hogy x akkor kisebb, mint y, ha az els olyan x[i] elem, amely nem egyezik meg a megfelel y[i] elemmel, kisebb, mint y[i], vagy x.size()<y.size() s minden x[i] megegyezik a megfelel y[i] rtkkel. A standard knyvtr az == s a < defincijnak megfelelen meghatrozza a !=, a <=, a >, s a >= opertort is. Mivel a swap() egy tagfggvny, v1.swap(v2) formban hvhatjuk meg. Nem minden tpusban szerepel azonban a swap() tagfggvny, gy az ltalnos algoritmusok a swap(a,b) formt hasznljk. Ahhoz, hogy ez a forma a vector-ok esetben is mkdjn, a standard knyvtr a kvetkez specializcit adja meg:
template <class T, class A> void std::swap(vector<T,A>& x, vector<T,A>& y) { x.swap(y); }

16.3.11. Vector<bool>
A vector<bool> specializlt osztly (13.5) logikai rtkeknek egy tmrtett vektort valstja meg. Egy bool tpus vltozt is meg kell tudnunk cmezni, gy az legalbb egy bjtot foglal. A vector<bool> osztlyt azonban knnyen elkszthetjk gy is, hogy minden elem csak egy bitet foglaljon. A szoksos vector mveletek ugyanolyan jelentssel hasznlhatk a vector<bool> esetben is. Klnsen fontos, hogy az indexels s a bejrs is elvrsainknak megfelelen mkdnek:

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

605

void f(vector<bool>& v) { for (int i = 0; i<v.size(); ++i) cin >> v[i]; typedef vector<bool>::const_iterator VI; for (VI p = v.begin(); p!=v.end(); ++p) cout<<*p; }

// ciklus index hasznlatval // ciklus bejrk // hasznlatval

Ennek elrshez utnozni kell a bitenknti cmzst. Mivel egy mutat az egy bjtnl kisebb memriaegysgeket nem kpes azonostani, a vector<bool>::iterator nem lehet mutat. A bool* pldul biztosan nem hasznlhat bejrknt a vector<bool> osztlyban:
void f(vector<bool>& v) { bool* p = v.begin(); // ... }

// hiba: nem megfelel tpus

A klnll bitek megcmzsnek mdjt a 17.5.3 pontban mutatjuk be. A knyvtrban szerepel a bitset tpus is, amely logikai rtkek halmazt trolja, a logikai halmazok szoksos mveleteivel (17.5.3).

16.4. Tancsok
[1] Hordozhat programok ksztshez hasznljuk a standard knyvtr szolgltatsait. 16.1. [2] Ne prbljuk jraalkotni a standard knyvtr szolgltatsait. 16.1.2. [3] Ne higgyk, hogy minden esetben a standard knyvtr jelenti a legjobb megoldst. [4] Amikor j szolgltatsokat hozunk ltre, gondoljuk vgig, hogy nem valsthatk-e meg a standard knyvtr nyjtotta kereteken bell. 16.3. [5] Ne felejtsk, hogy a standard knyvtr szolgltatsai az std nvtrhez tartoznak. 16.1.2. [6] A standard knyvtr szolgltatsait a megfelel fejllomny segtsgvel ptsk be, ne hasznljunk kzvetlen deklarcit. 16.1.2.

Forrs: http://www.doksi.hu

606

A standard knyvtr

[7] Hasznljuk ki a ksei elvonatkoztats elnyeit. 16.2.1. [8] Kerljk a kvr felletek hasznlatt. 16.2.2. [9] Ha az elemeken visszafel akarunk haladni, hasznljunk reverse_iterator-okat iterator helyett. 16.3.2. [10] Ha iterator-t szeretnnk csinlni egy reverse_iterator-bl, hasznljuk a base() fggvnyt. 16.3.2. [11] Trolkat referencia szerint adjunk t. 16.3.4. [12] Ha egy trol elemeire akarunk hivatkozni, mutatk helyett hasznljunk bejrtpusokat (pldul list<char>::iterator). 16.3.1. [13] Hasznljunk const bejrkat, ha a trol elemeit nem akarjuk megvltoztatni. 16.3.1. [14] Ha tartomnyellenrzst akarunk vgezni akr kzvetlenl, akr kzvetve , hasznljuk az at() fggvnyt. 16.3.3. [15] Hasznljuk inkbb a push_back() vagy a resize() fggvnyt egy trolban, mint a realloc() fggvnyt egy tmbben. 16.3.5. [16] Ne hasznljuk egy tmretezett vector bejrit. 16.3.8. [17] A bejrk rvnytelenn vlst elkerlhetjk a reserve() fggvny hasznlatval. 16.3.8. [18] Ha szksges, a reserve() fggvnyt felhasznlhatjuk a teljestmny kiszmthatv ttelre is. 16.3.8.

16.5. Feladatok
A fejezet legtbb feladatnak megoldst knnyen megtallhatjuk, ha megnzzk a standard knyvtr adott vltozatt. Mieltt azonban megnznnk, hogy a knyvtr kszti hogyan kzeltettk meg az adott problmt, rdemes sajt megoldst ksztennk. 1. (*1.5.) Ksztsnk egy vector<char> tpus objektumot, amely az bc betit tartalmazza, sorrendben. rassuk ki ennek a tmbnek a tartalmt elre, majd visszafel. 2. (*1.5.) Ksztsnk egy vector<string> objektumot, majd olvassuk be gymlcsk neveit a cin eszkzrl. Rendezzk a listt, majd rassuk ki elemeit. 3. (*1.5.) A 16.5[2] feladat vektornak felhasznlsval rjunk olyan ciklust, amely az sszes 'a' betvel kezdd nev gymlcst jelenti meg. 4. (*1.) A 16.5[2] feladat vektornak felhasznlsval rjunk olyan ciklust, amellyel trlhetjk az sszes 'a' betvel kezdd nev gymlcst. 5. (*1.) A 16.5[2] feladat vektornak felhasznlsval rjunk olyan ciklust, amellyel az sszes citrusflt trlhetjk.

Forrs: http://www.doksi.hu

16. A knyvtr szerkezete s a trolk

607

6. (*1.5.) A 16.5[2] feladat vektornak felhasznlsval rjunk olyan ciklust, amely azokat a gymlcsket trli, amelyeket nem szeretnk. 7. (*2.) Fejezzk be a 16.2.1 pontban elkezdett Vector, List s Itor osztlyt. 8. (*2.5.) Az Itor osztlybl kiindulva gondoljuk vgig, hogyan kszthetnk bejrkat elre halad bejrsokhoz, visszafel halad bejrsokhoz, olyan trolhoz, amely a bejrs alatt megvltozhat, illetve megvltoztathatatlan trolhoz. Szervezzk gy ezeket a trolkat , hogy a programoz mindig azt a bejrt hasznlhassa, amelyik a leghatkonyabb megoldst knlja az adott algoritmus megvalstsra. A trolkban kerljnk minden kdismtlst. Milyen ms bejr-fajtra lehet szksge egy programoznak? Gyjtsk ssze az ltalunk hasznlt megkzelts elnyeit s htrnyait. 9. (*2.) Fejezzk be a 16.2.2 pontban elkezdett Container, Vector s List osztly megvalstst. 10. (*2.5.) lltsunk el 10 000 darab egyenletes eloszls vletlenszmot a 0-1023 tartomnyban s troljuk azokat a.) a standard knyvtr vector osztlyval, b.) a 16.5[7] feladat Vector osztlyval, c.) a 16.5[9] feladat Vector osztlyval. Mindegyik esetben szmoljuk ki a vektor elemeinek szmtani kzept (mintha mg nem tudnnk). Mrjk a ciklusok lefutsnak idejt. Becsljk meg vagy szmoljuk ki a hromfle vektor memria-felhasznlst, s hasonltsuk ssze az rtkeket. 11. (*1.5.) rjunk egy bejrt, amely lehetv teszi, hogy a 16.2.2 pontban bemutatott Vector osztlyt a 16.2.1 pontban hasznlt trol stlusban kezeljk. 12. (*1.5.) Szrmaztassunk egy osztlyt a Container-bl, amely lehetv teszi, hogy a 16.2.1 vektort a 16.2.2 pontban bemutatott trolstlusban hasznljuk. 13. (*2.) Ksztsnk olyan osztlyokat, amelyek lehetv teszik, hogy a 16.2.1 s a 16.2.2 Vector osztlyait szabvnyos trolkknt hasznljuk. 14. (*2) rjunk egy ltez (nem szabvnyos, nem tanknyvi plda) troltpushoz egy olyan sablont, amely ugyanazokkal a tagfggvnyekkel s tpus tagokkal rendelkezik, mint a szabvnyos vector. Az eredeti troltpust ne vltoztassuk meg. Hogyan tudjuk felhasznlni azokat a szolgltatsokat, amelyeket a nem szabvnyos vektor megvalst, de a vector nem? 15. (*1.5) Gondoljuk vgig, hogyan viselkedik a 16.3.6 pontban bemutatott duplicate_elements() fggvny egy olyan vector<string> esetben, melynek hrom eleme: ne tedd ezt.

Forrs: http://www.doksi.hu

17
Szabvnyos trolk
Itt az ideje, hogy munknkat vgre szilrd elmleti alapokra helyezzk. (Sam Morgan) Szabvnyos trolk Trolk s mveletek - ttekints Hatkonysg brzols Megktsek az elemekre Sorozatok vector list deque talaktk stack queue priority_queue Asszociatv trolk map sszehasonltsok multimap set multiset Majdnem-trolk bitset Tmbk Hast tblk A hash_map egy megvalstsa Tancsok Gyakorlatok

17.1. A szabvnyos trolk


A standard knyvtr ktfle trolt tartalmaz: sorozatokat (sequences) s asszociatv trolkat (associative container). A sorozatok mindegyike nagyon hasonlt a vector trolra (16.3). Ha kln nem emltjk, ugyanazok a tagtpusok s fggvnyek hasznlhatk ezekre is, mint a vektorokra, s eredmnyk is ugyanaz lesz. Az asszociatv trolk ezen kvl lehetsget adnak az elemek kulcsokkal trtn elrsre is (3.7.4).

Forrs: http://www.doksi.hu

610

A standard knyvtr

A beptett tmbk (5.2), a karakterlncok (20. fejezet), a valarray osztly (22.4) s a bitset (bithalmaz) tpusok szintn elemek trolsra szolglnak, gy ezeket is trolknak tekinthetjk. E tpusok azonban mgsem tkletesen kidolgozott, szabvnyos trolk. Ha a standard knyvtr elvrsainak megfelelen prblnnk meg elkszteni ket, akkor elsdleges cljukat nem tudnk maradktalanul elrni. A beptett tmbktl pldul nem vrhatjuk el, hogy egyszerre tartsk nyilvn sajt mretket s ugyanakkor teljesen sszeegyeztethetek maradjanak a C tmbkkel. A szabvnyos trolk alaptlete, hogy logikailag felcserlhetek legyenek minden rtelmes helyzetben. gy a felhasznl mindig szabadon vlaszthat kzlk, az ppen hasznlni kvnt mveletek, illetve a hatkonysgi szempontok figyelembe vtelvel. Ha pldul gyakran kell elrnnk elemeket valamilyen kulcsrtk segtsgvel, akkor a map (17.4.1) trolt hasznljuk. Ha legtbbszr a szoksos listamveletekre van szksgnk, akkor a list (17.2.2) a megfelel osztly. Ha sokszor kell hozzfznnk elemeket a trol vghez, vagy ppen el kell onnan tvoltanunk elemeket, akkor a deque (ktvg sor, 17.2.3), a stack (17.3.1) vagy a queue (17.3.2) nyjtja a legtbb segtsget. Ezeken kvl maga a programoz is kifejleszthet olyan trolkat, melyek pontosan illeszkednek a szabvnyos trolk ltal knlt keretrendszerbe (17.6). Leggyakrabban a vector osztlyt fogjuk hasznlni, mert ennek elnyeit rengeteg felhasznlsi terleten kiaknzhatjuk. Az tlet, hogy a klnbz jelleg trolkat vagy mg ltalnosabban, az sszes informciforrst egysges formban kezeljk, elvezet minket az ltalnostott (generikus) programozs fogalmhoz (2.7.2, 3.8). Ezt a szemlletmdot kvetve a standard knyvtr nagyon sok ltalnos algoritmust biztost (18. fejezet), melyek megkmlik a programozt attl, hogy kzvetlenl az egyes trolk rszleteivel foglalkozzon.

17.1.1. Mveletek ttekints


Ebben a pontban felsoroljuk azokat az osztlytagokat, amelyek minden, vagy majdnem minden trolban megtallhatk. Ha rszleteket szeretnnk megtudni, nzzk meg a megfelel fejllomnyt (<vector>, <list>, <map> stb, 16.1.2).

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

611

Tpusok (16.3.1) value_type allocator_type size_type difference_type iterator const_iterator reverse_iterator const_reverse_iterator reference const_reference key_type mapped_type key_compare Az elemek tpusa. A memriakezel tpusa. Tpus az indexelshez, elemszmllshoz stb. A bejrk kztti klnbsg tpusa. gy viselkedik, mint a value_type* tpus. A const value_type* megfelelje. A trol elemeit fordtott sorrendben ltjuk, egybknt a value_type* megfelelje. A trol elemeit fordtott sorrendben ltjuk; a const value_type* tpushoz hasonlt. Olyan, mint a value_type&. Olyan, mint a const value_type&. A kulcs tpusa (csak asszociatv trolkhoz). A mapped_value tpusa (csak asszociatv trolkhoz). Az sszehasonltsi szempont tpusa (csak asszociatv trolkhoz).

A trolt tekinthetjk elemek sorozatnak, akr abban a sorrendben, amelyet a trol bejrja meghatroz, akr ellenkez irnyban. Egy asszociatv trol esetben a sorrendet a trol sszehasonltsi szempontja hatrozza meg (ami alaprtelmezs szerint a < mvelet).

Bejrk (16.3.2) begin() end() rbegin() rend() Az els elemre mutat. Az utols utni elemre mutat. Visszafel halad felsorols esetn az els elemre mutat. Visszafel halad felsorols esetn az utols utni elemre mutat.

Forrs: http://www.doksi.hu

612

A standard knyvtr

Nhny elemet kzvetlenl is elrhetnk:

Hozzfrs elemekhez (16.3.3) front() back() [] at() Az els elem. Az utols elem. Indexels, ellenrzs nlkli hozzfrs. (Listknl nem hasznlhat.) Indexels, ellenrztt vltozat. (Listknl nem hasznlhat.)

A vektorok (vector) s a ktvg sorok (deque) olyan hatkony mveleteket is biztostanak, amelyek az elemek sorozatnak vgn (back) vgeznek mdostsokat. A listk (list) s a ktvg sorok (deque) az ezekkel egyenrtk mveleteket az elemsorozatok elejn (front) is kpesek elvgezni:

Verem- s sormveletek (16.3.5, 17.2.2.2) push_back() pop_back() push_front() pop_front() Elem beszrsa a trol vgre. Elem eltvoltsa a trol vgrl. j els elem beillesztse (csak listkhoz s ktvg sorokhoz). Az els elem eltvoltsa (csak listkhoz s ktvg sorokhoz).

A trolk lehetv teszik a listamveletek hasznlatt is: Listamveletek (16.3.6) insert(p,x) insert(p,n,x) insert(p,first,last) erase(p) erase(first,last) clear() x beillesztse p el. x elem n darab msolatnak beillesztse p el. Elemek beszrsa a [first:last[ tartomnybl a p el. A p helyen lv elem eltvoltsa. A [first:last[ tartomny trlse. Az sszes elem trlse.

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

613

Minden trolban tallhatunk az elemek szmval kapcsolatos mveleteket, illetve nhny egyb fggvnyt is:

Tovbbi mveletek (16.3.8, 16.3.9, 16.3.10) size() empty() max_size() capacity() reserve() resize() swap() get_allocator() == != < Az elemek szma. res a trol? A legnagyobb lehetsges trol mrete. A vector szmra lefoglalt terlet mrete (csak vektorokhoz). Memriafoglals a ksbbi nvelsek gyorstshoz (csak vektorokhoz). A trol mretnek mdostsa (csak vektorokhoz, listkhoz s ktvg sorokhoz). Kt trol elemeinek felcserlse. A trol memriafoglaljnak lemsolsa. Megegyezik a kt trol tartalma? Klnbzik a kt trol tartalma? Az egyik trol bcsorrendben megelzi a msikat?

A trolk szmos konstruktort, illetve rtkad opertor biztostanak:

Konstruktorok stb. (16.3.4) container() container(n) container(n,x) container(first,last) container(x) ~container() res trol. n darab elem, alaprtelmezett rtkkel (asszociatv trolkhoz nem hasznlhat). x elem n pldnybl ksztett trol (asszociatv trolkhoz nem hasznlhat). A kezdelemek a [first:last[ tartomnybl szrmaznak. Msol konstruktor. A kezdeti elemeket az x trol elemei adjk. A trol s sszes elemnek megsemmistse.

Forrs: http://www.doksi.hu

614

A standard knyvtr

rtkadsok (16.3.4) operator=(x) assign(n,x) assign(first,last) Msol rtkads. Az elemek az x trolbl szrmaznak. x elem n pldnynak beillesztse a trolba (asszociatv trolknl nem hasznlhat). rtkads a [first:last[ tartomnybl.

Az asszociatv trolk lehetv teszik az elemek kulcs alapjn trtn elrst:

Asszociatv mveletek (17.4.1) operator[ ](k) find(k) lower_bound(k) upper_bound(k) equal_range(k) key_comp() value_comp() A k kulcs elem elrse (egyedi kulccsal rendelkez trolkhoz). k kulccsal rendelkez elem keresse. Az els k kulcs elem megkeresse. Az els olyan elem megkeresse, melynek kulcsa nagyobb, mint k. A k kulcs elemek als s fels hatrnak megkeresse. A kulcs-sszehasonlt objektum msolata. A mapped_value rtkeket sszehasonlt objektum msolata.

Ezen ltalnos mveleteken kvl a legtbb trol nhny egyedi mveletet is biztost.

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

615

17.1.2. Trolk ttekints


A szabvnyos trolkat (container) az albbi tblzat foglalja ssze:

A szabvnyos trolk mveletei [] Listamveletek Mveletek a trol elejn 17.2.2.2 20.3.9 konstans konstans konstans O(log(n)) O(log(n))+ O(log(n))+ O(log(n))+ O(log(n))+ O(n)+ O(n)+ Mveletek a trol vgn (verem mveletek) 16.3.5 20.3.12 konstans+ konstans konstans konstans konstans O(log(n)) Bejrk

vector list deque konstans stack queue priority_queue map O(log(n)) multimap set multiset string konstans array konstans valarray konstans bitset konstans

16.3.3 17.4.1.3 konstans

16.3.6 20.3.9 O(n)+ konstans O(n)

19.2.1 Kzvetlen Ktirny Kzvetlen

konstans+

Ktirny Ktirny Ktirny Ktirny Kzvetlen Kzvetlen Kzvetlen

Az Elrs oszlopban a Kzvetlen azt jelenti, hogy az elemek tetszleges sorrendben elrhetk (kzvetlen elrs, random access), a Ktirny esetben pedig ktirny (bidirectional) soros hozzfrs bejrkat hasznlhatunk. A ktirny bejrk mveletei rszhalmazt kpezik a kzvetlen elrsek mveleteinek (19.2.1). A tblzat tovbbi elemeibl az adott mvelet hatkonysgra kvetkeztethetnk. A konstans rtk azt fejezi ki, hogy az adott mvelet vgrehajtsi ideje nem fgg a trolban trolt elemek szmtl. A konstans idigny egy msik szoksos jellse O(1). Az O(n) rtk azt fejezi ki, hogy a szmtsi id arnyos a feldolgozott elemek szmval. A + jellst azokban

Forrs: http://www.doksi.hu

616

A standard knyvtr

az esetekben hasznltuk, ahol idnknt jelents mennyisg tbbletterhels is elfordulhat. Egy elem beszrsa egy listba pldul mindig ugyanannyi ideig tart, gy a megfelel helyen a konstans rtket olvashatjuk. Ugyanez a mvelet a vector esetben a beszrsi pont utn ll sszes elem thelyezsvel jr, gy ott az O(n) rtk szerepel. St, idnknt az sszes elemet (teht a beszrsi pont elttieket is) t kell helyezni, ezrt a + jelet is megadtuk. A nagy O (ordo) egy hagyomnyos jellsmd, a + jelet csak azrt hasznltam, hogy kiegszt informcit adjak azoknak a programozknak, akik az tlagos teljestmny mellett a mvelet idejnek megjsolhatsgra is hangslyt helyeznek. Az O(n)+ jells hagyomnyos jelentse amortizlt lineris id (amortized linear time, vagyis kb. tbbletterhet jelent, egyenes arnyossgban nvekv id). Termszetesen, ha a konstans egy nagyon hossz idtartamot jelent, akkor ez nagyobb lehet, mint az elemek szmval egyenesen arnyos idigny. Nagy adatszerkezetek esetben azonban a konstans jelentse mgis olcs, az O(n) jelentse pedig drga, mg az O(log(n)) idt tekinthetjk elg olcsnak. Mg viszonylag nagy n rtkek esetn is, az O(log(n)) kzelebb ll a konstanshoz, mint az O(n)-hez. Azoknak a programozknak, akiknek programjaik kltsgeivel komolyan foglalkozniuk kell, ennl alaposabb ismeretekre is szksgk van. Tisztban kell lennik azzal, mely elemek figyelembe vtelvel alakul ki n rtke. Olyan alapmvelet szerencsre nincs, melyet nagyon drgnak nevezhetnnk, ami O(n*n) vagy mg jelentsebb idignyt jelentene. A string kivtelvel az itt kzlt kltsgrtkek megfelelnek a C++ szabvny elvrsainak. A string tpusnl szerepl becslsek sajt felttelezseim. A bonyolultsgnak, illetve az idignynek ezen mrszmai a fels hatrt jelentik. Szerepk abban ll, hogy a programoz megtudhatja bellk, mit vrhat el az adott szolgltatstl. Termszetesen a konkrt megvalstsok kszti igyekeznek a lnyeges helyeken ezeknl jobb megoldst biztostani.

17.1.3. brzols
A szabvny nem r el semmilyen egyedi brzolsi mdot egyik trol esetben sem. A szabvny csak a trol fellett rja le, illetve nhny bonyolultsgi kvetelmnyt hatroz meg. Az egyes nyelvi vltozatok kszti maguk vlaszthatjk meg a legmegfelelbb s gyakran nagyon jl optimalizlt megvalstsi mdot, amely kielgti az ltalnos kvetelmnyeket. A trolkat ltalban olyan adatszerkezet segtsgvel hozzk ltre, amely az elemek elrst biztost azonost mellett a mretre s kapacitsra vonatkoz informcikat is nyilvntartja. A vector esetben az elemek adatszerkezete leggyakrabban egy tmb:

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

617

vektor:

mret brzols elemek tartalk hely

A list megvalstsnak legegyszerbb eszkze a lncols, ahol az elemek egymsra mutatnak: list:

brzols elemek:

A map leggyakoribb megvalstsa a (kiegyenslyozott) binris fa, ahol a cscsok vagy ms nven csompontok (kulcs, rtk) prokat jellnek ki: map:

brzols ...

cscs cscs

(kulcs, rtk) prok:

A string megvalstsra a 11.12 pontban adtunk egy tletet, de elkpzelhetjk tmbk sorozataknt is, ahol minden tmb nhny karaktert trol:

string:

brzols rszlnc-lerk

rszlncok:

Forrs: http://www.doksi.hu

618

A standard knyvtr

17.1.4. Megktsek az elemekre


A trolban trolt elemek a beillesztett objektumok msolatai, gy ahhoz, hogy egy objektum bekerlhessen a trolba, olyan tpusnak kell lennie, amely lehetv teszi, hogy a trol msolatot ksztsen rla. A trol az elemeket egy msol konstruktor vagy egy rtkads segtsgvel msolhatja le. A msols eredmnynek mindkt esetben egyenrtknek kell lennie az eredeti objektummal. Ez nagyjbl azt jelenti, hogy minden elkpzelhet egyenlsgvizsglat azonosnak kell, hogy minstse az eredeti s a msolt objektumot. Az elemek msolsnak teht gy kell mkdnie, mint a beptett tpusok (kztk a mutatk) szoksos msolsnak. Az albbi rtkads pldul nagy lpst jelent afel, hogy az X tpus egy szabvnyos trol elemeinek tpusa legyen:
X& X::operator=(const X& a) // helyes rtkad opertor { // 'a' minden tagjnak msolsa *this-be return *this; }

Az albbi kdrszlet Y tpusa viszont helytelen, mert a visszatrsi rtk nem a szoksos s a jelents sem megfelel.
void Y::operator=(const Y& a) { // 'a' minden tagjnak trlse } // helytelen rtkad opertor

A szabvnyos trolk szablyaitl val eltrsek egy rszt mr a fordt kpes jelezni, de sok esetben ezt a segtsget sem kapjuk meg, csak teljesen vratlan esemnyekkel kerlnk szembe. Egy olyan msolsi mvelet pldul, amely kivtelt vlthat ki, rszlegesen tmsolt elemet is eredmnyezhet. Ennek kvetkezmnyekppen a trol olyan llapotba kerl, amely csak jval ksbb okoz problmkat. Az ilyen msolsi mveletek teht mr nmagukban is rossz tervezs kvetkezmnyei (14.4.6.1). Ha az elemek msolsa nem lehetsges, megoldst az jelenthet, ha a trolban konkrt objektumok helyett azokra hivatkoz mutatkat helyeznk el. Ennek legnyilvnvalbb pldja a tbbalak (polimorf) tpusok esete (2.5.4, 12.2.6). Ha a tbbalaksg lehetsgt meg akarjuk rizni, akkor a vector<Shape> helyett pldul a vector<Shape*> osztlyra lesz szksgnk.

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

619

17.1.4.1. sszehasonltsok Az asszociatv trolk megkvetelik, hogy elemeiket ssze lehessen hasonltani. Ugyanez elmondhat ms trolk nhny mveletrl is (pldul a rendez eljrsokrl, mint a sort()). Alaprtelmezs szerint a sorrend meghatrozsra a < opertort hasznljuk. Ha ez az adott esetben nem hasznlhat, akkor a programoznak valamilyen ms megoldst kell tallnia (17.4.1.5, 18.4.2). A rendezsi szempontnak szigoran megengednek kell lennie (strict weak ordering), ami lnyegben azt jelenti, hogy a kisebb mint s egyenl mveletnek is tranzitvnak kell lennie. Vegyk pldul a kvetkez cmp rendezst: 1. cmp(x,x) mindig hamis. 2. Ha cmp(x,y) s cmp(y,z), akkor cmp(x,z). 3. Hatrozzuk meg az egyenlsg equiv(x,y) mvelett a kvetkez kifejezssel: !(cmp(x,y)||cmp(y,x)). gy, ha equiv(x,y) s equiv(y,z), akkor equiv(x,z). Ennek megfelelen a kvetkezket rhatjuk:
template<class Ran> void sort(Ran first, Ran last); // a < hasznlata az // sszehasonltsra template<class Ran, class Cmp> void sort(Ran first, Ran last, Cmp cmp); // a cmp hasznlata

Az els vltozat a < opertort hasznlja, mg a msodik a programoz ltal megadott cmp rendezst. Az elz fejezet gymlcsket tartalmaz troljnak esetben pldul szksgnk lehet egy olyan rendez eljrsra, amely nem klnbzteti meg a kis- s nagybetket. Ezt gy rhetjk el, hogy ltrehozunk egy fggvnyobjektumot (function object) (11.9, 18.4), amely egy string-prra elvgzi az sszehasonltst:
class Nocase { // kis- s nagybetket nem megklnbztet karakterlnc// sszehasonlts

public: bool operator()(const string&, const string&) const; };

bool Nocase::operator()(const string& x, const string& y) const // igazat ad vissza, ha x bcsorrendben megelzi y-t; a kis- s nagybetk kztti // klnbsg nem szmt { string::const_iterator p = x.begin(); string::const_iterator q = y.begin();

Forrs: http://www.doksi.hu

620

A standard knyvtr

while (p!=x.end() && q!=y.end() && toupper(*p)==toupper(*q)) { ++p; ++q; } if (p == x.end()) return q != y.end(); if (q == y.end()) return false; return toupper(*p) < toupper(*q);

A sort fggvnyt ezutn meghvhatjuk ezzel a rendezsi szemponttal. Pldul az albbi sorozatbl kiindulva: fruit: alma krte Alma Krte citrom A sort(fruit.begin(), fruit.end, Nocase()) rendezs eredmnye a kvetkez lesz: fruit: Alma alma citrom Krte krte Ugyanakkor az egyszer sort(fruit.begin(), fruit.end()) a kvetkez eredmnyt adn (felttelezve, hogy olyan karakterkszletet hasznlunk, ahol a nagybetk megelzik a kisbetket): fruit: Alma Krte alma citrom krte Vigyzzunk r, hogy a < opertor a C stlus karakterlncoknl (teht a char* tpusnl) nem bcsorrend szerinti rendezst jelent (13.5.2). Ennek pldul az a kvetkezmnye, hogy az olyan asszociatv trolk, melyeknek kulcsa C stlus karakterlnc, nem gy mkdnek, mint ahogy azt els rnzsre gondolnnk. Ezen kellemetlensg kijavtshoz egy olyan < opertort kell hasznlnunk, amely tnyleg bcsorrend szerinti sszehasonltst vgez:
struct Cstring_less { bool operator()(const char* p, const char* q) const { return strcmp(p,q)<0; } }; map<char*,int,Cstring_less> m; // const char* kulcsok sszehasonltsra a strcmp() // fggvnyt hasznl asszociatv tmb

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

621

17.1.4.2. Tovbbi relcis mveletek A trolk s az algoritmusok alaprtelmezs szerint a < mveletet hasznljk, amikor az elemek kztt sorrendet kell megllaptaniuk. Ha ez az alaprtelmezs nem felel meg a programoznak, akkor j sszehasonltsi szempontot is megadhat. Az egyenlsgvizsglat mvelett viszont nem adhatjuk meg kzvetlenl. Ha van egy sorrend-meghatroz fggvnynk (pldul a cmp), akkor az egyenlsget kt sorrendvizsglattal llapthatjuk meg:
if (x == y) // megadott felhasznli sszehasonlts esetn nem hasznlatos

if (!cmp(x,y) && !cmp(y,x)) // a felhasznli cmp sszehasonlts esetn hasznlatos

Ez a lehetsg megkml minket attl, hogy minden asszociatv trol s szmos algoritmus esetben kln paramterknt adjuk t az egyenlsgvizsgl eljrst. Els rnzsre azt mondhatnnk, hogy ez a megolds nem tl hatkony, de a trolk rendkvl ritkn vizsgljk elemeik egyenlsgt, s ilyenkor is ltalban elegend egyetlen cmp() hvs. A kisebb mint (alaprtelmezs szerint < ) mvelettel vgzett egyenrtksg-vizsglat gyakorlati okokbl is hasznosabb lehet, mint az egyenl (==) hasznlata. Az asszociatv trolk (17.4) pldul a ! (cmp(x,y)||cmp(y,x)) egyenrtksg-vizsglattal hasonltjk ssze a kulcsaikat. Az egyenrtk kulcsok nem felttlenl egyenlk. Egy olyan multimap (17.4.2) pldul, amelynek sorrend-meghatroz fggvnye nem klnbzteti meg a kiss nagybetket, a Last, last, lAst, s lasT karakterlncokat egyenrtknek fogja minsteni, annak ellenre, hogy az == opertor klnbznek tartja azokat. gy teht lehetsgnk nylik arra, hogy a szmunkra lnyegtelen klnbsgektl a rendezsben eltekintsnk. A < s az == opertor segtsgvel a tbbi szoksos sszehasonlt mveletet knnyen definilhatjuk. A standard knyvtr az std::rel_ops nvtrben adja meg ezeket s a <utility> fejllomny segtsgvel hasznlhatk:
template<class T> bool rel_ops::operator!=(const T& x, const T& y) { return !(x==y); } template<class T> bool rel_ops::operator>(const T& x, const T& y) { return y<x; } template<class T> bool rel_ops::operator<=(const T& x, const T& y) { return !(y<x); } template<class T> bool rel_ops::operator>=(const T& x, const T& y) { return !(x<y); }

A rel_ops nvtr alkalmazsa biztostja, hogy az opertorokat knnyen elrhetjk, amikor szksg van rjuk, viszont meghatrozsuk nem trtnik meg titokban, ha explicit nem krjk a nvtr hasznlatt.
void f() { using namespace std; // a !=, > stb. alaprtelmezs szerint nem jn ltre }

Forrs: http://www.doksi.hu

622

A standard knyvtr

void g() { using namespace std; using namespace std::rel_ops; // a !=, > stb. alaprtelmezs szerint ltrejn }

A !=, stb. opertorokat nem az std nvtr rja le, mert nem mindig van rjuk szksg, s bizonyos esetekben meghatrozsuk meg is vltoztatn a felhasznl programjnak mkdst. Pldul, ha egy ltalnos matematikai knyvtrat akarunk kszteni, akkor valsznleg a sajt relcis mveleteinkre lesz szksgnk s nem a beptett fggvnyekre.

17.2. Sorozatok
A sorozatok krlbell gy nznek ki, mint a korbban (16.3) bemutatott vector. A standard knyvtr ltal biztostott alapvet sorozatok a kvetkezk: vector list deque Ezekbl szrmaznak a megfelel fellet kialaktsval az albbi trolk: stack queue priority_queue Ezeket a sorozatokat trol-talaktknak (container adapter), sorozat-talaktknak (sequence adapter) vagy egyszeren talaktknak (adapter, 17.3) nevezzk.

17.2.1. A vector
A szabvnyos vector osztllyal a 16.3 pontban mr rszletesen foglalkoztunk. Az elzetes memriafoglals (reserve) lehetsge kizrlag a vektorokra jellemz. Az indexels a [ ] opertorral ellenrizetlen adatelrst jelent. Ha ellenrztt hozzfrst szeretnnk, hasznljuk az at() fggvnyt (16.3.3), egy ellenrztt vektort (3.7.2) vagy egy ellenrztt bejrt (19.3). A vector osztlyokban kzvetlen elrs (random-access) bejrkat (19.2.1) hasznlhatunk.

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

623

17.2.2. A list
A lista egy olyan sorozat, amely elemek beszrsra s trlsre a legalkalmasabb. A vector (s a deque, 17.2.3) osztllyal sszehasonltva az indexels fjdalmasan lass lenne, gy meg sem valstottk a list trolban. Ennek kvetkezmnye, hogy a list csak ktirny (bidirectional) bejrkat (19.2.1) biztost, kzvetlen elrseket nem hasznlhatunk. A list osztlyt ezek alapjn valamilyen ktirny lncolt listval szoks megvalstani (17.8[16]). A lista mindazon tagtpusok s tagfggvnyek hasznlatt lehetv teszi, melyet a vektor (16.3) esetben megtallunk, kivve az indexelst, a capacity() s a reserve() fggvnyeket:
template <class T, class A = allocator<T> > class std::list { public: // a vector-hoz hasonl tpusok s mveletek, kivve: [ ], at(), capacity(), s reserve() // ... };

17.2.2.1. thelyezs, rendezs, sszefsls Az ltalnos sorozat-mveletek mellett a list szmos, kifejezetten a listkhoz ksztett mveleteket knl:
template <class T, class A = allocator<T> > class list { public: // ... // kifejezetten listkhoz ksztett mveletek void splice(iterator pos, list& x); // x minden elemnek thelyezse // a listban lev pos el, msols nlkl void splice(iterator pos, list& x, iterator p); // *p thelyezse x-bl // a listban lev pos el, msols nlkl void splice(iterator pos, list& x, iterator first, iterator last); void merge(list&); // rendezett listk sszefslse template <class Cmp> void merge(list&, Cmp); void sort(); template <class Cmp> void sort(Cmp); }; // ...

Forrs: http://www.doksi.hu

624

A standard knyvtr

Ezek a listamveletek stabilak (stable), ami azt jelenti, hogy az egyenrtk rtkkel rendelkez elemek egymshoz viszonytott (relatv) sorrendje nem vltozik meg. A 16.3.6 pontban pldakppen a fruit troln vgeztnk mveleteket. Azok a feladatok vltoztats nlkl elvgezhetk akkor is, ha a fruit trtnetesen egy lista. Ezenkvl az egyik lista elemeit egyetlen splice mvelettel t is helyezhetjk egy msik listba. Induljunk ki az albbi listkbl: fruit: alma krte citrus: narancs grapefruit citrom A kvetkez mvelettel tudjuk a narancs elemet thelyezni a citrus listbl a fruit listba:
list<string>::iterator p = find_if(fruit.begin(),fruit.end(),initial('k')); fruit.splice(p,citrus,citrus.begin());

A mvelet kiveszi a citrus els elemt, majd beilleszti ezt az elemet a fruit trol els k betvel kezdd eleme el. Az eredmny teht a kvetkez: fruit: alma narancs krte citrus: grapefruit citrom Jegyezzk meg, hogy a splice() nem kszt msolatokat az elemekrl gy, mint az insert() (16.3.6), csak a listk adatszerkezett rendezi t a feladatnak megfelelen. A splice() segtsgvel nem csak egyetlen elemet, hanem tartomnyokat, vagy akr teljes listkat is thelyezhetnk:
fruit.splice(fruit.begin(),citrus);

Eredmnye: fruit: grapefruit citrom alma narancs krte citrus: <res>

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

625

A splice fggvny minden vltozatnak a msodik paramterben meg kell adnunk azt a list objektumot, amelybl az elemeket t kell helyezni. Ez teszi lehetv, hogy az eredeti listbl trljk az elemet. Egy bejr erre nmagban nem lenne alkalmas, hiszen egy konkrt elemre mutat bejrbl semmilyen ltalnos mdszerrel nem lehet megtudni, hogy az adott elem ppen melyik trolban tallhat (18.6). Termszetesen, a bejr-paramternek olyan rvnyes bejrt kell tartalmaznia, amely a megfelel listhoz kapcsoldik. Teht vagy a list egyik elemre kell mutatnia, vagy a lista end() fggvnynek rtkt kell tartalmaznia. Ha ez nem gy van, akkor az eredmny nem meghatrozhat s ltalban vgzetes problmkat okoz:
list<string>::iterator p = find_if(fruit.begin(),fruit.end(),initial('k')); fruit.splice(p,citrus,citrus.begin()); // rendben fruit.splice(p,citrus,fruit.begin()); // hiba: fruit.begin() nem mutat citrus-ban lev elemre citrus.splice(p,fruit,fruit.begin()); // hiba: p nem mutat citrus-ban lev elemre

Az els splice() helyes mg akkor is, ha a citrus trol res. A merge() kt rendezett listt egyest (fsl ssze). Az egyik listbl trli az elemeket, mikzben a msikba beszrja azokat, a rendezs megrzsvel. f1: alma birsalma krte f2: citrom grapefruit narancs lime Ez a kt lista az albbi programrszlettel rendezhet s fzhet ssze:
f1.sort(); f2.sort(); f1.merge(f2);

Az eredmny a kvetkez lesz: f1: alma birsalma citrom grapefruit krte f2: <res> lime narancs

Ha az sszefslt listk valamelyike nem volt rendezett, a merge() akkor is olyan listt eredmnyez, amelyben a kt lista elemeinek unija szerepel, de az elemek helyes sorrendje nem biztostott.

Forrs: http://www.doksi.hu

626

A standard knyvtr

A splice() fggvnyhez hasonlan a merge() sem msolja az elemeket, hanem a forrslistbl kiveszi, majd a cllistba beilleszti azokat. Az x.merge(y) fggvny meghvsa utn az y lista res lesz.

17.2.2.2. Mveletek a lista elejn A lista els elemre vonatkoz mveletek azoknak a minden sorozatban megtallhat eljrsoknak a prjai, melyek az utols elemre hivatkoznak (16.3.6):
template <class T, class A = allocator<T> > class list { public: // ... // hozzfrs elemekhez reference front(); const_reference front() const; void push_front(const T&); void pop_front(); }; // ... // hivatkozs az els elemre // j els elem hozzadsa // els elem eltvoltsa

A trol els elemnek neve front. A list esetben a front fejelem mveletei ugyanolyan hatkonyak s knyelmesek, mint a sorozat vgt kezel fggvnyek (16.3.5). Ha mi dnthetnk, akkor rdemesebb az utols elemet hasznlni, mert az gy megrt programrszletek a vector s a list szmra is megfelelek. gy ha csak egy kicsi esly is van arra, hogy az ltalunk listra hasznlt algoritmust valamikor ltalnos eljrsknt ms troltpusokra is alkalmazzuk, akkor mindenkppen az utols elem mveleteit hasznljuk, hiszen ezek jval szlesebb krben mkdkpesek. Itt is ahhoz a szablyhoz kell igazodnunk, miszerint a lehet legnagyobb rugalmassg elrse rdekben rdemes a lehet legkisebb mvelethalmazt hasznlnunk egy feladat elvgzshez (17.1.4.1).

17.2.2.3. Tovbbi mveletek Az elemek beszrsa s trlse rendkvl hatkony mvelet a list trolban. Ez termszetesen arra sztnzi a programozkat, hogy listt hasznljanak, ha programjaikban az ilyen mveletek gyakoriak. Ezrt fontos, hogy az elemek kzvetlen trlsre ltalnos, szabvnyos mdszereket adjunk:

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

627

template <class T, class A = allocator<T> > class list { public: // ... void remove(const T& val); template <class Pred> void remove_if(Pred p); void unique(); // ktszer szerepl elemek eltvoltsa == hasznlatval template <class BinPred> void unique(BinPred b); // ktszer szerepl elemek // eltvoltsa b hasznlatval }; void reverse(); // az elemek sorrendjnek megfordtsa

Pldul tegyk fel, hogy adott az albbi lista: fruit: alma narancs grapefruit citrom narancs grgdinnye krte birsalma Ekkor a narancs rtkkel rendelkez elemeket a kvetkez paranccsal tvolthatjuk el:
fruit.remove("narancs");

Az eredmny teht: fruit: alma grapefruit citrom grgdinnye krte birsalma Gyakran bizonyos felttelt kielgt elemeket szeretnnk trlni, nem csak egy adott rtkkel rendelkezket. A remove_if fggvny pontosan ezt a feladatot hajtja vgre. A kvetkez utasts pldul az sszes g betvel kezdd gymlcsnevet trli a fruit listbl:
fruit.remove_if(initial('g'));

A fggvny lefutsa utn a fruit a kvetkezkppen nz ki: fruit: alma citrom krte birsalma A trlseknek gyakran az az oka, hogy meg szeretnnk szntetni az ismtldseket. A unique() fggvny ezzel a cllal szerepel a list osztlyban:
fruit.sort(); fruit.unique();

Forrs: http://www.doksi.hu

628

A standard knyvtr

A rendezsre azrt van szksg, mert a unique() csak azokat az ismtldseket szri ki, amelyeknl kzvetlenl egyms utn szerepel kt (vagy tbb) azonos rtk. Pldul ha a fruit lista tartalma a kvetkez: alma krte alma alma krte akkor a fruit.unique() hvs nmagban az albbi eredmnyt adn: alma krte alma krte Ha elszr rendeznk, akkor a vgeredmny: alma krte Ha csak bizonyos ismtldseket akarunk megszntetni, akkor megadhatunk egy prediktumot (felttelt), amely meghatrozza, mely ismtldsek nem kellenek. Pldul meghatrozhatjuk a ktoperandus (binris) initial2(x) prediktumot (18.4.2), amely karakterlncokat vizsgl, s csak akkor ad igaz rtket, ha a karakterlnc x betvel kezddik. Teht ha a kvetkez listbl indulunk ki: krte krte alma alma akkor a kvetkez utastssal el tudjuk tntetni az sszes k betvel kezdd, egyms utn kvetkez nevet:
fruit.unique(initial2('k'));

Az eredmny a kvetkez lesz: krte alma alma A 16.3.2 pontban mr volt rla sz, hogy a trol elemeit nha fordtott sorrendben akarjuk elrni. A list esetben lehetsg van arra, hogy az elemek sorrendjt teljesen megfordtsuk, azaz az utols elem vljon elsv, az els pedig utolsv. A reverse() fggvny ezt a mveletet az elemek msolsa nlkl valstja meg. Vegyk a kvetkez listt: fruit: bann cseresznye eper krte

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

629

Ekkor a fruit.reverse() hvs eredmnye a kvetkez lesz: fruit: krte eper cseresznye bann

A listbl eltvoltott elemek teljesen trldnek. Egy mutat trlse azonban nem jelenti azt, hogy maga a mutatott objektum is trldik. Ha olyan mutatkbl ll trolt akarunk hasznlni, amely automatikusan trli a belle kivett mutatk ltal mutatott objektumokat, akkor azt neknk kell megrnunk (17.8[13]).

17.2.3. A deque
A deque egy ktvg sort (double-ended queue) valst meg. Ez azt jelenti, hogy a deque egy olyan sorozat, amit arra optimalizltak, hogy egyrszt mindkt vgn ugyanolyan hatkony mveleteket hasznlhassunk, mint egy list-nl, msrszt az indexels ugyanolyan hatkony legyen, mint a vector esetben:
template <class T, class A = allocator<T> > class std::deque { // a vector-hoz hasonl tpusok s mveletek (16.3.3, 16.3.5, 16.3.6), kivve: // capacity() s reserve() // a list-hez hasonl fejelem-mveletek (17.2.2.2) };

Az adatszerkezet belsejben az elemek trlse s beszrsa ugyanolyan (rossz) hatkonysg, mint a vector-nl. Ennek kvetkeztben a deque trolt akkor hasznljuk, ha beszrsok s trlsek ltalban csak a vgeken fordulnak el, pldul egy vastszakasz modellezshez vagy egy krtyapakli brzolshoz:
deque<car> siding_no_3; deque<Card> bonus;

17.3. Sorozat-talaktk
A vector, a list s a deque sorozatok nem pthetk fel egymsbl komoly hatkonysgromls nlkl. Ugyanakkor a verem (stack) s a sor (queue) elegnsan s hatkonyan megvalsthat e hrom alap-sorozattpus segtsgvel. gy ezt a kt osztlyt nem teljesen nll trolknt hatroztk meg, hanem az alaptrolk talaktiknt.

Forrs: http://www.doksi.hu

630

A standard knyvtr

Egy trol talaktja (adapter) egy leszktett (korltozott) felletet ad az adott trolhoz. A legszembetnbb, hogy az talaktk nem biztostanak bejrkat, mert a cljuknak megfelel felhasznlskor a leszktett fellet elegend szolgltatst nyjt. Azokat a mdszereket, melyekkel egy trolbl ltrehozhatunk egy talaktt, ltalnosan hasznlhatjuk olyan esetekben, amikor egy osztly szolgltatsait a felhasznlk ignyeihez szeretnnk igaztani, az eredeti osztly megvltoztatsa nlkl.

17.3.1. A stack
A stack (verem) trolt a <stack> fejllomny rja le. Annyira egyszer szerkezet, hogy lersnak legegyszerbb mdja, ha bemutatunk egy lehetsges megvalstst:
template <class T, class C = deque<T> > class std::stack { protected: C c; public: typedef typename C::value_type value_type; typedef typename C::size_type size_type; typedef C container_type; explicit stack(const C& a = C()) : c(a) { } bool empty() const { return c.empty(); } size_type size() const { return c.size(); } value_type& top() { return c.back(); } const value_type& top() const { return c.back(); } void push(const value_type& x) { c.push_back(x); } void pop() { c.pop_back(); }

};

Vagyis a stack egyszeren egy fellet egy olyan trolhoz, melynek tpust sablonparamterknt adjuk meg. A stack mindssze annyit tesz, hogy elrejti troljnak nem verem stlus mveleteit, valamint a back(), push_back(), pop_back() fggvnyeket hagyomnyos nevkn (top(), push(), pop()) teszi elrhetv. Alaprtelmezs szerint a stack egy deque trolt hasznl elemeinek trolsra, de brmilyen sorozatot hasznlhatunk, melyben elrhet a back(), a push_back() s a pop_back() fggvny:
stack<char> s1; stack< int,vector<int> > s2; // char tpus elemek trolsa deque<char> segtsgvel // int tpus elemek trolsa vector<int> segtsgvel

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

631

Egy verem kezdeti feltltshez felhasznlhatunk egy mr ltez trolt is:


void print_backwards(vector<int>& v) { stack< int,vector<int> > state(v); // kezdllapot belltsa v segtsgvel while (state.size()) { cout << state.top(); state.pop(); } }

De gondoljunk r, hogy ez a mvelet a paramterknt megadott trol elemeinek msolsval jr, gy rendkvl hosszadalmas lehet. A veremhez az elemek trolshoz hasznlt trol push_back() mveletvel adunk elemeket. gy a stack nem telhet meg mindaddig, amg a trol kpes memrit lefoglalni (memriafoglaljnak segtsgvel, 19.4). Ugyanakkor a verem alulcsordulhat:
void f() { stack<int> s; s.push(2); if (s.empty()) { // nincs kiemels } else { s.pop(); s.pop(); } }

// az alulcsorduls megakadlyozhat // de nem elkpzelhetetlen // j: s.size() rtke 0 lesz // nem meghatrozott hats, valsznleg rossz

Jegyezzk meg, hogy a fels elem hasznlathoz nincs szksgnk a pop() fggvnyre. Erre a top() utasts szolgl, a pop() mveletre pedig akkor van szksg, ha el akarjuk tvoltani a legfels elemet. Ez a megolds nem tlsgosan knyelmetlen, s sokkal hatkonyabb, amikor a pop() mveletre nincs szksg:
void f(stack<char>& s) { if (s.top()=='c') s.pop(); // ... }

// nem ktelez kezd 'c' eltvoltsa

Forrs: http://www.doksi.hu

632

A standard knyvtr

Az nllan, teljesen kifejlesztett trolkkal ellenttben a veremnek (s a tbbi trol-talaktnak) nem lehet memriafoglalt megadni sablonparamterknt, azt a stack megvalstshoz hasznlt trol sajt memriafoglalja helyettesti.

17.3.2. A queue
A <queue> fejllomnyban lert queue (sor) olyan fellet egy trolhoz, amely lehetv teszi az elemek beszrst az adatszerkezet vgre, illetve kivtelt a trol elejrl:
template <class T, class C = deque<T> > class std::queue { protected: C c; public: typedef typename C::value_type value_type; typedef typename C::size_type size_type; typedef C container_type; explicit queue(const C& a = C()) : c(a) { } bool empty() const { return c.empty(); } size_type size() const { return c.size(); } value_type& front() { return c.front(); } const value_type& front() const { return c.front(); } value_type& back() { return c.back(); } const value_type& back() const { return c.back(); } void push(const value_type& x) { c.push_back(x); } void pop() { c.pop_front(); } };

Alaprtelmezs szerint a queue a deque trolt hasznlja elemeinek trolshoz, de brmely sorozat betltheti ezt a szerepet, amely rendelkezik a front(), back(), push_back() s pop_front() fggvnyekkel. Mivel a vector nem teszi lehetv a pop_front() hasznlatt, a vektor nem lehet a sor bels trolja.

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

633

A sor szerkezet szinte minden rendszerben elfordul valamilyen formban. Egy egyszer zenetalap kiszolglt pldul a kvetkezkppen hatrozhatunk meg:
struct Message { // ... }; void server(queue<Message>& q) { while(!q.empty()) { Message& m = q.front(); m.service(); q.pop(); } }

// zenet elfogsa // a krst kiszolgl fggvny meghvsa // zenet trlse

Az zeneteket a push() utasts segtsgvel helyezhetjk a sorba. Ha az gyfl (kliens) s a kiszolgl (szerver) kln-kln folyamatknt vagy szlknt fut, akkor a sor-hozzfrseket valamilyen mdon ssze kell hangolnunk (szinkronizls):
void server2(queue<Message>& q, Lock& lck) { while(!q.empty()) { Message m; { LockPtr h(lck); // zrols az zenet kinyerse kzben (14.4.1) if (q.empty()) return; // valaki ms mr megszerezte az zenetet m = q.front(); q.pop(); } m.service(); // a krst kiszolgl fggvny meghvsa } }

Az egyidej hozzfrsnek (konkurrencia, prhuzamossg), illetve a zrolsnak (lock) mg nincs szabvnyos defincija sem a C++ nyelvben, sem a szmtstechnika vilgban. Ha ezeket a lehetsgeket szeretnnk hasznlni, akkor jrjunk utna, hogy sajt rendszernk milyen lehetsgeket biztost, s azokat hogyan rhetjk el a C++ nyelvbl (17.8[8]).

17.3.3. A priority_queue
A priority_queue (prioritsos sor) egy olyan sor, melyben az elemek fontossgi rtket kapnak, s ez az rtk szablyozza, hogy az elemek milyen sorrendben vehetk ki:

Forrs: http://www.doksi.hu

634

A standard knyvtr

template <class T, class C = vector<T>, class Cmp = less<typename C::value_type> > class std::priority_queue { protected: C c; Cmp cmp; public: typedef typename C::value_type value_type; typedef typename C::size_type size_type; typedef C container_type; explicit priority_queue(const Cmp& a1 = Cmp(), const C& a2 = C()) : c(a2), cmp(a1) { make_heap(c.begin(),c.end(),cmp); } template <class In> priority_queue(In first, In last, const Cmp& = Cmp(), const C& = C()); bool empty() const { return c.empty(); } size_type size() const { return c.size(); } const value_type& top() const { return c.front(); } void push(const value_type&); void pop(); // lsd 18.8

};

A priority_queue-t a <queue> fejllomny deklarlja. Alaprtelmezs szerint a priority_queue az elemeket egyszeren a < opertor segtsgvel hasonltja ssze, s a top() mindig a legnagyobb elemet adja vissza:
struct Message { int priority; bool operator<(const Message& x) const { return priority < x.priority; } // ... }; void server(priority_queue<Message>& q, Lock& lck) { while(!q.empty()) { Message m; { LockPtr h(lck); // zrols az zenet kinyerse kzben (14.4.1) if (q.empty()) return; // valaki ms mr megszerezte az zenetet m = q.top(); q.pop(); } m.service(); // a krst kiszolgl fggvny meghvsa } }

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

635

Ez a programrszlet abban klnbzik a sornl (queue, 17.3.2) bemutatott pldtl, hogy itt az zenetek kzl a legfontosabb (legnagyobb priorits) kerl elszr feldolgozsra. Az nem meghatrozott, hogy az azonos fontossgi rtkkel rendelkez elemek kzl melyik jelenik meg elszr a sor elejn. Kt elem fontossga (prioritsa) akkor egyezik meg, ha egyik sem fontosabb a msiknl (17.4.1.5). Ha a < opertor nem felel meg cljainknak, akkor egy sablonparamter segtsgvel megadhatjuk az sszehasonlt mveletet. Pldul karakterlncokat rendezhetnk a kis- s nagybetk megklnbztetse nlkl, ha az albbi sorban helyezzk el azokat:
priority_queue<string, vector<string>, Nocase> pq; // Nocase hasznlata az // sszehasonltsokhoz (17.1.4.1)

A karakterlncokat a pq.push() utastssal tesszk be a sorba s a pq.top(), pq.pop() mveletekkel dolgozhatjuk fel. Az olyan sablonokbl ltrehozott objektumok, melyeknl klnbz sablonparamtereket hasznltunk, klnbz tpusak (13.6.3.1):
priority_queue<string>& pq1 = pq; // hiba: nem megfelel tpus

sszehasonltsi szempontot megadhatunk gy is, hogy kzben nem vltoztatjuk meg a prioritsos sor tpust. Ehhez egy megfelel tpus sszehasonlt objektumot kell ltrehoznunk, melynek konstruktor-paramterben megadjuk az rvnyes sszehasonltsi szempontot:
struct String_cmp { String_cmp(int n = 0); // ... }; // futsi idej sszehasonltsi felttelt kifejez tpus // az n sszehasonltsi felttel hasznlata

typedef priority_queue<string, vector<string>, String_cmp> Pqueue; void g(Pqueue& pq) // a pq a String_cmp()-et hasznlja az sszehasonltsokhoz { Pqueue pq2(String_cmp(nocase)); pq = pq2; // rendben: pq s pq2 azonos tpus, gy pq most // a String_cmp(nocase)-t shasznlja }

Az elemek rendezse nmi teljestmnyromlssal jr, de ez egyltaln nem jelents. A priority_queue egyik hatkony megvalstsi mdja a faszerkezet, mellyel az elemek egy-

Forrs: http://www.doksi.hu

636

A standard knyvtr

mshoz viszonytott helyt knnyedn bellthatjuk. Ezzel a megoldssal a push() s a pop() mvelet kltsge is O(log(n)). Alaprtelmezs szerint a priority_queue egy vector trolt hasznl elemeinek nyilvntartsra, de brmely sorozattpus megfelel e clra, amely rendelkezik a front(), push_back() s pop_back() fggvnyekkel, valamint lehetv teszi kzvetlen elrs bejrk hasznlatt. A prioritsos sorok leggyakrabban hasznlt megvalstsi eszkze a heap (kupac vagy halom, 18.8)

17.4. Asszociatv trolk


Az asszociatv tmb az egyik leghasznosabb felhasznli tpus. Ennek kvetkeztben az elssorban szveg- vagy szimblum-feldolgozsra kifejlesztett nyelvekben gyakran beptett tpusknt szerepel. Az asszociatv tmb, melynek gyakori elnevezse a map (hozzrendels, lekpezs) s a dictionary (sztr) is, rtkprokat trol. Az egyik rtk a kulcs (key), melyet a msik rtk (a hozzrendelt rtk, mapped value) elrshez hasznlunk. Az asszociatv tmbt gy kpzelhetjk el, mint egy tmbt, melynek indexe nem felttlenl egy egsz szm:
template<class K, class V> class Assoc { public: V& operator[ ](const K&); // hivatkozs visszaadsa a K-nak megfelel V-re // ... };

Itt a K tpus kulcs segtsgvel a V tpus hozzrendelt rtket vlasztjuk ki. Az asszociatv trol az asszociatv tmb fogalmnak ltalnostsa. A map sablon a hagyomnyos asszociatv tmbnek felel meg, ahol minden kulcsrtkhez egyetlen rtket rendelnk. A multimap egy olyan asszociatv tmb, amely megengedi, hogy ugyanolyan kulcsrtkkel tbb elem is szerepeljen, mg a set s a multiset olyan egyedi asszociatv tmbk, melyekben nincs hozzrendelt rtk.

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

637

17.4.1. A map
A map (kulcs,rtk) prok sorozata, melyben a bejegyzseket a kulcs (key) alapjn gyorsan elrhetjk. A map trolban a kulcsok egyediek, azaz minden kulcshoz legfeljebb egy rtket rendelnk hozz. A map szerkezetben ktirny bejrkat hasznlhatunk (19.2.1). A map megkveteli, hogy a kulcsknt hasznlt tpusokban a kisebb mint mvelet hasznlhat legyen (17.1.4.1), s ez alapjn az elemeket rendezve trolja. Ennek kvetkeztben a map bejrsakor az elemeket rendezve kapjuk meg. Ha olyan elemeink vannak, melyek kztt nem lehet egyrtelm sorrendet megllaptani, vagy nincs szksg arra, hogy az elemeket rendezve troljuk, akkor hasznljuk inkbb a hash_map szerkezetet (17.6).

17.4.1.1. Tpusok A map a trolk szoksos tpus tagjain (16.3.1) kvl nhny, az osztly egyedi cljnak megfelel tpust is meghatroz:
template <class Key, class T, class Cmp = less<Key>, class A = allocator< pair<const Key,T> > > class std::map { public: // tpusok typedef Key key_type; typedef T mapped_type; typedef pair<const Key, T> value_type; typedef Cmp key_compare; typedef A allocator_type; typedef typename A::reference reference; typedef typename A::const_reference const_reference; typedef megvalsts_fgg1 iterator; typedef megvalsts_fgg2 const_iterator; typedef typename A::size_type size_type; typedef typename A::difference_type difference_type; typedef std::reverse_iterator<iterator> reverse_iterator; typedef std::reverse_iterator<const_iterator> const_reverse_iterator; // ...

};

Forrs: http://www.doksi.hu

638

A standard knyvtr

Jegyezzk meg, hogy a value_type a map esetben a (kulcs, rtk) prt (pair) jelenti. A hozzrendelt rtkek tpust a mapped_type adja meg. Teht a map nem ms, mint pair<const Key, mapped_type> tpus elemek sorozata. Szoks szerint a konkrt bejr-tpusok az adott megvalststl fggek. Mivel a map trolt leggyakrabban valamilyen faszerkezet segtsgvel valstjk meg, a bejrk ltalban biztostanak valamilyen fabejrst. A visszafel halad bejrkat a szabvnyos reverse_iterator sablonok (19.2.5) segtsgvel hatrozhatjuk meg.

17.4.1.2. Bejrk s prok A map a szoksos fggvnyeket biztostja a bejrk elrshez (16.3.2):
template <class Key, class T, class Cmp = less<Key>, class A = allocator< pair<const Key,T> > > class map { public: // ... // bejrk iterator begin(); const_iterator begin() const; iterator end(); const_iterator end() const; reverse_iterator rbegin(); const_reverse_iterator rbegin() const; reverse_iterator rend(); const_reverse_iterator rend() const; }; // ...

A map bejrsakor pair<const Key, mapped_type> tpus elemek sorozatn haladunk vgig. Pldul egy telefonknyv bejegyzseit az albbi eljrssal rathatjuk ki:
void f(map<string,number>& phone_book) { typedef map<string,number>::const_iterator CI; for (CI p = phone_book.begin(); p!=phone_book.end(); ++p) cout << p->first << '\t' << p->second << '\n'; }

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

639

A map bejri az elemeket kulcs szerint nvekv sorrendben adjk vissza (17.1.4.5), gy a phone_book bejegyzsei bcsorrendben jelennek meg. Egy pair els elemre a first, msodik elemre a second nvvel hivatkozhatunk, fggetlenl attl, hogy ppen milyen tpusak ezek az elemek:
template <class T1, class T2> struct std::pair { typedef T1 first_type; typedef T2 second_type; T1 first; T2 second; pair() :first(T1()), second(T2()) { } pair(const T1& x, const T2& y) :first(x), second(y) { } template<class U, class V> pair(const pair<U, V>& p) :first(p.first), second(p.second) { }

};

Az utols konstruktorra azrt van szksg, hogy a prokon tpuskonverzit is vgezhessnk (13.6.2):
pair<int,double> f(char c, int i) { pair<char,int> x(c,i); // ... return x; // pair<char,int>-rl pair<int,double>-ra val konverzi szksges .. return pair<int,double>(c,i); // konverzi szksges }

A map esetben a pr els tagja a kulcs, a msodik tagja a hozzrendelt rtk. A pair adatszerkezet nem csak a map megvalstsban hasznlhat, gy ez is egy nll osztly a standard knyvtrban. A pair lerst a <utility> fejllomnyban tallhatjuk meg. A pair tpus vltozk ltrehozst knnyti meg a kvetkez fggvny:
template <class T1, class T2> pair<T1,T2> std::make_pair(T1 t1, T2 t2) { return pair<T1,T2>(t1,t2); }

A pair alaprtelmezett kezdrtkeit kt elemtpusnak alaprtelmezett rtke adja. Fontos, hogy ha az elemek tpusa beptett, akkor a kezdrtk 0 (5.1.1), karakterlncok esetben pedig egy res karakterlnc (20.3.4). Olyan tpus, melynek nincs alaprtelmezett konstruktora, csak akkor lehet egy pair eleme, ha a pair kezdrtkt kifejezetten megadjuk.

Forrs: http://www.doksi.hu

640

A standard knyvtr

17.4.1.3. Indexels A legjellemzbb map-mvelet a hozzrendelses (asszocicis) keress, amit az indexel opertor valst meg:
template <class Key, class T, class Cmp = less<Key>, class A = allocator< pair<const Key,T> > > class map { public: // ... mapped_type& operator[ ](const key_type& k); // a k kulcs elem elrse // ... };

Az indexel opertor a megadott kulcsot indexnek tekintve egy keresst hajt vgre s visszaadja a kulcshoz rendelt rtket. Ha a kulcs nem tallhat meg a trolban, akkor ezzel a kulccsal s a mapped_type alaprtelmezett rtkvel egy j elem kerl az asszociatv tmbbe:
void f() { map<string,int> m; int x = m["Henry"]; m["Harry"] = 7; int y = m["Henry"]; m["Harry"] = 9;

// a map kezdetben res // j bejegyzs ksztse ("Henry"); a kezdrtk 0, a // visszaadott rtk 0 // j bejegyzs ksztse ("Harry"), a kezdrtk 0, kapott rtk 7 // a "Henry" bejegyzs rtknek visszaadsa // "Harry" rtknek mdostsa 9-re

Egy kicsit valsgszerbb plda: kpzeljnk el egy programot, amely kiszmtja a bemenetn megadott trgyak darabszmt. A listban (nv, mennyisg) bejegyzsek szerepelnek. Az sszestst el szeretnnk vgezni trgyanknt s az egsz listra is. A lista lehet pldul a kvetkez: szg 100 kalapcs 2 frsz 3 frsz 4 kalapcs 7 szg 1000 szg 250 A feladat nagy rszt elvgezhetjk, mikzben a (nv, mennyisg) prokat beolvassuk egy map trolba:
void readitems(map<string,int>& m) { string word; int val = 0; while (cin >> word >> val) m[word] += val; }

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

641

Az m[word] indexels azonostja a megfelel (string, int) prt, s visszaadja az int rtket. A fenti programrszletben kihasznljuk azt is, hogy az j elemek int rtke alaprtelmezs szerint 0. A readitems() fggvnnyel felptett map egy szoksos ciklus segtsgvel jelenthet meg:
int main() { map<string,int> tbl; readitems(tbl); int total = 0; typedef map<string,int>::const_iterator CI; for (CI p = tbl.begin(); p!=tbl.end(); ++p) { total += p->second; cout << p->first << '\t' << p->second << '\n'; } cout << "----------------\nsszesen\t" << total << '\n'; } return !cin;

A fenti bemenettel a fggvny eredmnye a kvetkez: frsz 7 kalapcs 9 szg 1350 -----------------------sszesen 1366 Figyeljk meg, hogy a nevek bcsorrendben jelennek meg (17.4.1, 17.4.1.5). Az indexel opertornak meg kell tallnia a megadott kulcsrtket a trolban. Ez termszetesen nem olyan egyszer mvelet, mint a tmbk esetben az egsz rtkkel val indexels. A pontos kltsg: O(log(a_map_mrete)). Ez sok alkalmazs esetben mg elfogadhat, ha azonban a mi cljainknak tl drga, rdemesebb hast trolt (17.6) hasznlnunk. Ha a map trolban nem tallhat meg egy kulcs, akkor az erre vonatkoz indexelssel ltrehozunk egy alaprtelmezett elemet. Ennek kvetkeztben a const map osztlyok esetben nem hasznlhatjuk az operator[ ]() mveletet. Radsul az indexels csak akkor hasznlhat, ha a mapped_type tpusnak van alaprtelmezett rtke. Ha csak arra vagyunk kvncsiak, hogy egy adott kulcs megtallhat-e a trolban, akkor hasznljuk a find() mveletet (17.4.1.6), ami a map megvltoztatsa nlkl keresi meg a kulcsot.

Forrs: http://www.doksi.hu

642

A standard knyvtr

17.4.1.4. Konstruktorok A map a szoksos konstruktorokat s egyb fggvnyeket biztostja (16.3.4):


template <class Key, class T, class Cmp =less<Key>, class A =allocator<pair<const Key,T> > > class map { public: // ... // ltrehozs/msols/megsemmists: explicit map(const Cmp& = Cmp(), const A& = A()); template <class In> map(In first, In last, const Cmp& = Cmp(), const A& = A()); map(const map&); ~map(); map& operator=(const map&); }; // ...

A trol lemsolsa azt jelenti, hogy helyet foglalunk az elemeknek, majd mindegyikrl msolatot ksztnk (16.3.4). Ez nagyon kltsges mvelet is lehet, ezrt csak akkor hasznljuk, ha elkerlhetetlen. Ebbl kvetkezik, hogy az olyan trolkat, mint a map, csak referencia szerint rdemes tadni. A sablon konstruktor pair<const Key, T> elemek sorozatt kapja paramterknt, amelyet egy bemeneti bejr-prral adunk meg. A fggvny a sorozat elemeit az insert() mvelet segtsgvel szrja be az asszociatv tmbbe.

17.4.1.5. sszehasonltsok Ahhoz, hogy egy adott kulcs elemet megtalljunk egy map-ben, a map mveleteinek ssze kell tudnia hasonltani a kulcsokat. A bejrk is a kulcsok nvekv rtkei szerint haladnak vgig a troln, gy a beszrsok is kulcs-sszehasonltsokat vgeznek (ahhoz, hogy az elemeket elhelyezzk a map trolt brzol faszerkezetben). Alaprtelmezs szerint a kulcsok sszehasonltshoz hasznlt mvelet a < (kisebb mint) opertor, de egy sablon- vagy konstruktor-paramterben ms fggvnyt is megadhatunk (17.3.3). Az itt megadott rendezsi szempont a kulcsokat hasonltja ssze, mg a map ese-

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

643

tben a value_type (kulcs, rtk) prokat jelent. Ezrt van szksg a value_comp() fggvnyre, amely a kulcsokat sszehasonlt eljrs alapjn a prokat hasonltja ssze:
template <class Key, class T, class Cmp = less<Key>, class A = allocator< pair<const Key,T> > > class map { public: // ... typedef Cmp key_compare; class value_compare : public binary_function<value_type,value_type,bool> { friend class map; protected: Cmp cmp; value_compare(Cmp c) : cmp(c) {} public: bool operator()(const value_type& x, const value_type& y) const { return cmp(x.first, y.first); } }; key_compare key_comp() const; value_compare value_comp() const; // ...

};

Pldul:
map<string,int> m1; map<string,int,Nocase> m2; // sszehasonlts tpusnak megadsa (17.1.4.1) map<string,int,String_cmp> m3; // sszehasonlts tpusnak megadsa (17.1.4.1) map<string,int,String_cmp> m4(String_cmp(literary)); // sszehasonltand objektum // tadsa

A key_comp() s a value_comp() tagfggvny lehetv teszi, hogy az asszociatv tmbben az egsz elemekre a csak kulcsokra, illetve a csak rtkekre vonatkoz sszehasonlt mveleteket hasznljuk. Erre legtbbszr akkor van szksg, ha ugyanazt az sszehasonltst szeretnnk hasznlni egy msik trolban vagy algoritmusban is:
void f(map<string,int>& m) { map<string,int> mm; // sszehasonlts alaprtelmezs szerint < opertorral map<string,int> mmm(m.key_comp()); // m szerinti sszehasonlts // ... }

Forrs: http://www.doksi.hu

644

A standard knyvtr

A 17.1.4.1 pontban pldt lthatunk arra, hogyan adhatunk meg egyedi sszehasonltsokat, a 18.4 pontban pedig a fggvnyobjektumok ltalnos bemutatsval foglalkozunk.

17.4.1.6. Mveletek asszociatv tmbkkel A map s termszetesen az sszes asszociatv trol legfontosabb tulajdonsga, hogy egy kulcs alapjn frhetnk hozz az informcikhoz. Ezen cl megvalstshoz szmos egyedi fggvny ll rendelkezsnkre:
template <class Key, class T, class Cmp = less<Key>, class A = allocator< pair<const Key,T> > > class map { public: // ... // map-mveletek iterator find(const key_type& k); // a k kulcs elem megkeresse const_iterator find(const key_type& k) const; size_type count(const key_type& k) const; // a k kulcs elemek szmnak // meghatrozsa iterator lower_bound(const key_type& k); // az els k kulcs elem megkeresse const_iterator lower_bound(const key_type& k) const; iterator upper_bound(const key_type& k); // az els k-nl nagyobb kulcs elem // megkeresse const_iterator upper_bound(const key_type& k) const; pair<iterator,iterator> equal_range(const key_type& k); pair<const_iterator,const_iterator> equal_range(const key_type& k) const; }; // ...

Az m.find(k) mvelet egyszeren visszaad egy k kulcs elemre hivatkoz bejrt. Ha ilyen elem nincs, akkor a visszaadott bejr az m.end(). Egy egyedi kulcsokkal rendelkez trol esetben (mint a map s a set) az eredmny az egyetlen k kulcs elemre mutat bejr lesz. Ha a trol nem teszi ktelezv egyedi kulcsok hasznlatt (mint a multimap s a multiset), akkor a visszaadott bejr az els megfelel kulcs elem lesz:
void f(map<string,int>& m) { map<string,int>::iterator p = m.find("Arany");

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

645

if (p!=m.end()) { // ... } else if (m.find("Ezst")!=m.end()) { // ... } // ...

// ha "Arany"-at talltunk // "Ezst" keresse

Egy multimap (17.4.2) esetben az els k kulcs elem megkeresse helyett ltalban az sszes ilyen elemre szksgnk van. Az m.lower_bound(k) s az m.upper_bound(k) fggvnyekkel az m k kulcs elemeibl ll rszsorozat elejt, illetve vgt krdezhetjk le. Szoks szerint, a sorozat vgt jelz bejr az utols utni elemre mutat:
void f(multimap<string,int>& m) { multimap<string,int>::iterator lb = m.lower_bound("Arany"); multimap<string,int>::iterator ub = m.upper_bound("Arany"); for (multimap<string,int>::iterator p = lb; p!=ub; ++p) { // ... }

Az als s a fels hatr meghatrozsa kt kln mvelettel nem tl elegns s nem is hatkony. Ezrt ll rendelkezsnkre az equal_range() fggvny, amely mindkt rtket visszaadja:
void f(multimap<string,int>& m) { typedef multimap<string,int>::iterator MI; pair<MI,MI> g = m.equal_range("Arany"); for (MI p = g.first; p!=g.second; ++p) { // ... }

Ha a lower_bound(k) nem tallja meg a k kulcsot, akkor az els olyan elemre hivatkoz bejrt adja vissza, melynek kulcsa nagyobb, mint k, illetve az end() bejrt, ha nem ltezik k-nl nagyobb kulcs. Ugyanezt a hibajelzsi mdot hasznlja az upper_bound() s az equal_range() is.

Forrs: http://www.doksi.hu

646

A standard knyvtr

17.4.1.7. Listamveletek Ha egy asszociatv tmbbe j rtket szeretnnk bevinni, akkor a hagyomnyos megolds az, hogy egyszer indexelssel rtket adunk az adott kulcs elemnek:
phone_book["Rendelsi osztly"] = 8226339;

Ez a sor biztostja, hogy a phone_book trolban meglesz a Rendelsi osztly bejegyzs a megfelel rtkkel, fggetlenl attl, hogy korbban ltezett-e mr ilyen kulcs. Elemeket beilleszthetnk kzvetlenl az insert() fggvny segtsgvel is, s az erase() szolgl az elemek trlsre:
template <class Key, class T, class Cmp = less<Key>, class A = allocator< pair<const Key,T> > > class map { public: // ... // listamveletek pair<iterator, bool> insert(const value_type& val); iterator insert(iterator pos, const value_type& val); template <class In> void insert(In first, In last); void erase(iterator pos); size_type erase(const key_type& k); void erase(iterator first, iterator last); void clear(); }; // ... // (kulcs,rtk) pr beszrsa // a pos csak javaslat // elemek beszrsa sorozatbl

// a mutatott elem trlse // a k kulcs elem trlse (ha van ilyen) // tartomny trlse // minden elem trlse

Az m.insert(val) fggvny beszrja a val rtket, amely egy (Key, T) prt ad meg. Mivel a map egyedi kulcsokkal foglalkozik, a beszrsra csak akkor kerl sor, ha az m trolban mg nincs ilyen kulccsal rendelkez elem. Az m.insert(val) visszatrsi rtke egy pair<iterator,bool>. A pr msodik, logikai tagja akkor igaz, ha a val rtk tnylegesen bekerlt a trolba. A bejr az m azon elemt jelli ki, melynek kulcsa megegyezik a val kulcsval (val.first):
void f(map<string,int>& m) { pair<string,int> p99("Pali",99); pair<map<string,int>::iterator,bool> p = m.insert(p99); if (p.second) {

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

647

} else {

// "Pali"-t beszrtuk

// "Pali" mr szerepelt } map<string,int>::iterator i = p.first; // points to m["Pali"] // ...

ltalban nem rdekel minket, hogy a kulcs korbban mr szerepelt-e a map-ben vagy j elemknt kerlt be. Ha mgis erre vagyunk kvncsiak, annak oka ltalban az, hogy a kvnt kulcs egy msik map trolba is kerlhet az ltalunk vizsglt helyett, s ezt szre kell vennnk. Az insert() msik kt vltozata nem jelzi, hogy az j rtk tnyleg bekerlt-e a trolba. Ha az insert(pos,val) forma szerint egy pozcit is megadunk, akkor csak egy ajnlst adunk, hogy a rendszer a val kulcsnak keresst a pos pozcitl kezdje. Ha az ajnls helyes, jelents teljestmnynvekedst rhetnk el. Ha nem tudunk j ajnlst adni, hasznljuk inkbb az elz vltozatot, mind az olvashatsg, mind a hatkonysg rdekben:
void f(map<string,int>& m) { m["Dilbert"] = 3; // elegns, de valsznleg kevsb hatkony m.insert(m.begin(),make_pair(const string("Dogbert"),99)); // csnya }

Valjban a [ ] egy kicsit tbb, mint egyszeren az insert() knyelmesebb alakja. Az m[k] eredmnye a kvetkez kifejezssel egyenrtk:
(*(m.insert(make_pair(k,V())).first)).second

ahol a V() a hozzrendelt rtk alaprtelmezett rtkt jelli. Ha ezt az egyenrtksget megrtettk, akkor valsznleg mr rtjk az asszociatv trolkat is. Mivel a [ ] mindenkppen hasznlja a V() rtket, nem hasznlhatjuk az indexelst olyan map esetben, melynek rtktpusa nem rendelkezik alaprtelmezett rtkkel. Ez egy sajnlatos hinyossga az asszociatv trolknak, annak ellenre, hogy az alaprtelmezett rtk meglte nem alapvet kvetelmnyk (ld. 17.6.2). Az elemek trlst is kulcs szerint vgezhetjk el:
void f(map<string,int>& m) { int count = m.erase("Ratbert"); // ... }

Forrs: http://www.doksi.hu

648

A standard knyvtr

A visszaadott egsz rtk a trlt elemek szmt adja meg. Teht a count tartalma 0, ha nincs Ratbert kulcs elem a trolban. A multimap s a multiset esetben a visszaadott rtk egynl nagyobb is lehet. Egy elemet egy r mutat bejr segtsgvel trlhetnk, elemek sorozatt pedig a megfelel tartomny kijellsvel:
void g(map<string,int>& m) { m.erase(m.find("Catbert")); m.erase(m.find("Alice"),m.find("Wally")); }

Termszetesen gyorsabban lehet trlni egy olyan elemet, melynek mr megtalltuk a bejrjt, mint egy olyat, melyet elszr a kulcs alapjn meg kell keresnnk. Az erase() utn a bejrt tovbb mr nem hasznlhatjuk, mivel az ltala mutatott elem megsemmislt. Az m.erase(b,e) hvsa, ahol e rtke m.end() veszlytelen (b vagy m valamelyik elemre hivatkozik, vagy m.end()). Ugyanakkor az m.erase(p) meghvsa, amennyiben p rtke m.end(), slyos hiba s tnkreteheti a trolt.

17.4.1.8. Tovbbi mveletek A map biztostja azokat a szoksos fggvnyeket, melyek az elemek szmval foglalkoznak, illetve egy specializlt swap() fggvnyt:
template <class Key, class T, class Cmp = less<Key>, class A = allocator< pair<const Key,T> > > class map { public: // ... // kapacits: size_type size() const; // elemek szma size_type max_size() const; // a lehetsges legnagyobb map mrete bool empty() const { return size()==0; } }; void swap(map&);

Szoks szerint a size() s a max_size() ltal visszaadott rtk valamilyen elemszm. Ezenkvl a map hasznlatakor rendelkezsnkre llnak az sszehasonlt opertorok (==, !=, <, >, <=, >=), valamint a swap() eljrs is, ezek azonban nem tagfggvnyei a map osztlynak:

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

649

template <class Key, class T, class Cmp, class A> bool operator==(const map<Key,T,Cmp,A>&, const map<Key,T,Cmp,A>&); // ugyangy !=, <, >, <=, and >= template <class Key, class T, class Cmp, class A> void swap(map<Key,T,Cmp,A>&, map<Key,T,Cmp,A>&);

Mirt lehet szksg arra, hogy kt asszociatv tmbt sszehasonltsunk? Ha kt map objektumot hasonltunk ssze, ltalban nem csak azt akarjuk megtudni, hogy egyenlek-e, hanem azt is, hogy miben klnbznek, ha klnbznek. Ilyenkor teht az == s a != nem hasznlhat. Az ==, a < s a swap() megvalstsval azonban lehetv vlik, hogy olyan algoritmusokat ksztsnk, melyek minden trolra alkalmazhatk. Ezekre a fggvnyekre pldul akkor lehet szksgnk, ha asszociatv tmbkbl ll vektort szeretnnk rendezni vagy map trolk halmazra van szksgnk.

17.4.2. A multimap
A multimap nagyon hasonlt a map trolra, azzal a kivtellel, hogy ugyanaz a kulcs tbbszr is szerepelhet:
template <class Key, class T, class Cmp = less<Key>, class A = allocator< pair<const Key,T> > > class std::multimap { public: // mint a map, kivve a kvetkezt: iterator insert(const value_type&); }; // nincs indexel opertor ([ ]) // bejrt ad vissza, nem prt

Pldul (felhasznlva a 17.1.4.1 pontban C stlus karakterlncok sszehasonltshoz bemutatott CString_less osztlyt):
void f(map<char*,int,Cstring_less>& m, multimap<char*,int,Cstring_less>& mm) { m.insert(make_pair("x",4)); m.insert(make_pair("x",5));// nincs hatsa: "x" mr szerepel (17.4.1.7) // most m["x"] == 4

Forrs: http://www.doksi.hu

650

A standard knyvtr

mm.insert(make_pair("x",4)); mm.insert(make_pair("x",5)); // mm most ("x",4)-et s ("x",5)-t is tartalmazza

Teht a multimap trolban nem lehet olyan indexelst megvalstani, mint a map esetben. Itt az equal_range(), a lower_bound() s az upper_bound() mvelet (17.4.1.6) jelenti az azonos kulcs elemek elrsre szolgl legfbb eszkzt. Termszetesen ha ugyanahhoz a kulcshoz tbb rtket is hozzrendelhetnk, akkor a map helyett felttlenl a multimap szerkezetet hasznljuk. Ez pedig sokkal gyakrabban elfordul, mint gondolnnk. Bizonyos tekintetben a multimap sokkal tisztbb s elegnsabb megoldst ad, mint a map. Mivel egy embernek tbb telefonszma is lehet, a telefonknyvet is rdemesebb multimap formban elkszteni. Sajt telefonszmaimat pldul a kvetkez programrszlettel jelenthetem meg:
void print_numbers(const multimap<string,int>& phone_book) { typedef multimap<string,int>::const_iterator I; pair<I,I> b = phone_book.equal_range("Stroustrup"); for (I i = b.first; i != b.second; ++i) cout << i->second << '\n'; }

A multimap esetben az insert() mindig beilleszti a paramtereknt megadott elemet, gy nincs szksg a pair<iterator, bool> tpus visszatrsi rtkre; a multimap::insert() egyetlen bejrt ad vissza. Az egysgessg rdekben a knyvtr tartalmazhatn az insert() ltalnos formjt, mind a map-hez, mind a multimap-hez, annak ellenre, hogy a bool rsz felesleges a multimap osztlyban. Egy msik tervezsi szemllet szerint elkszthettk volna az egyszer insert() fggvnyt mindkt trolhoz, amely nem ad vissza logikai rtket. Ez esetben viszont a map felhasznlinak valamilyen ms lehetsget kellett volna biztostani annak megllaptshoz, hogy j kulcs kerlt-e a trolba. Ebben a krdsben a klnbz fellettervezsi elmletek tkztek, s nem szletett megegyezs.

17.4.3. A set
A set (halmaz) olyan egyedi map (17.4.1) trolnak tekinthet, ahol a hozzrendelt rtkek rdektelenek, gy csak a kulcsokat tartjuk meg. Ez a felletnek csupn apr mdostst jelenti:

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

651

template <class Key, class Cmp = less<Key>, class A = allocator<Key> > class std::set { public: // mint a map, kivve a kvetkezt typedef Key value_type; typedef Cmp value_compare; // nincs indexel opertor ([ ]) }; // a kulcs maga az rtk

A value_type tpust teht itt a key_type tpussal azonosknt hatrozzuk meg. Ez a trkk lehetv teszi, hogy nagyon sok esetben ugyanazzal a programmal kezeljnk egy map s egy set trolt. Figyeljk meg, hogy a set egy sorrendvizsgl opertort hasznl (alaprtelmezs szerint <), s nem egyenlsget (==) ellenriz. Ennek kvetkeztben az elemek egyenrtksgt a nem-egyenlsg hatrozza meg (17.1.4.1) s a halmaz bejrsakor az elemeket meghatrozott sorrendben kapjuk meg. Ugyangy, mint a map esetben, itt is rendelkezsnkre llnak a halmazokat sszehasonlt opertorok (==, !=,< , >, <=, >=) s a swap() fggvny is.

17.4.4. A multiset
A multiset egy olyan halmaz, amely megengedi, hogy ugyanazok a kulcsok tbbszr is elforduljanak:
template <class Key, class Cmp = less<Key>, class A = allocator<Key> > class std::multiset { public: // mint a set, kivve a kvetkezt: iterator insert(const value_type&); // bejrt ad vissza, nem prt };

A tbbszr elfordul kulcsok elrshez elssorban az equal_range(), a lower_bound() s az upper_bound() fggvnyeket hasznlhatjuk.

Forrs: http://www.doksi.hu

652

A standard knyvtr

17.5. Majdnem-trolk
A beptett tmbk (5.2), a karakterlncok (20. fejezet), valamint a valarray (22.4) s a bitset (17.5.3) osztlyok is elemeket trolnak, gy sok szempontbl trolnak tekinthetjk ket. Mindegyik hordoz azonban nhny olyan vonst, amely ellentmond a szabvnyos trolk elveinek, ezrt ezek a majdnem-trolk nem mindig cserlhetk fel a teljesen kifejlesztett trolkkal, pldul a vector vagy a list osztlyokkal.

17.5.1. Karakterlncok
A basic_string osztly lehetsget ad indexelsre, hasznlhatunk kzvetlen elrs bejrkat s a trolknl megszokott knyelmi lehetsgek tbbsge is rendelkezsnkre ll (20. fejezet). A basic_string azonban nem teszi lehetv, hogy annyifle tpust hasznljunk elemknt, mint a trolkban. Leginkbb karakterlncokhoz felel meg s ltalban a trolktl jelentsen eltr formban hasznljuk.

17.5.2. A valarray
A valarray kifejezetten a szmokkal vgzett mveletekhez kszlt vektor, gy nem is akar ltalnos trol lenni. Ehelyett szmos hasznos matematikai mveletet biztost, mg a szabvnyos trolk mveletei (17.1.1) kzl csak a size() s az indexels ll a rendelkezsnkre (22.4.2). A valarray elemeit kzvetlen elrs bejrval rhetjk el (19.2.1).

17.5.3. Bithalmazok
Egy rendszer szempontjbl gyakran van szksg arra, hogy pldul egy bemeneti adatfolyamot (21.3.3) egy sor ktrtk (pldul j/rossz, igaz/hamis, kikapcsolt/bekapcsolt) llapotjelzvel rjunk le. A C++ egsz rtkekre megvalstott bitszint mveletek segtsgvel tmogatja az ilyen llapotjelzk hatkony kezelst (amennyiben csak nhny van bellk). Ilyen mvelet az & (s), a | (vagy), a ^ (kizr vagy), a << (lptets balra) s a >> (lptets jobbra). A bitset<N> osztly ezt a fogalmat ltalnostja. Segtsgvel egyszeren vgezhetnk mveleteket N darab biten, melyeket 0-tl N1-ig indexelnk. Az N rtkt fordtsi idben rgztjk. Egy olyan bitsorozat esetben, amely nem fr el egy long int vltozban, a bitset hasznlata sokkal knyelmesebb, mint az int rtkek kzvetlen kezelse. Kevesebb jelzbit esetben hatkonysgi szempontok alapjn kell dntennk. Ha a biteket sorszmozs helyett el szeretnnk nevezni, halmazt (set, 17.4.3), felsorolsi tpust (4.8), vagy bitmezt (bitfield, C.8.1) hasznlhatunk.

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

653

A bitset<N> N darab bit tmbjnek tekinthet. A bitset a vector<bool> osztlytl abban klnbzik, hogy rgztett mret, a set troltl abban, hogy egszekkel s nem asszociatv rtkkel indexelhet, illetve mindketttl eltr abban, hogy kzvetlen bitmveleteket knl. Egy beptett mutat (5.1) segtsgvel nem tudunk biteket kijellni, ezrt a bitset osztlynak meg kell hatroznia egy hivatkozs bitre tpust. Ez a mdszer ltalnosan hasznlhat olyan objektumok esetben, melyekhez a beptett mutatk valamilyen okbl nem hasznlhatk:
template<size_t N> class std::bitset { public: class reference { // hivatkozs egyetlen bitre friend class bitset; reference(); public: // b[i] az (i+1)-edik bitre hivatkozik ~reference(); reference& operator=(bool x); // b[i] = x; reference& operator=(const reference&); // b[i] = b[j]; bool operator~() const; // return ~b[i] operator bool() const; // x = b[i]; reference& flip(); // b[i].flip(); }; }; // ...

A bitset sablon az std nvtrhez tartozik s a <bitset> fejllomny segtsgvel rhetjk el. A C++ trtnetre visszanyl okokbl a bitset nhny szempontbl eltr a standard knyvtr osztlyainak stlustl. Pldul, ha egy index (amit gyakran bitpozcinak neveznk) kvl esik a megengedett tartomnyon, akkor egy out_of_range kivtel keletkezik. Nem llnak rendelkezsnkre bejrk. A bitpozcikat jobbrl balra szmozzuk, ugyangy, ahogy a biteket egy gpi szban. gy a b[i] bit rtke pow(2,i). A bitset teht egy N bites binris szmnak tekinthet:

pozici: bitset<10>(989)

9 1

8 1

7 1

6 1

5 0

4 1

3 1

2 1

1 0

0 1

Forrs: http://www.doksi.hu

654

A standard knyvtr

17.5.3.1. Konstruktorok Egy bitset objektumot ltrehozhatunk alaprtelmezett rtkkel, valamint egy unsigned long int vagy egy string bitjeibl is:
template<size_t N> class bitset { public: // ... // konstruktorok bitset(); bitset(unsigned long val); // N nulla bit // bitek a val-bl

template<class Ch, class Tr, class A> // Tr karakter-jellemz (20.2) explicit bitset(const basic_string<Ch,Tr,A>& str, // bitek az str karakterlncbl basic_string<Ch,Tr,A>::size_type pos = 0, basic_string<Ch,Tr,A>::size_type n = basic_string<Ch,Tr,A>::npos); }; // ...

A bitek alaprtelmezett rtke 0. Ha unsigned long int paramtert adunk meg, akkor a szm minden egyes bitje a neki megfelel bit kezdrtkt hatrozza meg. A basic_string (20. fejezet) ugyangy mkdik; a 0 karakter jelenti a 0 bitrtket, az 1 karakter az 1 bitrtket. Minden ms karakter invalid_argument kivtelt vlt ki. Alaprtelmezs szerint a rendszer a bitek belltshoz teljes karakterlncot hasznl, de a basic_string konstruktornak (20.3.4) megfelel stlusban megadhatjuk, hogy a karaktereknek csak egy rsztartomnyt hasznljuk, a pos pozcitl kezdve a lnc vgig vagy a pos+n pozciig:
void f() { bitset<10> b1;

// all 0 // 1010101010101010 // 00000000000000001010101010101010 // 1010101010 // 0111011110 // 0011011101

bitset<16> b2 = 0xaaaa; bitset<32> b3 = 0xaaaa; bitset<10> b4("1010101010"); bitset<10> b5("10110111011110",4); bitset<10> b6("10110111011110",2,8); bitset<10> b7("n0g00d"); bitset<10> b8 = "n0g00d";

// invalid_argument vltdott ki // hiba: nincs char*-rl bitset-re val talakts

A bitset osztly alapvet clja az, hogy egyetlen gpi sz helyigny bitsorozathoz hatkony megoldst adhassunk. A felletet ennek a szemlletnek megfelelen alaktottk ki.

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

655

17.5.3.2. Bitkezel mveletek A bitset szmos olyan mveletet biztost, melyekkel egyes biteket rhetnk el vagy az sszes bitet egyszerre kezelhetjk:
template<size_t N> class std::bitset { public: // ... // bithalmaz-mveletek reference operator[ ](size_t pos); bitset& operator&=(const bitset& s); bitset& operator|=(const bitset& s); bitset& operator^=(const bitset& s); bitset& operator<<=(size_t n); bitset& operator>>=(size_t n); bitset& set(); bitset& set(size_t pos, int val = 1); bitset& reset(); bitset& reset(size_t pos); bitset& flip(); bitset& flip(size_t pos); // b[i] // s // vagy // kizr vagy // logikai eltols balra (feltlts nullkkal) // logikai eltols jobbra (feltlts nullkkal) // minden bit 1-re lltsa // b[pos]=val // minden bit 0-ra lltsa // b[pos]=0 // minden bit rtknek mdostsa // b[pos] rtknek mdostsa

bitset operator~() const { return bitset<N>(*this).flip(); }

// komplemens halmaz // ltrehozsa bitset operator<<(size_t n) const { return bitset<N>(*this)<<=n; } // eltolt halmaz // ltrehozsa bitset operator>>(size_t n) const { return bitset<N>(*this)>>=n; } // eltolt halmaz // ltrehozsa // ...

};

Az indexel opertor out_of_range kivtelt vlt ki, ha a megadott index kvl esik a megengedett tartomnyon. Nem ellenrztt indexelsre nincs lehetsg. A mveletek ltal visszaadott bitset& rtk a *this. Azok a mveletek, melyek a bitset& helyett bitset rtket adnak vissza, egy msolatot ksztenek a *this objektumrl, ezen a msolaton vgzik el a kvnt mveletet, s ennek eredmnyt adjk vissza. Fontos, hogy a << s a >> mveletek itt tnyleg eltolst jelentenek s nem be- vagy kimeneti mveleteket.

Forrs: http://www.doksi.hu

656

A standard knyvtr

A bitset kimeneti opertora egy olyan << fggvny, melynek kt paramtere egy ostream s egy bitset (17.5.3.3). Amikor a biteket eltoljuk, logikai eltols trtnik (teht nem ciklikus). Ez azt jelenti, hogy nhny bit kiesik, msok pedig az alaprtelmezett 0 rtket kapjk meg. Mivel a size_t tpus eljel nlkli, arra nincs lehetsgnk, hogy negatv szmmal toljuk el a bitsorozatot. Ennek kvetkeztben a b<<-1 utasts egy igen nagy pozitv szmmal tolja el a biteket, gy a b bitset minden bitjnek rtke 0 lesz. A fordtk ltalban figyelmeztetnek erre a hibra.

17.5.3.3. Tovbbi mveletek A bitset is tmogatja az olyan szoksos mveleteket, mint a size(), az ==, a ki- s bemenet stb.:
template<size_t N> class bitset { public: // ... unsigned long to_ulong() const; template <class Ch, class Tr, class A> basic_string<Ch,Tr,A> to_string() const; size_t count() const; size_t size() const { return N; } // 1 rtk bitek szma // bitek szma

bool operator==(const bitset& s) const; bool operator!=(const bitset& s) const; bool test(size_t pos) const; bool any() const; bool none() const; // igaz, ha b[pos] rtke 1 // igaz, ha brmelyik bit rtke 1 // igaz, ha egyik bit rtke sem 1

};

A to_ulong() s a to_string() fggvny a megfelel konstruktor fordtott (inverz) mvelete. A flrerthet talaktsok elkerlse rdekben a szoksos konverzis opertorok helyett a fenti mveletek llnak rendelkezsnkre. Ha a bitset objektumnak olyan bitjei is rtket tartalmaznak, melyek nem brzolhatk unsigned long int formban, a to_ulong() fggvny overflow_error kivtelt vlt ki. A to_string() mvelet a megfelel tpus karakterlncot lltja el, amely '0' s '1' karakterekbl pl fel. A basic_string a karakterlncok brzolshoz hasznlt sablon (20. fejezet).

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

657

A to_string() fggvnyt hasznlhatjuk egy int binris alakjnak kiratshoz is:


void binary(int i) { bitset<8*sizeof(int)> b = i; // 8 bites bjtot tteleznk fel (lsd mg 22.2) cout << b.template to_string< char,char_traits<char>,allocator<char> >() << '\n'; }

Sajnos egy minstett sablon tag meghvsa igen bonyolult s ritkn hasznlt utastsformt kvetel (C.13.6). A tagfggvnyeken kvl a bitset lehetv teszi a logikai & (s), a | (vagy), a ^ (kizr vagy), valamint a szoksos ki- s bemeneti opertorok hasznlatt is:
template<size_t N> bitset<N> std::operator&(const bitset<N>&, const bitset<N>&); template<size_t N> bitset<N> std::operator|(const bitset<N>&, const bitset<N>&); template<size_t N> bitset<N> std::operator^(const bitset<N>&, const bitset<N>&); template <class charT, class Tr, size_t N> basic_istream<charT,Tr>& std::operator>>(basic_istream<charT,Tr>&, bitset<N>&); template <class charT, class Tr, size_t N> basic_ostream<charT,Tr>& std::operator<<(basic_ostream<charT,Tr>&, const bitset<N>&);

Teht egy bitset objektumot kirathatunk anlkl is, hogy elbb karakterlncc alaktannk:
void binary(int i) { bitset<8*sizeof(int)> b = i; // 8 bites bjtot tteleznk fel (lsd mg 22.2) cout << b << '\n'; }

Ez a programrszlet nullk s egyesek formjban rja ki a biteket s a legnagyobb helyirtk bit lesz a bal oldalon.

17.5.4. Beptett tmbk


A beptett tmbk tmogatjk az indexelst s a szoksos mutatk formjban a kzvetlen elrs bejrk hasznlatt is (2.7.2). A tmb azonban nem tudja a sajt mrett, gy a programoznak kell azt nyilvntartania. A tmbk ltalban nem biztostjk a szabvnyos tagmveleteket s -tpusokat. Nha nagyon hasznos az a lehetsg, hogy egy beptett tmbt elrejthetnk a szabvnyos trolk knyelmes jellsrendszere mg, gy, hogy kzben megtartjuk alacsonyszint termszetnek elnyeit:

Forrs: http://www.doksi.hu

658

A standard knyvtr

template<class T, int max> struct c_array { typedef T value_type; typedef T* iterator; typedef const T* const_iterator; typedef T& reference; typedef const T& const_reference; T v[max]; operator T*() { return v; } reference operator[ ](size_t i) { return v[i]; } const_reference operator[ ](ptrdiff_t i) const { return v[i]; } iterator begin() { return v; } const_iterator begin() const { return v; } iterator end() { return v+max; } const_iterator end() const { return v+max; } }; ptrdiff_t size() const { return max; }

A tmbkkel val kompatibilits miatt az eljeles ptrdiff_t tpust (16.2.2) hasznlom indexknt az eljel nlkli size_t helyett. A c_array sablon nem rsze a standard knyvtrnak. Mindssze egyszer pldaknt szerepel itt arra, hogy egy idegen trolt hogyan igazthatunk a szabvnyos trolk keretrendszerhez. Ez az osztly olyan szabvnyos algoritmusokhoz (18. fejezet) hasznlhat, melyeknek a begin(), end() stb. mveletekre van szksgk. A verembe anlkl helyezhet, hogy kzvetve dinamikus memrit hasznlnnk, s ezenkvl tadhat olyan C stlus fggvnyeknek is, melyek mutatt vrnak paramterknt:
void f(int* p, int sz); void g() { c_array<int,10> a; f(a,a.size()); c_array<int,10>::iterator p = find(a.begin(),a.end(),777); } // ... // C stlus hasznlat // C++/STL stlus // hasznlat // C stlus

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

659

17.6. j trolk ltrehozsa


A szabvnyos trolk olyan keretrendszert biztostanak, melyet a programoz szabadon bvthet. Az albbiakban azt mutatom be, hogyan kszthetnk olyan trolkat, melyek felcserlhetk a szabvnyos trolkkal, ha indokolt. A bemutatott plda a gyakorlatban is mkdik, de nem optimlis. A felletet gy vlasztottam meg, hogy az nagyon kzel lljon a hash_map ltez, szles krben elrhet, magas szint megvalstsaihoz. Az itt bemutatott hash_map arra hasznlhat, hogy megtanuljuk az ltalnos alapelveket. Komoly munkhoz hasznljunk inkbb a tmogatott hash_map tpust.

17.6.1. A hash_map
A map olyan asszociatv trol, melynek szinte brmilyen tpus eleme lehet, hiszen az egyetlen kvetelmny egy kisebb mint mvelet az elemek sszehasonltshoz (17.4.1.5). Ha azonban tbbet tudunk a kulcs tpusrl, ltalban lervidthetjk az elemek megtallshoz szksges idt, azzal, hogy hast fggvnyt (hash function) ksztnk s a trolt hast tblaknt (hash table) valstjuk meg. A hast fggvny egy olyan eljrs, amely egy rtkhez gyorsan hozz tud rendelni egy indexet, mgpedig gy, hogy kt klnbz rtk ritkn kapja ugyanazt. A hast tbla ezutn lnyegben gy mkdik, hogy az rtket a kiszmtott indexre helyezi, ha az mg res, ellenkez esetben az index kzelbe. Ha egy elem a hozz rendelt indexen tallhat, akkor annak megtallsa nagyon gyors lehet, de az index kzelben elhelyezett elem keresse sem tl lass, ha az elemek egyenlsgnek vizsglata megfelelen gyors. Ezrt aztn nem ritka, hogy olyan nagyobb trolk esetben, ahol a keress a legjelentsebb mvelet, a hash_map akr 510-szer gyorsabban vgzi el ezt a feladatot, mint a map. Ugyanakkor az is igaz, hogy a hash_map egy rosszul megvlasztott hast fggvnnyel mg a map-nl is sokkal lassabb lehet. Hast tblt sokflekppen kszthetnk. A hash_table felletnek csak ott kell klnbznie a szabvnyos trolk fellettl, ahol a hasts hatkonysga ezt megkveteli. A legalapvetbb klnbsg a map s a hash_map kztt, hogy a map a < mvelettel hasonltja ssze az elemeit, mg a hash_map az == mveletet hasznlja s szksge van egy hast fggvnyre is. Teht ha a hash_map objektumot nem alaprtelmezett belltsokkal akarjuk ltrehozni, akkor a paramterek jelentsen eltrnek a map trolnl hasznltaktl:
map<string,int> m1; map<string,int,Nocase> m2; // karakterlncok sszehasonltsa < hasznlatval // karakterlncok sszehasonltsa Nocase() // hasznlatval (17.1.4.1)

Forrs: http://www.doksi.hu

660

A standard knyvtr

hash_map<string,int> hm1; hash_map<string,int,hfct> hm2; hash_map<string,int,hfct,eql> hm3;

// hasts Hash<string>() hasznlatval (17.6.2.3), // sszehasonlts == hasznlatval // hasts hfct() hasznlatval, sszehasonlts == // hasznlatval // hasts hfct() hasznlatval, sszehasonlts eql // hasznlatval

A hastsos keresst hasznl trolt egy vagy tbb tblzat segtsgvel hozhatjuk ltre. Az elemek trolsn kvl a trolnak nyilvn kell tartania azt is, milyen rtket milyen hastott rtkhez (az elzekben index) rendelt hozz. Erre szolgl a hast tbla. A hast tblk teljestmnye ltalban jelentsen romlik, ha tlsgosan teltett vlnak, teht mondjuk 75 szzalkig megteltek. Ezrt az albbiakban lert hash_map automatikusan nveli mrett, ha tl teltett vlik. Az tmretezs azonban rendkvl kltsges mvelet lehet, gy mindenkppen lehetv kell tennnk egy kezdeti mret megadst. Ezek alapjn a hash_map els vltozata a kvetkez lehet:
template<class Key, class T, class H = Hash<Key>, class EQ = equal_to<Key>, class A = allocator< pair<const Key,T> > > class hash_map { // mint a map, kivve a kvetkezket: typedef H Hasher; typedef EQ key_equal; hash_map(const T& dv =T(), size_type n =101, const H& hf =H(), const EQ& =EQ()); template<class In> hash_map(In first, In last, const T& dv =T(), size_type n =101, const H& hf =H(), const EQ& =EQ());

};

Ez lnyegben megegyezik a map felletvel (17.4.1.4), csak a < mveletet az == opertorral helyettestettk s megadtunk egy hast fggvnyt is. A knyvben eddig hasznlt map objektumok (3.7.4, 6.1, 17.4.1) knnyedn helyettesthetk a hash_map szerkezettel. Nem kell mst tennnk, csak a map nevet t kell rnunk hash_mapre. A map cserjt hash_map-re ltalban leegyszersthetjk egy typedef utastssal:
typedef hash_map<string,record> Map; Map dictionary;

A typedef arra is hasznlhat, hogy a sztr (dictionary) tnyleges tpust elrejtsk a felhasznlk ell.

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

661

Br a meghatrozs nem egszen pontos, a map s a hash_map kztti ellenttet tekinthetjk egyszeren tr-id ellenttnek. Ha a hatkonysg nem fontos, nem rdemes idt vesztegetni a kzttk val vlasztsra: mindkett jl hasznlhat. Nagy s nehzkesen hasznlhat tblk esetben a hash_map jelents elnye a sebessg, s ezt rdemes hasznlnunk, hacsak nem fontos a kis trigny. Mg ha takarkoskodnunk is kell a memrival, akkor is rdemes megvizsglnunk nhny egyb lehetsget a trigny cskkentsre, mieltt az egyszer map szerkezetet vlasztjuk. Az aktulis mret kiszmtsa nagyon fontos ahhoz, hogy ne egy alapjaiban rossz kdot prbljunk optimalizlni. A hatkony hasts megvalstsnak legfontosabb rsze a megfelel hast fggvny megvlasztsa. Ha nem tallunk j hast fggvnyt, akkor a map knnyen tlszrnyalhatja a hash_map hatkonysgt. A C stlus karakterlncokra vagy egsz szmokra pl hasts ltalban elg hatkony tud lenni, de mindig rdemes arra gondolnunk, hogy egy hast fggvny hatkonysga nagymrtkben fgg az aktulis rtkektl, melyeket hastanunk kell (17.8[35]). A hash_map trolt kell hasznlnunk akkor, ha a kulcshoz nem adhatunk meg < mveletet vagy az nem felel meg elvrsainknak. Megfordtva: a hast fggvny nem hatroz meg az elemek kztt olyan sorrendet, mint amilyet a < opertor, gy a map osztlyra van szksgnk, ha az elemek sorrendje fontos. A map osztlyhoz hasonlan a hash_map is biztost egy find() fggvnyt, amely megllaptja, hogy egy kulcs szerepel-e mr a trolban.

17.6.2. brzols s ltrehozs


A hash_map sokflekppen megvalsthat. Itt egy olyan formt mutatok be, amely viszonylag gyors, de legfontosabb mveletei elg egyszerek. Ezek a mveletek a konstruktorok, a keress (a [ ] opertor), az tmretezs s az egy elem trlst vgz fggvny (erase()). A hast tbla itt bemutatott egyszer megvalstsa egy olyan vektort hasznl, amely a bejegyzsekre hivatkoz mutatkat trolja. Minden bejegyzsben (Entry) szerepel egy kulcs (key), egy rtk (value) , egy mutat a kvetkez ugyanilyen hastkddal rendelkez Entry bejegyzsre (ha van ilyen), illetve egy trltsget jelz (erased) bit:

kulcs ... kulcs

rtk

trlt

kvetkez

rtk

trlt

kvetkez

Forrs: http://www.doksi.hu

662

A standard knyvtr

Ez deklarci formjban a kvetkezkppen nz ki.


template<class Key, class T, class H = Hash<Key>, class EQ = equal_to<Key>, class A = allocator< pair<const Key,T> > > class hash_map { // ... private: // brzols struct Entry { key_type key; mapped_type val; bool erased; Entry* next; // hash-tlcsorduls esetre Entry(key_type k, mapped_type v, Entry* n) : key(k), val(v), next(n), erased(false) { } }; vector<Entry> v; // az aktulis bejegyzsek vector<Entry*> b; // a hast tbla: mutat v-be }; // ...

Vizsgljuk meg az erased bit szerept. Az ugyanolyan hastkddal rendelkez elemek kezelsnek mdja miatt nagyon nehz lenne egy bizonyos elemet trlni. Ezrt a tnyleges trls helyett az erase() meghvsakor csak az erased bitet jelljk be, s az elemet mindaddig figyelmen kvl hagyjuk, amg a tblt t nem mretezzk. A f adatszerkezet mellett a hash_map osztlynak egyb adatokra is szksge van. Termszetesen minden konstruktornak az sszes adattagot be kell lltania:
template<class Key, class T, class H = Hash<Key>, class EQ = equal_to<Key>, class A = allocator< pair<const Key,T> > > class hash_map { // ... hash_map(const T& dv =T(), size_type n =101, const H& h =H(), const EQ& e =EQ()) : default_value(dv), b(n), no_of_erased(0), hash(h), eq(e) { set_load(); // alaprtelmezs v.reserve(max_load*b.size()); // hely biztostsa a nvekedshez } void set_load(float m = 0.7, float g = 1.6) { max_load = m; grow = g; } // ...

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

663

private: float max_load; float grow; size_type no_of_erased; Hasher hash; key_equal eq; }; const T default_value;

// v.size()<=b.size()*max_load megtartsa // ha szksges, resize(bucket_count()*grow) // v azon bejegyzseinek szma, amelyeket trlt elem foglal el // hast fggvny // egyenlsg // a [ ] ltal hasznlt alaprtelmezett rtk

A szabvnyos asszociatv trolk megkvetelik, hogy a hozzrendelt rtk tpusa alaprtelmezett rtkkel rendelkezzen (17.4.1.7). Ez a kvetelmny valjban nem elengedhetetlen s bizonyos esetekben nagyon knyelmetlen is lehet. Ezrt az alaprtelmezett rtket paramterknt vesszk t, ami lehetv teszi a kvetkez sorok lerst:
hash_map<string,Number> phone_book1; // alaprtelmezs: Number() hash_map<string,Number> phone_book2(Number(411)); // alaprtelmezs: Number(411)

17.6.2.1. Keressek Vgl elrkeztnk a kritikus keressi fggvnyekhez:


template<class Key, class T, class H = Hash<Key>, class EQ = equal_to<Key>, class A = allocator< pair<const Key,T> > > class hash_map { // ... mapped_type& operator[ ](const key_type&); iterator find(const key_type&); const_iterator find(const key_type&) const; // ...

};

Az rtkek megtallshoz az operator[ ]() a hast fggvnyt hasznlja, mellyel kiszmtja a kulcshoz tartoz indexet a hast tblban. Ezutn vgignzi az adott index alatt tallhat bejegyzseket, amg meg nem tallja a kvnt kulcsot. Az gy megtallt Entry objektumban tallhat az a value rtk, amelyet kerestnk. Ha nem talltuk meg a kulcsot, akkor alaprtelmezett rtkkel felvesznk egy j elemet:

Forrs: http://www.doksi.hu

664

A standard knyvtr

template<class Key, class T, class H = Hash<Key>, class EQ = equal_to<Key>, class A = allocator< pair<const Key,T> > > hash_map<Key,T,H,EQ,A>::mapped_type& hash_map<Key,T,H,EQ,A>::operator[ ](const key_type& k) { size_type i = hash(k)%b.size(); // hasts // keress a hasts eredmnyekppen i-be kerlt // elemek kztt if (eq(k,p->key)) { // megvan if (p->erased) { // jbli beszrs p->erased = false; no_of_erased--; return p->val = default_value; } return p->val; } // ha nincs meg if (size_type(b.size()*max_load) <= v.size()) { //ha "teltett" resize(b.size()*grow); // nvekeds return operator[ ](k); // jrahasts } v.push_back(Entry(k,default_value,b[i])); b[i] = &v.back(); } return b[i]->val; // Entry hozzadsa // az j elemre mutat for(Entry* p = b[i]; p; p = p->next)

A map megoldstl eltren a hash_map nem a kisebb mint mveletbl szrmaztathat egyenlsgvizsglatot (17.1.4.1) hasznlja, hiszen az azonos hastkddal rendelkez elemeket vgignz ciklusban az eq() fggvny meghvsa pontosan ezt a feladatot ltja el. Ez a ciklus nagyon fontos a keress hatkonysga szempontjbl, a leggyakoribb kulcstpusok (pldul a string, vagy a C stlus karakterlncok) szempontjbl pedig egy felesleges sszehasonlts akr jelents teljestmnyromlst is eredmnyezhet. Az azonos hastkddal rendelkez elemek trolshoz hasznlhattuk volna a set<Entry> trolt is, de ha elg j hastfggvnynk (hash()) van s a hasttbla (b) mrete is megfelel, akkor ezen halmazok tbbsgben egyetlen elem lesz. Ezrt ezeket az elemeket egyszeren az Entry osztly next mezjvel kapcsoltam ssze (17.8[27]). Figyeljk meg, hogy a b az elemekre hivatkoz mutatkat trolja s az elemek valjban a v-be kerlnek. A push_back() ltalban megtehetn, hogy thelyezi az elemeket, s ezzel

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

665

a mutatk rvnytelenn vlnnak (16.3.5), most azonban a konstruktorok (17.6.2) s a resize() fggvny elre helyet foglalnak az elemek szmra a reserve() eljrssal, gy elkerljk a vratlan thelyezseket.

17.6.2.2. Trls s tmretezs A hastfggvnyes keress nagyon rossz hatsfokv vlhat, ha a tbla tlsgosan teltett. Az ilyen kellemetlensgek elkerlse rdekben az indexel opertor automatikusan tmretezheti a tblt. A set_load() (17.6.2) fggvny szablyozza, hogy ez az tmretezs mikor s hogyan menjen vgbe, nhny tovbbi fggvnnyel pedig lehetv tesszk a programoz szmra, hogy lekrdezze a hash_map llapott:
template<class Key, class T, class H = Hash<Key>, class EQ = equal_to<Key>, class A = allocator< pair<const Key,T> > > class hash_map { // ... void resize(size_type n); void erase(iterator position); // a hast tbla mretnek n-re lltsa // a mutatott elem trlse

size_type size() const { return v.size()-no_of_erased; } // az elemek szma size_type bucket_count() const { return b.size(); } Hasher hash_fun() const { return hash; } key_equal key_eq() const { return eq; } }; // ... // a hast tbla mrete // a hasznlt hast fggvny // a hasznlt egyenlsgvizsglat

A resize() fggvny rendkvl fontos, viszonylag egyszer, de nha rendkvl drga mvelet:
template<class Key, class T, class H = Hash<Key>, class EQ = equal_to<Key>, class A = allocator< pair<const Key,T> > > void hash_map<Key,T,H,EQ,A>::resize(size_type s) { size_type i = v.size(); while (no_of_erased) { // a "trlt" elemek tnyleges eltvoltsa if (v[--i].erased) { v.erase(&v[i]); --no_of_erased; } }

Forrs: http://www.doksi.hu

666

A standard knyvtr

if (s <= b.size()) return; b.resize(s); fill(b.begin(),b.end(),0); v.reserve(s*max_load);

// s-b.size() szm mutat hozzadsa // bejegyzsek trlse (18.6.6) // ha v-nek jbli memriafoglalsra van szksge, most // trtnjen meg if (no_of_erased) { // a "trlt" elemek tnyleges eltvoltsa for (size_type i = v.size()-1; 0<=i; i--) if (v[i].erased) { v.erase(&v[i]); if (--no_of_erased == 0) break; } } for (size_type i = 0; i<v.size(); i++) { size_type ii = hash(v[i].key)%b.size(); v[i].next = b[ii]; b[ii] = &v[i]; } // jrahasts // hasts // lncols

Ha szksg van r, a programoz maga is meghvhatja a resize() eljrst, gy biztosthatja, hogy az idvesztesg kiszmthat helyen kvetkezzen be. Tapasztalataim szerint bizonyos alkalmazsokban a resize() mvelet rendkvl fontos, de a hasttblk szempontjbl nem nlklzhetetlen. Egyes megvalstsi mdszerek egyltaln nem hasznljk. Mivel a tnyleges munka nagy rsze mshol trtnik (s csak akkor, amikor tmretezzk a hash_map trolt), az erase() fggvny nagyon egyszer:
template<class Key, class T, class H = Hash<Key>, class EQ = equal_to<Key>, class A = allocator< pair<const Key,T> > > void hash_map<Key,T,H,EQ,A>::erase(iterator p) // a mutatott elem trlse { if (p->erased == false) no_of_erased++; p->erased = true; }

17.6.2.3. Hasts A hash_map::operator[ ]() teljess ttelhez mg meg kell hatroznunk a hash() s az eq() fggvnyt. Bizonyos okokbl (amelyeket majd a 18.4 pontban rszleteznk) a hast fggvnyt rdemesebb egy fggvnyobjektum operator()() mveleteknt elksztennk:
template <class T> struct Hash : unary_function<T, size_t> { size_t operator()(const T& key) const; };

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

667

A j hast fggvny paramterben egy kulcsot kap s egy egsz rtket ad vissza, amely klnbz kulcsok esetben nagy valsznsggel klnbz. A j hastfggvny kivlasztsa nagyon nehz. Gyakran az vezet elfogadhat eredmnyhez, ha a kulcsot jell biteken s egy elre meghatrozott egsz rtken kizr vagy mveletet hajtunk vgre:
template <class T> size_t Hash<T>::operator()(const T& key) const { size_t res = 0; size_t len = sizeof(T); const char* p = reinterpret_cast<const char*>(&key); //objektumok elrse bjtok // sorozataknt while (len--) res = (res<<1)^*p++; return res; // a kulcsbrzols bjtjainak hasznlata

A reinterpret_cast (6.2.7) hasznlata jl jelzi, hogy valami csnya dolgot mveltnk, s ha tbbet tudunk a hastott rtkrl, akkor ennl hatkonyabb megoldst is tallhatunk. Pldul, ha az objektum mutatt tartalmaz, ha nagy objektumrl van sz, vagy ha az adattagok igaztsa miatt az objektum brzolsban felhasznlatlan terletek (lyukak, holes) vannak, mindenkppen jobb hastfggvnyt kszthetnk (17.8[29]). A C stlus karakterlncok mutatk (a karakterekre) s a string is tartalmaz mutatt. A specializcik ezekre az esetekre sorrendben a kvetkezk:
size_t Hash<char*>::operator()(const char* key) const { size_t res = 0; while (*key) res = (res<<1)^*key++; return res; // a karakterek egsz rtknek hasznlata

template <class C> size_t Hash< basic_string<C> >::operator()(const basic_string<C>& key) const { size_t res = 0; typedef basic_string<C>::const_iterator CI; CI p = key.begin(); CI end = key.end(); while (p!=end) res = (res<<1)^*p++; return res; // a karakterek egsz rtknek hasznlata

Forrs: http://www.doksi.hu

668

A standard knyvtr

A hash_map minden megvalstsban legalbb az egsz s a karakterlnc tpus kulcsokhoz szerepelnik kell hastfggvnynek. Komolyabb kulcsok esetben a programozt segthetjk megfelel specializcikkal is. A j hastfggvny kivlasztsban sokat segthet a megfelel mrszmokra tmaszkod ksrletezs. Megrzseinkre nagyon ritkn hagyatkozhatunk ezen a terleten. A hash_map akkor vlik teljess, ha bejrkat is definilunk s nhny tovbbi egyszer fggvnyt is ksztnk. Ez azonban maradjon meg feladatnak (17.8[34]).

17.6.3. Tovbbi hastott asszociatv trolk


Az egysgessg s a teljessg rdekben mindenkppen el kell ksztennk a hash_map testvreit is: a hash_set, a hash_multimap s a hash_multiset trolt. Ezek ltrehozsa a hash_map, a map, a multimap, a set s a multiset alapjn egyszer, gy ezeket is meghagyom feladatnak 17.8[34]. Ezeknek a hastott asszociatv trolknak komoly irodalma van s ksz kereskedelmi vltozatok is elrhetk bellk. Tnyleges programokban ezek a vltozatok jobban hasznlhatk, mint a hzilag sszeeszkbltak (kztk az ltalam bemutatott is).

17.7. Tancsok
[1] Ha trolra van szksgnk, ltalban a vector osztlyt hasznljuk. 17.1. [2] Ha gyakran hasznlunk egy mveletet, rdemes ismerni annak kltsgeit (bonyolultsg, nagy O mrtk) 17.1.2. [3] A trol fellete, megvalstsa s brzolsa kln-kln fogalom. Ne keverjk ssze ket. 17.1.3. [4] Keresst vagy rendezst brmilyen szempont szerint megvalsthatunk. 17.1.4.1. [5] Ne hasznljunk kulcsknt C stlus karakterlncokat, vagy biztostsunk megfelel sszehasonltsi mveletet. 17.1.4.1. [6] Az sszehasonltsi szemponttal elemek egyenrtksgt hatrozhatjuk meg, ami eltrhet attl, hogy a kulcsok teljesen egyenlk-e. 17.1.4.1. [7] Lehetleg hasznljuk a sorozat vgt mdost fggvnyeket (back-mveletek), ha elemek beszrsra vagy trlsre van szksgnk. 17.1.4.1.

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

669

[8] Ha sok beszrsra, illetve trlsre van szksg a sorozat elejn vagy belsejben, hasznljuk a list trolt. [9] Ha az elemeket legtbbszr kulcs alapjn rjk el, hasznljuk a map vagy a multimap szerkezetet. 17.4.1. [10] A lehet legnagyobb rugalmassgot gy rhetjk el, ha a lehet legkevesebb mveletet hasznljuk. 17.1.1. [11] Ha fontos az elemek sorrendje, a hash_map helyett hasznljuk a map osztlyt. 17.6.1. [12] Ha a keress sebessge a legfontosabb, a hash_map hasznosabb, mint a map. 17.6.1. [13] Ha nem tudunk az elemekre kisebb mint mveletet megadni, a hash_map trolt hasznlhatjuk. 17.6.1. [14] Ha azt akarjuk ellenrizni, hogy egy kulcs megtallhat-e egy asszociatv trolban, hasznljuk a find() fggvnyt. 17.4.1.6. [15] Ha az adott kulccsal rendelkez sszes elemet meg akarjuk tallni, hasznljuk az equal_range()-et. 17.4.1.6. [16] Ha ugyanazzal a kulccsal tbb elem is szerepelhet, hasznljuk a multimap trolt. 17.4.2. [17] Ha csak a kulcsot kell nyilvntartanunk, hasznljuk a set vagy a multiset osztlyt. 17.4.3.

17.8. Gyakorlatok
Az itt szerepl gyakorlatok tbbsgnek megoldsa kiderl a standard knyvtr brmely vltozatnak forrskdjbl. Mieltt azonban megnznnk, hogy a knyvtr megalkoti hogyan kzeltettk meg az adott problmt, rdemes sajt megoldst ksztennk. Vgl nzzk t, hogy sajt rendszernkben milyen trolk s milyen mveletek llnak rendelkezsnkre. 1. (*2.5) rtsk meg az O( ) jellst (17.1.2). Vgezznk nhny mrst a szabvnyos trolk mveleteire s hatrozzuk meg a konstans szorzkat. 2. (*2) Sok telefonszm nem brzolhat egy long rtkkel. Ksztsnk egy phone_number tpust s egy osztlyt, amely meghatrozza az sszes olyan mveletet, melyeknek egy telefonszmokat trol trolban hasznt vehetjk. 3. (*2) rjunk programot, amely kirja egy fjl szavait bcsorrendben. Ksztsnk kt vltozatot: az egyikben a sz egyszeren reshely karakterekkel hatrolt karaktersorozat legyen, a msikban olyan betsorozat, melyet nem bet karakterek sorozata hatrol.

Forrs: http://www.doksi.hu

670

A standard knyvtr

4. (*2.5) rjunk egy egyszer Paszinsz krtyajtkot. 5. (*1.5) rjunk programot, amely eldnti, hogy egy sz palindrom-e (azaz brzolsa szimmetrikus-e, pldul: ada, otto, tat). rjuk eljrst, amely egy egsz szmrl dnti el ugyanezt, majd ksztsnk mondatokat vizsgl fggvnyt. ltalnostsunk. 6. (*1.5) Ksztsnk egy sort (queue) kt verem segtsgvel. 7. (*1.5) Ksztsnk egy vermet, amely majdnem olyan, mint a stack, csak nem msolja az elemeit s lehetv teszi bejrk hasznlatt. 8. (*3) Szmtgpnk minden bizonnyal tmogat valamilyen konkurens (prhuzamos vgrehajtsi) lehetsget: szlakat (thread), taszkokat (task) vagy folyamatokat (process). Dertsk ki, hogyan mkdik ez. A konkurens hozzfrst lehetv tev rendszer valamilyen mdot ad r, hogy megakadlyozzuk, hogy kt folyamat ugyanazt a memriaterletet egyszerre hasznlja. Sajt rendszernk zrolsi eljrsa alapjn ksztsnk egy osztlyt, amely a programoz szmra egyszeren elrhetv teszi ezt a lehetsget. 9. (*2.5) Olvassuk be dtumok egy sorozatt (pldul: Dec85, Dec50, Jan76), majd jelentsk meg azt gy, hogy a legksbbi idpont legyen az els a sorban. A dtum formtuma a kvetkez legyen: hnapnv hrom karakteren, majd vszm kt karakteren. Ttelezzk fel, hogy mindegyik dtum ugyanarra az vszzadra vonatkozik. 10. (*2.5) ltalnostsuk a dtumok bemeneti formtumt gy, hogy felismerje az albbi dtumformtumokat: Dec1985, 12/3/1990, (Dec,30,1950), 3/6/2001, stb. Mdostsuk a 17.8[9] feladatot gy, hogy mkdjn ezekre a formtumokra is. 11. (*1.5) Hasznljuk a bitset trolt nhny szm binris alakjnak kiratshoz. Pldul 0, 1, -1, 18, -18 s a legnagyobb pozitv int rtk. 12. (*1.5) A bitset segtsgvel brzoljuk, hogy egy osztly mely tanuli voltak jelen egy adott napon. Olvassuk be 12 nap bitset objektumt s llaptsuk meg, kik voltak jelen minden nap, s kik voltak legalbb 8 napot az iskolban. 13. (*1.5) Ksztsnk egy olyan mutatkbl ll listt, amely trli a mutatott objektumokat is, ha egy mutatt trlnk belle vagy ha az egsz listt megszntetjk. 14. (*1.5) rassuk ki rendezve egy adott stack objektum elemeit anlkl, hogy az eredeti vermet megvltoztatnnk. 15. (*2.5) Fejezzk be a hash_map (17.6.1) megvalstst. Ehhez meg kell rnunk a find() s az equal_range() fggvnyt, valamint mdot kell adnunk a ksz sablon ellenrzsre. Prbljuk ki a hash_map osztlyt legalbb egy olyan kulcstpusra, melyre az alaprtelmezett hastfggvny nem hasznlhat. 16. (*2.5) Ksztsnk el egy listt a szabvnyos list stlusban s teszteljk. 17. (*2) Bizonyos helyzetekben a list tlzott memria-felhasznlsa problmt jelent. Ksztsnk egy egyirny lncolt listt a szabvnyos trolk stlusban.

Forrs: http://www.doksi.hu

17. Szabvnyos trolk

671

18. (*2.5) Ksztsnk el egy listt, amely olyan, mint a szabvnyos list, csak tmogatja az indexelst is. Hasonltsunk ssze nhny listra az indexels kltsgt egy ugyanilyen mret vector indexelsi kltsgvel. 19. (*2) Ksztsnk egy sablon fggvnyt, amely kt trolt sszefsl. 20. (*1.5) llaptsuk meg, hogy egy C stlus karakterlnc palindrom-e. Vizsgljuk meg, hogy a karakterlnc (legalbb) els hrom szavbl ll sorozat palindrom-e. 21. (*2) Olvassuk be (name,value) (nv, rtk) prok egy sorozatt s ksztsnk egy rendezett listt (name, total, mean, median) (nv, sszesen, tlag, kzprtk) sorokbl. 22. (*2.5) Vizsgljuk meg, mekkora az ltalunk ksztett trolk trignye. 23. (*3.5) Gondolkozzunk el azon, milyen megvalstsi stratgit hasznlhatnnk egy olyan hash_map trolhoz, melynl a lehet legkisebb trigny a legfbb kvetelmny. Hogyan kszthetnnk olyan hash_map osztlyt, melynek keressi ideje minimlis? Nzzk t, mely mveleteket rdemes kihagyni a megvalstsbl az optimlishoz (felesleges memriafoglals, illetve idvesztesg nlkli) kzeli megolds elrshez. Segtsg: a hasttblknak igen kiterjedt irodalma van. 24. (*2) Dolgozzunk ki olyan elvet a hash_map tlcsordulsnak (klnbz rtkek ugyanazon hastkdra kerlsnek) kezelsre, amely az equal_range() elksztst egyszerv teszi. 25. (*2.5) Becsljk meg a hash_map trignyt, majd mrjk is le azt. Hasonltsuk ssze a becslt s a szmtott rtkeket. Hasonltsuk ssze az ltalunk megvalstott hash_map s map trignyt. 26. (*2.5) Vizsgljuk meg sajt hash_map osztlyunkat abbl a szempontbl, hogy melyik mvelettel telik el a legtbb id. Tegyk meg ugyanezt sajt map trolnkra, illetve a hash_map kereskedelmi vltozataira is. 27. (*2.5) Ksztsk el a hash_map trolt egy vector<map<K,V>*> szerkezet segtsgvel. Minden map trolja az sszes olyan kulcsot, melyek hastkdja megegyezik. 28. (*3) Ksztsk el a hash_map osztlyt Splay fk segtsgvel. (Lsd: D. Sleator, R. E. Tarjan:Self-Adjusting Binary Search Trees, JACM, 32. ktet, 1985) 29. (*2) Adott egy struktra, amely egy karakterlnc-szer egyedet r le:
struct St { int size; char type_indicator; char* buf; St(const char* p); };

// mretre mutat // memriafoglals s a buf feltltse

Forrs: http://www.doksi.hu

672

A standard knyvtr

Ksztsnk 1000 St objektumot s hasznljuk ezeket egy hash_map kulcsaknt. Ksztsnk programot, mellyel tesztelni lehet a hash_map hatkonysgt. rjunk egy hastfggvnyt (Hash, 17.6.2.3) kifejezetten az St tpus kulcsok kezelshez. 30. (*2) Adjunk legalbb ngyfle megoldst a trlsre kijellt (erased) elemek eltvoltsra a hash_map trolbl. Ciklus helyett hasznljuk a standard knyvtr algoritmusait. (3.8, 18. fejezet) 31. (*3) Ksztsnk olyan hash_map trolt, amely azonnal trli az elemeket. 32. (*2) A 17.6.2.3 pontban bemutatott hastfggvny nem mindig hasznlja a kulcs teljes brzolst. Mikor hagy figyelmen kvl rszleteket ez a megolds? rjunk olyan hastfggvnyt, amely mindig a kulcs teljes brzolst hasznlja. Adjunk r pldt, mikor lehet jogos a kulcs egy rsznek elfelejtse s rjunk olyan hastfggvnyt, amely a kulcsnak csak azt a rszt veszi figyelembe, amelyet fontosnak nyilvntunk. 33. (*2.5) A hastfggvnyek forrskdja meglehetsen hasonl: egy ciklus sorra veszi az adatokat, majd ellltja a hastrtket. Ksztsnk olyan Hash (17.6.2.3) fggvnyt, amely az adatokat egy, a felhasznl ltal megadott fggvny ismtelt meghvsval gyjti ssze. Pldul:
size_t res = 0; while (size_t v = hash(key)) res = (res<<3)^v;

Itt a felhasznl a hash(K) fggvnyt minden olyan K tpusra megadhatja, amely alapjn hastani akar. 34. (*3) A hash_map nhny megvalstsbl kiindulva ksztsk el a hash_multimap, a hash_set s a hash_multiset trolt. 35. (*2.5) Ksztsnk olyan hastfggvnyt, amely egyenletes eloszls int rtkeket kpez le egy krlbell 1024 mret hasttblra. Ezen hastfggvny ismeretben adjunk meg 1024 olyan kulcsrtket, amelyet a fggvny ugyanarra a hastkdra kpez le.

Forrs: http://www.doksi.hu

18
Algoritmusok s fggvnyobjektumok
A forma szabadd tesz. (a mrnkk kzmondsa) Bevezet A szabvnyos algoritmusok ttekintse Sorozatok Fggvnyobjektumok Prediktumok Aritmetikai objektumok Lektk Tagfggvny-objektumok for_each Elemek keresse count Sorozatok sszehasonltsa Keress Msols transform Elemek lecserlse s eltvoltsa Sorozatok feltltse trendezs swap Rendezett sorozatok binary_search merge Halmazmveletek min s max Kupac Permutcik C stlus algoritmusok Tancsok Gyakorlatok

18.1. Bevezet
Egy trol nmagban nem tlsgosan rdekes dolog. Ahhoz, hogy tnyleg hasznoss vljon, szmos alapvet mveletre is szksg van, melyekkel pldul lekrdezhetjk a trol mrett, bejrhatjuk, msolhatjuk, rendezhetjk, vagy elemeket kereshetnk benne. Szerencsre a standard knyvtr biztostja mindazokat a szolgltatsokat, melyekre a programozknak szksgk van a trolk hasznlathoz.

Forrs: http://www.doksi.hu

674

A standard knyvtr

Ebben a fejezetben a szabvnyos algoritmusokat foglaljuk ssze s nhny pldt mutatunk be hasznlatukra. Kiemeljk azokat a legfontosabb elveket s mdszereket, melyeket ismernnk kell az algoritmusok lehetsgeinek a C++-ban val kiaknzshoz. Nhny alapvet algoritmust rszletesen is megvizsglunk. Azokat az eljrsokat, melyek segtsgvel a programozk sajt ignyeikhez alakthatjk a szabvnyos algoritmusok viselkedst, a fggvnyobjektumok biztostjk. Ezek adjk meg azokat az alapvet informcikat is, melyekre a felhasznlk adatainak kezelshez szksg van. ppen ezrt nagy hangslyt helyeznk arra, hogy bemutassuk a fggvnyobjektumok ltrehozsnak s hasznlatnak mdszereit.

18.2. A standard knyvtr algoritmusainak ttekintse


Els pillantsra gy tnhet, hogy a standard knyvtr algoritmusainak szma szinte vgtelen, pedig mindssze 60 darab van bellk. Tallkoztam mr olyan osztllyal, melynek nmagban tbb tagfggvnye volt. Radsul nagyon sok algoritmus ugyanazt az ltalnos viselkedsformt, illetve felletstlust mutatja, s ez nagymrtkben leegyszersti megrtsket. Ugyangy, mint a programnyelvi lehetsgek esetben, a programoznak itt is csak azokat az elemeket kell hasznlnia, amelyekre ppen szksge van s amelyek mkdst ismeri. Semmilyen elnyt nem jelent, ha minden aprsghoz szabvnyos algoritmust keresnk, s azrt sem kapunk jutalmat, ha az algoritmusokat rendkvl okosan, de ttekinthetetlenl alkalmazzuk. Ne felejtsk el, hogy a programkd lersnak elsdleges clja, hogy a ksbbi olvask szmra a program mkdse rthet legyen (A ksbbi olvask valsznleg mi magunk lesznk nhny v mlva.) Msrszt, ha egy trol elemeivel valamilyen feladatot kell elvgeznnk, gondoljuk vgig, hogy a mvelet nem fogalmazhat-e meg a standard knyvtr algoritmusainak stlusban. Az is elkpzelhet, hogy az algoritmus mr meg is van, csak olyan ltalnos formban, hogy els rnzsre r sem ismernk. Ha megszokjuk az ltalnostott (generikus) algoritmusok vilgt, nagyon sok felesleges munktl kmlhetjk meg magunkat.

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

675

Mindegyik algoritmus egy sablon fggvny (template function) (13.3) formjban jelenik meg vagy sablon fggvnyek egy csoportjaknt. Ez a megolds lehetv teszi, hogy az algoritmusok sokfle elemsorozaton legyenek kpesek mkdni s termszetesen az elemek tpusa is mdosthat legyen. Azok az algoritmusok, melyek eredmnykppen egy bejrt (iterator) (19.1) adnak vissza, ltalban a bemeneti sorozat vgt hasznljk a sikertelen vgrehajts jelzsre:
void f(list<string>& ls) { list<string>::const_iterator p = find(ls.begin(),ls.end(),"Frici"); if (p == ls.end()) { // "Frici" nem tallhat } else { // p "Frici"-re mutat }

Az algoritmusok nem vgeznek tartomnyellenrzst a be- vagy kimenetkn. A rossz tartomnybl ered hibkat ms mdszerekkel kell elkerlnnk (18.3.1, 19.3) Ha az algoritmus egy bejrt ad vissza, annak tpusa ugyanolyan lesz, mint a bemeneti sorozat valamelyik bejrjnak. Teht pldul az algoritmus paramtere hatrozza meg, hogy a visszatrsi rtk const_iterator vagy nem konstans iterator lesz-e:
void f(list<int>& li, const list<string>& ls) { list<int>::iterator p = find(li.begin(),li.end(),42); list<string>::const_iterator q = find(ls.begin(),ls.end(),"Ring"); }

A standard knyvtr algoritmusai kztt megtalljuk a trolk leggyakoribb ltalnos mveleteit, pldul a bejrsokat, keresseket, rendezseket, illetve az elemek beszrst s trlst is. A szabvnyos algoritmusok kivtel nlkl az std nvtrben tallhatk s az <algorithm> fejllomny deklarlja ket. rdekes, hogy az igazn ltalnos algoritmusok nagy rsze annyira egyszer, hogy ltalban helyben kifejtett (inline) sablon fggvnyek valstjk meg azokat. Ezrt az algoritmusok ciklusait a hatkony, fggvnyen belli optimalizcis eljrsok jelentsen javthatjk. A szabvnyos fggvnyobjektumok is az std nvtrben tallhatk, de ezek deklarcijt a <functional> fejllomnyban rhetjk el. A fggvnyobjektumok felptse is olyan, hogy knnyen hasznlhatk helyben kifejtett fggvnyknt.

Forrs: http://www.doksi.hu

676

A standard knyvtr

A nem mdost sorozatmveletek arra hasznlhatk, hogy adatokat nyerjnk ki egy sorozatbl vagy bizonyos elemek helyt meghatrozzuk bennk:

Nem mdost sorozatmveletek (18.5) <algorithm> for_each() find() find_if() find_first_of() adjacent_find() count() count_if() mismatch() equal() search() find_end() search_n() Mvelet vgrehajtsa egy sorozat sszes elemre. Egy rtk els elfordulsnak megkeresse egy sorozatban. Az els olyan elem megkeresse egy sorozatban, amire egy llts teljesl. Egy sorozat egy elemnek megkeresse egy msik sorozatban. Kt szomszdos rtk keresse. Egy rtk elfordulsainak szma egy sorozatban. Azon elemek szma egy sorozatban, melyre teljesl egy llts. Az els olyan elemek keresse, ahol kt sorozat klnbzik. Igazat ad vissza, ha kt sorozat elemei pronknt megegyeznek. Egy sorozat els elfordulst keresi meg rszsorozatknt. Egy sorozat rszsorozatknt val utols elfordulst keresi meg. Egy rtk n-edik elfordulst keresi meg egy sorozatban.

A legtbb algoritmus lehetv teszi, hogy a programoz hatrozza meg azt a feladatot, amelyet minden elemen, illetve elempron el akar vgezni. Ezltal az algoritmusok sokkal ltalnosabbak s hasznosabbak, mint azt els rnzsre gondolhatnnk. A programoz hatrozhatja meg, mikor tekintnk kt elemet azonosnak s mikor klnbznek (18.4.2), s a leggyakrabban vgrehajtott, leghasznosabb mveletet alaprtelmezettknt is kijellheti. A sorozatmdost mveletek kztt igen kevs hasonlsgot tallhatunk azon kvl, hogy megvltoztatjk a sorozat egyes elemeinek rtkt:

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

677

Sorozatmdost mveletek (18.6) <algorithm> transform() copy() copy_backward() swap() iter_swap() swap_ranges() replace() replace_if() replace_copy() replace_copy_if() fill() fill_n() generate() generate_n() remove() remove_if() remove_copy() remove_copy_if() unique() unique_copy() reverse() reverse_copy() rotate() rotate_copy() random_shuffle() Mvelet vgrehajtsa a sorozat minden elemn. Sorozat msolsa az els elemtl kezdve. Sorozat msolsa az utols elemtl kezdve. Kt elem felcserlse. Bejrk ltal kijellt kt elem felcserlse. Kt sorozat elemeinek felcserlse. Adott rtk elemek helyettestse. lltst kielgt elemek helyettestse. Sorozat msolsa adott rtk elemek helyettestsvel. Sorozat msolsa lltst kielgt elemek helyettestsvel. Az sszes elem helyettestse egy adott rtkre. Az els n elem helyettestse egy adott rtkkel. Az sszes elem helyettestse egy mvelettel ellltott rtkre. Az els n elem helyettestse egy mvelettel ellltott rtkre. Adott rtkkel rendelkez elemek trlse. lltst kielgt elemek trlse. Sorozat msolsa adott rtk elemek trlsvel. Sorozat msolsa lltst kielgt elemek trlsvel. Szomszdos egyenrtk elemek trlse. Sorozat msolsa szomszdos egyenrtk elemek trlsvel. Az elemek sorrendjnek megfordtsa. Elemek msolsa fordtott sorrendben. Elemek krbeforgatsa. Sorozat msolsa az elemek krbeforgatsval. Elemek trendezse egyenletes eloszls szerint.

Minden j rendszer magn viseli megalkotjnak rdekldsi krt, illetve szemlyes jellemvonsait. A standard knyvtr troli s algoritmusai tkletesen tkrzik a klasszikus adatszerkezetek mgtti elveket s az algoritmusok tervezsi szempontjait. A standard knyvtr nem csak a trolk s algoritmusok legalapvetbb fajtit biztostja, melyekre minden programoznak szksge van, hanem olyan eszkzket is, melyekkel ezek az algoritmusok megvalsthatk, s lehetsget ad a knyvtr bvtsre is.

Forrs: http://www.doksi.hu

678

A standard knyvtr

A hangsly most nem igazn azon van, hogy ezek az algoritmusok hogyan valsthatk meg, st a legegyszerbb algoritmusoktl eltekintve nem is azok hasznlatn. Ha az algoritmusok szerkezetrl s elksztsi mdjairl tbbet szeretnnk megtudni, ms knyveket kell fellapoznunk (pldul [Knuth, 1968] vagy [Tarjan, 1983]). Itt azzal foglalkozunk, mely algoritmusok llnak rendelkezsnkre a standard knyvtrban s ezek hogyan jelennek meg a C++ nyelvben. Ez a nzpont lehetv teszi, hogy amennyiben tisztban vagyunk az algoritmusokkal hatkonyan hasznljuk a standard knyvtrat, illetve olyan szellemben fejlesszk azt tovbb, ahogy megszletett. A standard knyvtr sok-sok olyan eljrst knl, melyekkel sorozatokat rendezhetnk, kereshetnk bennk, vagy ms, sorrenden alapul mveleteket vgezhetnk velk:

Rendezett sorozatok mveletei (18.7) <algorithm> sort() stable_sort() partial_sort() partial_sort_copy() nth_element() lower_bound() upper_bound() equal_range() binary_search() merge() inplace_merge() partition() stable_partition() tlagos hatkonysggal rendez. Az egyenrtk elemek sorrendjnek megtartsval rendez. Egy sorozat elejt rendezi. Msol a sorozat elejnek rendezsvel. Az n-edik elemet a megfelel helyre teszi. Egy rtk els elfordulst keresi meg. Egy rtk utols elfordulst keresi meg. Egy adott rtket tartalmaz rszsorozatot ad meg. Igazat ad vissza, ha a megadott rtk szerepel a sorozatban. Kt rendezett sorozatot fsl ssze. Kt egymst kvet rendezett rszsorozatot fsl ssze. Elemeket helyez el egy lltsnak (felttelnek) megfelelen. Elemeket helyez el egy lltsnak megfelelen, a relatv sorrend megtartsval.

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

679

Halmazmveletek (18.7.5) <algorithm> includes() set_union() set_intersection() set_difference() set_symmetric_difference() Igazat ad vissza, ha egy sorozat rszsorozata egy msiknak. Rendezett unit llt el. Rendezett metszetet llt el. Azon elemek rendezett sorozatt lltja el, melyek az els sorozatban megtallhatk, de a msodikban nem. Azon elemek rendezett sorozatt lltja el, melyek csak az egyik sorozatban tallhatk meg.

A kupacmveletek (heap-mveletek) olyan llapotban tartjk a sorozatot, hogy az knnyen rendezhet legyen, amikor arra szksg lesz:

Kupacmveletek (18.8) <algorithm> make_heap() push_heap() pop_heap() sort_heap() Egy sorozatot felkszt kupacknt val hasznlatra. Elemet ad a kupachoz. Elemet trl a kupacbl. Rendezi a kupacot.

A knyvtr biztost nhny olyan eljrst is, melyek sszehasonltssal kivlasztott elemeket adnak meg:

Minimum s maximum (18.9) <algorithm> min() max() min_element() max_element() lexicographical_compare() Kt rtk kzl a kisebb. Kt rtk kzl a nagyobb. A legkisebb rtk egy sorozatban. A legnagyobb rtk egy sorozatban. Kt sorozat kzl az bcsorrend szerint korbbi.

Forrs: http://www.doksi.hu

680

A standard knyvtr

Vgl a knyvtr lehetv teszi azt is, hogy ellltsuk egy sorozat permutciit (az elemek sszes lehetsges sorrendjt):

Permutcik (18.10) <algorithm> next_permutation() prev_permutation() Az bcsorrend szerinti rendezs alapjn kvetkez permutci. Az bcsorrend szerinti rendezs alapjn elz permutci.

Ezeken kvl, a <numeric> (22.6) fejllomnyban nhny ltalnos matematikai algoritmust is tallhatunk. Az algoritmusok lersban a sablonparamterek neve nagyon fontos. Az In, Out, For, Bi s Ran elnevezsek sorrendben bemeneti bejrt, kimeneti bejrt, elre halad bejrt, ktirny bejrt, illetve kzvetlen elrs (vletlen elrs) bejrt jelentenek (19.2.1). A Pred egyparamter, a BinPred ktparamter prediktumot (lltst, logikai rtk fggvnyt, 18.4.2) hatroz meg, mg a Cmp sszehasonlt fggvnyre utal. Az Op egyoperandos mveletet, a BinOp ktoperandost vr. A hagyomnyok szerint sokkal hosszabb neveket kellett volna hasznlnom a sablonparamterek megnevezsre, de gy vettem szre, hogy ha mr egy kicsit is ismerjk a standard knyvtrat, a hossz nevek inkbb csak rontjk az olvashatsgot. A kzvetlen elrs bejrk hasznlhatk ktirny bejrknt is, a ktirny bejrk elre halad bejrknt, az elre halad bejrk pedig akr bemeneti, akr kimeneti bejrknt (19.2.1). Ha a sablonnak olyan tpust adunk t, amely nem biztostja a szksges mveleteket, a sablon pldnyostsakor hibazenetet kapunk (C.13.7). Ha olyan tpust hasznlunk, amelyben megvannak a szksges mveletek, de jelentsk (szerepk) nem a megfelel, akkor kiszmthatatlan futsi idej viselkedsre kell felkszlnnk (17.1.4).

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

681

18.3. Sorozatok s trolk


ltalnos szably, hogy mindennek a leggyakoribb felhasznlsi mdja legyen a legrvidebb, a legegyszerbb s a legbiztonsgosabb. A standard knyvtr az ltalnossg rdekben itt-ott megsrti ezt a szablyt, de egy szabvnyos knyvtr esetben az ltalnossg mindennl fontosabb. A 42 els kt elfordulst egy sorozatban pldul az albbi programrszlettel kereshetjk meg
void f(list<int>& li) { list<int>::iterator p = find(li.begin(),li.end(),42); if (p != li.end()) { list<int>::iterator q = find(++p,li.end(),42); // ... } // ... }

// els elforduls // msodik elforduls

Mivel a find() egy trolkon mkd mvelet, valamilyen tovbbi eszkz segtsgvel lehetv kell tennnk, hogy a msodik elfordulst is elrhessk. A tovbbi eszkz fogalmt ltalnostani minden trolra s algoritmusra nagyon nehz lenne, ezrt a standard knyvtr algoritmusai csak sorozatokon mkdnek. Az algoritmusok bemenett gyakran egy bejr-pr adja, amely egy sorozatot hatroz meg. Az els bejr az els elemet jelli ki, mg a msodik az utols utni elemet (3.8, 19.2). Az ilyen sorozatot flig nyltnak nevezzk, mivel az els megadott elemet tartalmazza, de a msodikat nem. A flig nylt sorozatok lehetv teszik, hogy a legtbb algoritmusnak ne kelljen egyedi esetknt kezelnie az res sorozatot. Egy sorozatot gyakran tekinthetnk tartomnynak (range) is, fleg ha elemeit kzvetlenl elrhetjk. A flig nylt tartomnyok hagyomnyos matematikai jellse az [els,utols) vagy az [els, utols[. Fontos, hogy egy ilyen sorozat lehet trol vagy annak rszsorozata is, de bizonyos sorozatok, pldul a ki- s bemeneti adatfolyamok, egyltaln nem kapcsoldnak trolkhoz. A sorozatokkal kifejezett algoritmusok mindegyik esetben tkletesen mkdnek.

Forrs: http://www.doksi.hu

682

A standard knyvtr

18.3.1. Bemeneti sorozatok


Az x.begin(), x.end() prral gyakran jelljk azt, hogy az x sszes elemre szksgnk van, pedig ez a jells hosszadalmas, radsul sok hibalehetsget hordoz magban. Pldul ha tbb bejrt is hasznlunk, nagyon knnyen hvunk meg egy algoritmust sorozatot nem alkot paramterprral:
void f(list<string>& fruit, list<string>& citrus) { typedef list<string>::const_iterator LI; LI p1 = find(fruit.begin(),citrus.end(),"alma"); LI p2 = find(fruit.begin(),fruit.end(),"alma"); LI p3 = find(citrus.begin(),citrus.end(),"krte"); LI p4 = find(p2,p3,"peach"); // ... // helytelen! (klnbz sorozatok) // rendben // rendben // helytelen! (klnbz sorozatok)

A pldban kt hiba is szerepel. Az els mg elg nyilvnval (fleg ha vrjuk a hibt), de a fordt mr ezt sem knnyen tallja meg. A msodik hibt viszont egy valdi programban nagyon nehz felderteni, mg egy gyakorlott programoz szmra is. Ha a bejrk szmt sikerl cskkentennk, akkor ezen hibk elfordulsnak valsznsgt is cskkenthetjk. Az albbiakban krvonalazunk egy megkzeltst, amely a bemeneti sorozatok fogalmnak bevezetsvel ezt a problmt prblja megoldani. Az algoritmusok ksbbi bemutatsakor azonban nem hasznljuk majd a bemeneti sorozatokat, mert azok a standard knyvtrban nem szerepelnek, s ebben a fejezetben kizrlag a standard knyvtrral akarunk foglalkozni. Az alaptlet az, hogy paramterknt egy sorozatot adunk meg:
template<class In, class T> In find(In first, In last, const T& v) { while (first!=last && *first!=v) ++first; return first; } template<class In, class T> In find(Iseq<In> r, const T& v) { return find(r.first,r.second,v); } // szabvnyos

// bvts

A tlterhels (overloading) (13.3.2) ltalban lehetv teszi, hogy az algoritmus bemeneti sorozat formjt hasznljuk, ha Iseq paramtert adunk t.

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

683

A bemeneti sorozatot termszetesen bejr-prknt (17.4.1.2) valstjuk meg:


template<class In> struct Iseq : public pair<In,In> { Iseq(In i1, In i2) : pair<In,In>(i1,i2) { } };

A find() fggvny msodik vltozatnak hasznlathoz kzvetlenl is elllthatunk Iseq bemeneti sorozatot:
LI p = find(Iseq<LI>(fruit.begin(),fruit.end()),"alma");

Ez a megolds azonban mg krlmnyesebb, mint az eredeti find() fggvny. Ahhoz, hogy ennek a megoldsnak hasznt vehessk, mg egy egyszer segdfggvnyre van szksg. Egy trolhoz megadott Iseq valjban nem ms, mint az elemek sorozata az elstl (begin()) az utolsig (end()):
template<class C> Iseq<C::iterator> iseq(C& c) { return Iseq<C::iterator>(c.begin(),c.end()); } // trolra

Ez a fggvny lehetv teszi, hogy a teljes trolkra vonatkoz algoritmusokat tmren, ismtlsek nlkl rjuk le:
void f(list<string>& ls) { list<string>::iterator p = find(ls.begin(),ls.end(),"szabvnyos"); list<string>::iterator q = find (iseq(ls),"bvts"); // .. }

Az iseq() fggvnynek knnyen elkszthetjk olyan vltozatait is, amelyek tmbkhz, bemeneti adatfolyamokhoz, vagy brmely ms trolhoz (18.13[6]) nyjtanak ilyen hozzfrst. Az Iseq legfontosabb elnye, hogy a bemeneti sorozat lnyegt vilgoss teszi. Gyakorlati haszna abban ll, hogy az iseq() megsznteti a knyelmetlen s hibalehetsget magban hordoz ismtldst, amelyet a bemeneti sorozat kt bejrval val megadsa jelent. A kimeneti sorozat fogalmnak meghatrozsa szintn hasznos lehet, br kevsb egyszer s kevsb kzvetlenl hasznlhat, mint a bemeneti sorozatok (18.13[7], lsd mg: 19.2.4).

Forrs: http://www.doksi.hu

684

A standard knyvtr

18.4. Fggvnyobjektumok
Nagyon sok olyan algoritmus van, amely a sorozatokat csak bejrk s rtkek segtsgvel kezeli. A 7 els elfordulst egy sorozatban pldul az albbi mdon tallhatjuk meg:
void f(list<int>& c) { list<int>::iterator p = find(c.begin(),c.end(),7); // ... }

Egy kicsit rdekesebb eset, ha mi adunk meg egy fggvnyt, amelyet az algoritmus hasznl (3.8.4). Az els, htnl kisebb elemet pldul a kvetkez programrszlet keresi meg:
bool less_than_7(int v) { return v<7; } void f(list<int>& c) { list<int>::iterator p = find_if(c.begin(),c.end(),less_than_7); // ... }

Nagyon sok egyszer helyzetben nyjtanak segtsget a paramterknt tadott fggvnyek: logikai felttelknt (prediktum), aritmetikai mveletknt, olyan eljrsknt, mellyel informcit nyerhetnk ki az elemekbl stb. Nem szoks s nem is hatkony minden felhasznlsi terletre kln fggvnyt rni. Msrszt egyetlen fggvny nha nem is kpes megvalstani ignyeinket. Gyakran elfordul pldul, hogy a minden egyes elemre meghvott fggvnynek a lefutsok kztt meg kell tartania valamilyen informcit. Az osztlyok tagfggvnyei az ilyen feladatokat jobban vgre tudjk hajtani, mint az nll eljrsok, hiszen az objektumok kpesek adatok trolsra s lehetsget adnak azok kezdeti rtknek belltsra is. Nzzk, hogyan is kszthetnk egy fggvnyt vagy pontosabban egy fggvnyszer osztlyt egy sszegzshez:
template<class T> class Sum { T res; public: Sum(T i = 0) : res(i) { } void operator()(T x) { res += x; }

// kezdeti rtkads // sszegzs

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

685

};

T result() const { return res; }

// sszegzs visszaadsa

Lthat, hogy a Sum osztly olyan aritmetikai tpusokhoz hasznlhat, melyek kezdrtke nulla lehet s alkalmazhat rjuk a += mvelet:
void f(list<double>& ld) { Sum<double> s; s = for_each(ld.begin(),ld.end(),s); cout << "Az sszeg: " << s.result() << '\n'; }

// s() meghvsa ld minden elemre

Ebben a programrszletben a for_each() (18.5.1) fggvny az ld minden egyes elemre meghvja a Sum<double>::operator()(double) tagfggvnyt, s eredmnyl a harmadik paramterben megadott objektumot adja vissza. Annak, hogy ez az utasts egyltaln mkdik, az az oka, hogy a for_each() nem teszi ktelezv, hogy harmadik paramtere tnyleg egy fggvny legyen. Mindssze annyit felttelez, hogy ez a valami meghvhat a megfelel paramterrel. Ezt a szerepet egy meghatrozott objektum is betltheti, sokszor hatkonyabban is, mint egy egyszer fggvny. Egy osztly fggvnyhv opertort pldul knnyebb optimalizlni, mint egy fggvnyt, melyet fggvnyre hivatkoz mutatknt adtunk t. Ezrt a fggvnyobjektumok gyakran gyorsabban futnak, mint a szoksos fggvnyek. Azon osztlyok objektumait, melyekhez elksztettk a fggvnyhv opertort (11.9), fggvnyszer objektumoknak, funktoroknak (functor) vagy egyszeren fggvnyobjektumoknak nevezzk.

18.4.1. Fggvnyobjektumok bzisosztlyai


A standard knyvtr szmos hasznos fggvnyobjektumot knl. A fggvnyobjektumok elksztsnek megknnytshez a knyvtr kt bzisosztlyt tartalmaz:
template <class Arg, class Res> struct unary_function { typedef Arg argument_type; typedef Res result_type; }; template <class Arg, class Arg2, class Res> struct binary_function { typedef Arg first_argument_type; typedef Arg2 second_argument_type; typedef Res result_type; };

Forrs: http://www.doksi.hu

686

A standard knyvtr

Ezen osztlyok clja, hogy szabvnyos neveket adjanak a paramtereknek s a visszatrsi rtk tpusnak, gy a unary_function s a binary_function osztly leszrmazottait hasznl programozk elrhetik azokat. A standard knyvtr kvetkezetesen hasznlja ezeket az osztlyokat, ami abban is segti a programozt, hogy megllaptsa, mire is j az adott fggvnyobjektum (18.4.4.1).

18.4.2. Prediktumok
A prediktum (llts, felttel; predicate) egy olyan fggvnyobjektum (vagy fggvny), amely logikai (bool) rtket ad vissza. A <functional> fejllomnyban pldul az albbi defincikat tallhatjuk:
template <class T> struct logical_not : public unary_function<T,bool> { bool operator()(const T& x) const { return !x; } }; template <class T> struct less : public binary_function<T,T,bool> { bool operator()(const T& x, const T& y) const { return x<y; } };

Az egy- s ktoperandos (unris/binris) prediktumokra gyakran van szksg a szabvnyos algoritmusok esetben. Pldul sszehasonlthatunk kt sorozatot gy, hogy megkeressk az els elemet, amely az egyik sorozatban nem kisebb, mint a neki megfelel elem a msikban:
void f(vector<int>& vi, list<int>& li) { typedef list<int>::iterator LI; typedef vector<int>::iterator VI; pair<VI,LI> p1 = mismatch(vi.begin(),vi.end(),li.begin(),less<int>()); // ... }

A mismatch() fggvny a sorozatok sszetartoz elemeire alkalmazza a megadott ktoperandus prediktumot, mindaddig, amg az igaz rtket nem ad vissza (18.5.4). A visszatrsi rtk az a kt bejr, amelyeket az sszehasonlts nem sszetartoznak tallt. Mivel nem tpust, hanem objektumot kell tadnunk, a less<int>() kifejezst hasznljuk (zrjelekkel) a less<int> forma helyett.

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

687

Elkpzelhet, hogy az els olyan elem helyett, amely nem kisebb a prjnl, pont arra van szksgnk, amely kisebb annl. Ezt a feladatot az els olyan elempr megkeressvel vgezhetjk el, amelyre a nagyobb vagy egyenl (greater_equal) kiegszt felttel nem teljesl:
p1 = mismatch(vi.begin(),vi.end(),li.begin(),greater_equal<int>());

Egy msik lehetsg, hogy a sorozatokat fordtott sorrendben adjuk meg s a kisebb vagy egyenl (less_equal) mveletet hasznljuk:
pair<LI,VI> p2 = mismatch(li.begin(),li.end(),vi.begin(),less_equal<int>());

A 18.4.4.4 pontban azt is megnzzk, hogyan rhatjuk le kzvetlenl a nem kisebb felttelt.

18.4.2.1. A prediktumok ttekintse A <functional> fejllomnyban nhny ltalnos prediktum tallhat:

Prediktumok <functional> equal_to not_equal_to greater less greater_equal less_equal logical_and logical_or logical_not binris binris binris binris binris binris binris binris unris arg1==arg2 arg1!=arg2 arg1>arg2 arg1<arg2 arg1>=arg2 arg1<=arg2 arg1&&arg2 arg1||arg2 !arg

Az unris egyparamter, a binris ktparamter fggvnyt jelent, az arg (argumentum) a paramtereket jelli. A less s a logical_not mveletet a 18.4.2 pont elejn rtuk le. A knyvtr ltal knlt prediktumokon kvl a programoz sajt maga is ltrehozhat ilyen fggvnyeket. Ezek a programoz ltal megadott prediktumok nagy szerepet jtszanak a standard knyvtr algoritmusainak egyszer s elegns hasznlatban. A prediktumok meghatrozsnak lehetsge klnsen fontos akkor, ha olyan osztlyra akarunk hasznlni egy algoritmust, amely teljesen fggetlen a standard knyvtrtl s annak algoritmusaitl. Pldul kpzeljk el a 10.4.6 pontban bemutatott Club osztly kvetkez vltozatt:

Forrs: http://www.doksi.hu

688

A standard knyvtr

class Person { /* ... */ }; struct Club { string name; list<Person*> members; list<Person*> officers; // ... }; Club(const string& n);

Igen logikus feladat lenne, hogy megkeressnk egy adott nev klubot egy list<Club> trolban. A standard knyvtr find_if() algoritmusa azonban egyltaln nem ismeri a Club osztlyt. A knyvtri algoritmusok tudjk, hogyan lehet egyenlsget vizsglni, de a klubot mi nem a teljes rtke alapjn akarjuk megtallni, mindssze a Club::name adattagot akarjuk kulcsknt hasznlni. Teht runk egy olyan prediktumot, amely ezt a felttelt tkrzi:
class Club_eq : public unary_function<Club,bool> { string s; public: explicit Club_eq(const string& ss) : s(ss) { } bool operator()(const Club& c) const { return c.name==s; } };

A megfelel prediktumok meghatrozsa ltalban nagyon egyszer, s ha az ltalunk ltrehozott tpusokhoz megadtuk a megfelel prediktumokat, akkor ezekre a szabvnyos algoritmusok ugyanolyan egyszeren s hatkonyan hasznlhatk lesznek, mint az egyszer tpusokbl felptett trolk esetben:
void f(list<Club>& lc) { typedef list<Club>::iterator LCI; LCI p = find_if(lc.begin(),lc.end(),Club_eq("tkez filozfusok")); // ... }

18.4.3. Aritmetikai fggvnyobjektumok


Amikor numerikus osztlyokat hasznlunk, gyakran van szksgnk a szoksos aritmetikai mveletekre fggvnyobjektumok formjban. Ezrt a <functional> llomnyban a kvetkez mveletek deklarcija is szerepel:

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

689

Aritmetikai mveletek <functional> plus minus multiplies divides modulus negate binris binris binris binris binris unris arg1+arg2 arg1arg2 arg1*arg2 arg1/arg2 arg1%arg2 arg

A multiplies mveletet hasznlhatjuk pldul arra, hogy kt vector elemeit sszeszorozzuk s ezzel egy harmadik vektort hozzunk ltre:
void discount(vector<double>& a, vector<double>& b, vector<double>& res) { transform(a.begin(),a.end(),b.begin(),back_inserter(res),multiplies<double>()); }

A back_inserter() eljrsrl a 19.2.4. pontban lesz sz. Az aritmetikai algoritmusok nmelyikvel a 22.6 fejezetben rszletesen foglalkozunk.

18.4.4. Lektk, talaktk s tagadk


Hasznlhatunk olyan prediktumokat s aritmetikai fggvnyobjektumokat is, melyeket mi magunk rtunk, de hivatkozunk bennk a standard knyvtr ltal knlt eljrsokra. Ha azonban egy j prediktumra van szksgnk, elllthatjuk azt egy mr ltez prediktum apr mdostsval is. A standard knyvtr tmogatja a fggvnyobjektumok ilyen felptst: 18.4.4.1 A lektk (binder) lehetv teszik, hogy egy ktparamter fggvnyobjektumot egyparamter fggvnyknt hasznljuk, azltal, hogy az egyik paramterhez egy rgztett rtket ktnek. 18.4.4.2 A tagfggvny-talaktk (member function adapter) lehetv teszik, hogy tagfggvnyeket hasznljunk az algoritmusok paramtereknt. 18.4.4.3 A fggvnyre hivatkoz mutatk talakti (pointer to function adapter) lehetv teszik, hogy fggvnyre hivatkoz mutatkat hasznljunk algoritmusok paramtereknt. 18.4.4.4 A tagadk (negater) segtsgvel egy llts (prediktum) tagadst, ellenttt (negltjt) fejezhetjk ki. Ezeket a fggvnyobjektumokat egytt talaktknak (adapter) nevezzk. Mindegyik talakt azonos felpts s a unary_function s binary_function fggvnyobjektum-bzisosztlyokon (18.4.1) alapul. Mindegyikhez rendelkezsnkre ll egy segdfggvny, mely

Forrs: http://www.doksi.hu

690

A standard knyvtr

paramterenknt egy fggvnyobjektumot kap s a megfelel fggvnyobjektumot adja vissza. Ha ezeket az osztlyokat az operator()() mvelettel hvjuk meg, akkor a kvnt feladat kerl vgrehajtsra. Teht az talakt egyszeren egy magasabb szint fggvny: egy fggvnyt kap paramterknt s ebbl egy msik fggvnyt llt el: Lektk, talaktk, tagadk <functional> bind2nd(y) Ktparamter fggvny meghvsa gy, hogy y a msodik paramter. bind1st(x) binder1st Ktparamter fggvny meghvsa gy, hogy x az els paramter. mem_fun() mem_fun_t Paramter nlkli tagfggvny meghvsa mutatn keresztl. mem_fun1_t Egyparamter tagfggvny meghvsa mutatn keresztl. const_mem_fun_t Paramter nlkli konstans tagfggvny meghvsa mutatn keresztl. const_mem_fun1_t Egyparamter konstans tagfggvny meghvsa mutatn keresztl. mem_fun_ref() mem_fun-ref_t Paramter nlkli tagfggvny meghvsa referencin keresztl. mem_fun1_ref_t Egyparamter tagfggvny meghvsa referencin keresztl. const_mem_fun_ref_t Paramter nlkli konstans tagfggvny meghvsa referencin keresztl. const_mem_fun1_ref_t Egyparamter konstans tagfggvny meghvsa referencin keresztl. ptr_fun() pointer_to_unary_function Egyparamter fggvnyre hivatkoz mutat meghvsa. ptr_fun() pointer_to_binary_function Ktparamter fggvnyre hivatkoz mutat meghvsa. not1() unary_negate Egyparamter prediktum neglsa. not2() binary_negate Ktparamter prediktum neglsa. binder2nd

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

691

18.4.4.1. Lektk Az olyan ktparamter prediktumok, mint a less (18.4.2), igen hasznosak s rugalmasak. Gyakran tapasztaljuk azonban, hogy a legknyelmesebb prediktum az lenne, amely a trol minden elemt ugyanahhoz a rgztett elemhez hasonltan. A 18.4. pontban bemutatott less_than_7() egy jellemz plda erre. A less mveletnek mindenkppen kt paramtert kell megadnunk minden egyes hvskor, gy kzvetlenl nem hasznlhatjuk ezt az eljrst. A megolds a kvetkez lehet:
template <class T> class less_than : public unary_function<T,bool> { T arg2; public: explicit less_than(const T& x) : arg2(x) { } bool operator()(const T& x) const { return x<arg2; } };

Ezek utn mr lerhatjuk a kvetkezt:


void f(list<int>& c) { list<int>::const_iterator p = find_if(c.begin(),c.end(),less_than<int>(7)); // ... }

A less_than(7) forma helyett a less_than<int>(7) alakot kell hasznlnunk, mert az <int> sablonparamter nem vezethet le a konstruktor paramternek (7) tpusbl (13.3.1). A less_than prediktum ltalban igen hasznos. A fenti mvelet viszont gy jtt ltre, hogy rgztettk, lektttk (bind) a less fggvny msodik paramtert. Az ilyen szerkezet, melyben teht egy paramtert rgztnk, minden helyzetben ugyangy megvalsthat, s annyira ltalnos s hasznos, hogy a standard knyvtr egy kln osztlyt knl erre a clra:
template <class BinOp> class binder2nd : public unary_function<BinOp::first_argument_type, BinOp::result_type> { protected: BinOp op; typename BinOp::second_argument_type arg2; public: binder2nd(const BinOp& x, const typename BinOp::second_argument_type& v) : op(x), arg2(v) { } result_type operator()(const argument_type& x) const { return op(x,arg2); } };

Forrs: http://www.doksi.hu

692

A standard knyvtr

template <class BinOp, class T> binder2nd<BinOp> bind2nd(const BinOp& op, const T& v) { return binder2nd<BinOp>(op,v); }

A bind2nd() fggvnyobjektumot hasznlhatjuk pldul arra, hogy meghatrozzuk a kisebb, mint 7 egyparamter felttelt a less fggvny s a 7 rtk felhasznlsval:
void f(list<int>& c) { list<int>::const_iterator p = find_if(c.begin(),c.end(),bind2nd(less<int>(),7)); // ... }

Elg olvashat ez a megolds? Elg hatkony? Egy tlagos C++-vltozat megvalstsval sszehasonltva bizony hatkonyabb, mint az eredeti, melyben a 18.4. pont less_than_7() fggvnyt hasznltuk akr idigny, akr trhasznlat szempontjbl! Az sszehasonlts radsul knnyen fordthat helyben kifejtett fggvnyknt. A jells logikus, de megszokshoz kell egy kis id, ezrt rdemes konkrt nvvel megadni a kttt paramter mveletet is:
template <class T> struct less_than : public binder2nd< less<T> > { explicit less_than(const T& x) : binder2nd(less<T>(),x) { } }; void f(list<int>& c) { list<int>::const_iterator p = find_if(c.begin(),c.end(),less_than<int>(7)); // ... }

Fontos, hogy a less_than eljrst a less mvelettel hatrozzuk meg, s nem kzvetlenl a < opertorral, mert gy a less_than hasznlni tudja a less brmely elkpzelhet specializcijt (13.5, 19.2.2). A bind2nd() s a binder2nd mellett a <functional> fejllomnyban megtallhatjuk a bind1st() s binder1st osztlyt is, melyekkel egy ktparamter fggvny els paramtert rgzthetjk. Egy paramter lektse, amit a bind1st() s a bind2nd() nyjt, nagyon hasonlt ahhoz az ltalnos szolgltatshoz, amelyet Currying-nek neveznek.

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

693

18.4.4.2. Tagfggvny-talaktk A legtbb algoritmus felhasznl valamilyen szabvnyos vagy programoz ltal megadott mveletet. Termszetesen gyakran tagfggvnyt szeretnnk meghvni. Pldul (3.8.5):
void draw_all(list<Shape*>& c) { for_each(c.begin(),c.end(),&Shape::draw); }

// hopp! hiba

A problmt az jelenti, hogy egy mf() tagfggvny meghvshoz az objektumot is meg kell adnunk: p->mf(). Az olyan algoritmusok azonban, mint a for_each(), a paramterknt kapott fggvnyeket az egyszer fggvnyhv utastssal hajtjk vgre: f(). Ezrt szksgnk van egy olyan kvetkezetes s hatkony mdszerre, amivel ltrehozhatunk valamit, ami kpes rvenni az algoritmusokat, hogy tagfggvnyeket hvjanak meg. Egy lehetsges megolds az lenne, hogy minden algoritmusnak kt pldnyt hozzuk ltre: az egyik a tagfggvnyekkel lenne hasznlhat, a msik az egyszer fggvnyekkel. A helyzet mg ennl is rosszabb, mert olyan vltozatokra is szksgnk lenne, amelyek objektumok trolin mkdnnek (nem objektumokra hivatkoz mutatkon). Ugyangy, mint a lektk (18.4.4.1) esetben, a megoldst itt is egy j osztly s egy fggvny megrsa jelenti. Elszr vizsgljuk meg azt az ltalnos esetet, amikor egy tagfggvnyt paramterek nlkl szeretnnk meghvni egy mutatkat tartalmaz trol minden elemre:
template<class R, class T> class mem_fun_t : public unary_function<T*,R> { R (T::*pmf)(); public: explicit mem_fun_t(R (T::*p)()) :pmf(p) {} R operator()(T* p) const { return (p->*pmf)(); } // meghvs mutatn keresztl }; template<class R, class T> mem_fun_t<R,T> mem_fun(R (T::*f)()) { return mem_fun_t<R,T>(f); }

Ez megoldja a pldban szerepl Shape::draw() hvst:


void draw_all(list<Shape*>& lsp) { } // paramter nlkli tag meghvsa objektumra // hivatkoz mutatn keresztl // minden alakzat // kirajzolsa

for_each(lsp.begin(),lsp.end(),mem_fun(&Shape::draw));

Forrs: http://www.doksi.hu

694

A standard knyvtr

Ezenkvl szksgnk van egy olyan osztlyra s mem_fun() fggvnyre is, amelyek a paramteres tagfggvnyek kezelsre kpesek. Olyan vltozatok is kellenek, melyekkel kzvetlenl objektumokat hasznlhatunk, nem pedig objektumokra hivatkoz mutatkat. Ezek neve mem_fun_ref(). Vgl szksg van a const tagfggvnyeket kezel vltozatokra is:
template<class R, class T> mem_fun_t<R,T> mem_fun(R (T::*f)()); // s az egyparamter tagokra, a const tagokra, s az egyparamter const tagokra // vonatkoz vltozatok (lsd a 18.4.4 tblzatot) template<class R, class T> mem_fun_ref_t<R,T> mem_fun_ref(R (T::*f)()); // s az egyparamter tagokra, a const tagokra, s az egyparamter const tagokra // vonatkoz vltozatok (lsd a 18.4.4 tblzatot)

A <functional> fejllomny tagfggvny-talaktinak felhasznlsval a kvetkezket rhatjuk:


void f(list<string>& ls) // paramter nlkli tagfggvny hasznlata objektumra { typedef list<string>::iterator LSI; LSI p = find_if(ls.begin(),ls.end(),mem_fun_ref(&string::empty)); // "" keresse } void rotate_all(list<Shape*>& ls, int angle) // egyparamter tagfggvny hasznlata objektumra hivatkoz mutatn keresztl { for_each(ls.begin(),ls.end(),bind2nd(mem_fun(&Shape::rotate),angle)); }

A standard knyvtrnak nem kell foglalkoznia azokkal a tagfggvnyekkel, melyek egynl tbb paramtert vrnak, mert a standard knyvtrban nincs olyan algoritmus, amely kettnl tbb paramter fggvnyt vrna operandusknt.

18.4.4.3. Fggvnyre hivatkoz mutatk talakti Egy algoritmus nem foglalkozik azzal, hogy a fggvnyparamter milyen formban adott: fggvny, fggvnyre hivatkoz mutat vagy fggvnyobjektum. Ellenben a lektk (18.4.4.1) szmra ez fontos, mert trolniuk kell egy msolatot a ksbbi felhasznlshoz. A standard knyvtr kt talaktt knl a fggvnyekre hivatkoz mutatk szabvnyos algoritmusokban val felhasznlshoz. A definci s a megvalsts nagyon hasonlt a tagfggvny-talaktknl (18.4.4.2) hasznlt megoldsra. Most is kt fggvnyt s kt osztlyt hasznlunk:

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

695

template <class A, class R> pointer_to_unary_function<A,R> ptr_fun(R (*f)(A)); template <class A, class A2, class R> pointer_to_binary_function<A,A2,R> ptr_fun(R (*f)(A, A2));

A fggvnyre hivatkoz mutatk ezen talakti lehetv teszik, hogy a szoksos fggvnyeket a lektkkel egytt hasznljuk:
class Record { /* ... */ }; bool name_key_eq(const Record&, const char*);// sszehasonlts nevek alapjn bool ssn_key_eq(const Record&, long); // sszehasonlts szmok alapjn void f(list<Record>& lr) // fggvnyre hivatkoz mutat hasznlata { typedef typename list<Record>::iterator LI; LI p = find_if(lr.begin(),lr.end(),bind2nd(ptr_fun(name_key_eq),"John Brown")); LI q = find_if(lr.begin(),lr.end(),bind2nd(ptr_fun(ssn_key_eq),1234567890)); // ... }

A fenti utastsok azokat az elemeket keresik meg az lr listban, melyekben a John Brown, illetve az 1234567890 kulcsrtk szerepel.

18.4.4.4. Tagadk A prediktum-tagadk (negater) a lektkhz kapcsoldnak abbl a szempontbl, hogy egy mveletet kapnak paramterknt s ebbl egy msik mveletet lltanak el. A tagadk defincija s megvalstsa kveti a tagfggvny-talaktknl (18.4.4.2) alkalmazott formt. Meghatrozsuk rendkvl egyszer, de ezt az egyszersget kicsit elhomlyostja a hossz szabvnyos nevek hasznlata:
template <class Pred> class unary_negate : public unary_function<typename Pred::argument_type,bool> { Pred op; public: explicit unary_negate(const Pred& p) : op(p) { } bool operator()(const argument_type& x) const { return !op(x); } }; template <class Pred> class binary_negate : public binary_function<typename Pred::first_argument_type, typename Pred::second_argument_type, bool> {

Forrs: http://www.doksi.hu

696

A standard knyvtr

typedef first_argument_type Arg; typedef second_argument_type Arg2; Pred op; public: explicit binary_negate(const Pred& p) : op(p) { } bool operator()(const Arg& x, const Arg2& y) const { return !op(x,y); } }; template<class Pred> unary_negate<Pred> not1(const Pred& p); // unris tagadsa template<class Pred> binary_negate<Pred> not2(const Pred& p); // binris tagadsa

Ezek az osztlyok s fggvnyek is a <functional> fejllomnyban kaptak helyet. A first_argument_type, second_argument_type stb. elnevezsek a unary_function, illetve a binary_function szabvnyos bzisosztlyokbl erednek. Ugyangy, mint a lektk, a tagadk is knyelmesen hasznlhatk segdfggvnyeiken keresztl. Pldul a nem kisebb, mint ktparamter prediktumot is egyszeren lerhatjuk, s megkereshetjk vele az els kt olyan szomszdos elemet, melyek kzl az els nagyobb vagy egyenl, mint a msodik:
void f(vector<int>& vi, list<int>& li) // a 18.4.2 pldjnak javtott vltozata { // ... p1 = mismatch(vi.begin(),vi.end(),li.begin(),not2(less<int>())); // ... }

Teht a p1 kapja meg az els olyan elemprt, melyre a nem kisebb, mint mvelet hamis rtket ad vissza. A prediktumok logikai rtkekkel dolgoznak, gy a bitenknti opertoroknak (|, &, ^, ~) nincs megfeleljk. Termszetesen a lektk, az talaktk s a tagadk egytt is hasznlhatk:
extern "C" int strcmp(const char*,const char*); // a <cstdlib> fejllomnybl void f(list<char*>& ls) // fggvnyre hivatkoz mutat hasznlata { typedef typename list<char*>::const_iterator LI; LI p = find_if(ls.begin(),ls.end(),not1(bind2nd(ptr_fun(strcmp),"vicces"))); }

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

697

Ez a kdrszlet az els olyan elemet keresi meg az ls listban, amely a vicces C stlus karakterlncot tartalmazza. A tagadra azrt van szksg, mert a strcmp() fggvny akkor ad vissza 0 rtket, ha a kt karakterlnc egyenl.

18.5. Nem mdost algoritmusok sorozatokon


A sorozatok nem mdost algoritmusai elssorban arra szolglnak, hogy a sorozatokban anlkl kereshessnk meg bizonyos elemeket, hogy ciklust rnnk. Ezen kvl lehetsget adnak arra, hogy az elemekrl megtudjunk minden ltez informcit. Ezek az algoritmusok csak konstans bejrkat (19.2.1) hasznlnak s a for_each() kivtelvel nem hasznlhatk olyan mveletek elvgzsre, melyek a sorozat elemeit megvltoztatnk.

18.5.1. A for_each
Knyvtrakat azrt hasznlunk, hogy ne neknk kelljen azzal fradozni, amit valaki ms mr megvalstott. Egy knyvtr fggvnyeinek, osztlyainak, algoritmusainak stb. hasznlata megknnyti egy program megtervezst, megrst, tesztelst s dokumentlst is. A standard knyvtr hasznlata ezenkvl olvashatbb is teszi programunkat olyanok szmra, akik ismerik a knyvtrat, hiszen nem kell idt tltenik a hzilag sszeeszkblt algoritmusok rtelmezsvel. A standard knyvtr algoritmusainak legfbb elnye, hogy a programoznak nem kell megrnia bizonyos ciklusokat. A ciklusok nehzkesek s knnyen kvethetnk el bennk hibkat. A for_each() algoritmus a legegyszerbb algoritmus, abban az rtelemben, hogy semmi mst nem csinl, minthogy egy ciklust helyettest, egy sorozat minden elemre vgrehajtva a paramterben megadott mveletet:
template<class In, class Op> Op for_each(In first, In last, Op f) { while (first != last) f(*first++); return f; }

Milyen fggvnyeket akarunk ilyen formban meghvni? Ha az elemekrl akarunk informcikat sszegyjteni, az accumulate() fggvnyt (22.6) hasznlhatjuk. Ha meg akarunk tallni valamit egy sorozatban, rendelkezsnkre ll a find() s a find_if() algoritmus. Ha bi-

Forrs: http://www.doksi.hu

698

A standard knyvtr

zonyos elemeket trlni vagy mdostani szeretnnk, a remove() (18.6.5), illetve a replace() (18.6.4) jelent egyszerbb megoldst. Teht mieltt hasznlni kezdjk a for_each() eljrst, gondoljuk vgig, nincs-e cljainknak jobban megfelel algoritmus. A for_each() eredmnye az a fggvny vagy fggvnyobjektum, amelyet harmadik paramterknt megadtunk. A 18.4 pontban a Sum plda bemutatta, hogy ez a megolds lehetv teszi az eredmnyek visszaadst a hvnak. A for_each() gyakori felhasznlsi terlete az, hogy egy sorozat elemeibl bizonyos informcikat fejtnk ki. Pldul sszegyjthetnk neveket klubok egy listjbl:
void extract(const list<Club>& lc, list<Person*>& off) // hivatalnokok thelyezse 'lc'-bl 'off'-ba { for_each(lc.begin(),lc.end(),Extract_officers(off)); }

A 18.4 s a 18.4.2. pontban szerepl pldknak megfelelen kszthetnk egy fggvnyosztlyt, amely kikeresi a kvnt informcit. Ebben az esetben a kigyjtend neveket a list<Person*> tartalmazza a Club objektumokon bell, ezrt az Extract_officers fggvnynek ki kell msolnia a hivatalnokokat (officer) a Club objektumok officers listjbl a gyjt listba:
class Extract_officers { list<Person*>& lst; public: explicit Extract_officers(list<Person*>& x) : lst(x) { } void operator()(const Club& c) { copy(c.officers.begin(),c.officers.end(),back_inserter(lst)); }

};

A nevek kiratst szintn a for_each() fggvnnyel vgezhetjk:


void extract_and_print(const list<Club>& lc) { list<Person*> off; extract(lc,off); for_each(off.begin(),off.end(),Print_name(cout)); }

A Print_name fggvny megrst meghagyjuk feladatnak (18.13[4]).

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

699

A for_each() algoritmust a nem mdost eljrsok kz soroltuk, mert kzvetlenl nem mdost. Ha azonban egy nem konstans sorozatra hvjuk meg, akkor a harmadik paramterben megadott mvelet mdosthatja a sorozatot. (Nzzk meg pldul a negate() fggvny hasznlatt a 11.9 pontban.)

18.5.2. A find fggvnycsald


A find() algoritmusok vgignznek egy sorozatot (vagy sorozatprt), s megkeresnek egy konkrt rtket vagy egy olyan elemet, amelyre valamilyen llts (prediktum) teljesl. A find() legegyszerbb vltozatai csak ezt a feladatot vgzik el:
template<class In, class T> In find(In first, In last, const T& val); template<class In, class Pred> In find_if(In first, In last, Pred p);

A find() s a find_if() egy bejrt ad vissza, amely az els olyan elemre mutat, amely a keress felttelnek megfelel. Valjban a find() felfoghat a find_if() egy olyan vltozatnak is, ahol a vizsglt prediktum az ==. Mirt nem lett mindkt fggvny neve find()? Azrt, mert fggvny-tlterhelssel nem mindig tudunk klnbsget tenni kt azonos paramterszm sablon fggvny kztt:
bool pred(int); void f(vector<bool(*f)(int)>& v1, vector<int>& v2) { find(v1.begin(),v1.end(),pred); // 'pred' keresse find_if(v2.begin(),v2.end(),pred); // azon int keresse, amelyre pred() igazat ad vissza }

Ha a find() s a find_if() fggvnynek ugyanaz lenne a neve, akkor igen meglep tbbrtelmsggel tallkoztunk volna. ltalban az _if uttag azt jelzi, hogy az algoritmus egy prediktumot vr paramterknt. A find_first_of() algoritmus egy sorozat els olyan elemt keresi meg, amely megtallhat a msodik sorozatban is:
template<class For, class For2> For find_first_of(For first, For last, For2 first2, For2 last2); template<class For, class For2, class BinPred> For find_first_of(For first, For last, For2 first2, For2 last2, BinPred p);

Forrs: http://www.doksi.hu

700

A standard knyvtr

Pldul:
int x[ ] = { 1,3,4 }; int y[ ] = { 0,2,3,4,5}; void f() { int* p = find_first_of(x,x+3,y,y+5); int* q = find_first_of(p+1,x+3,y,y+5); }

// p = &x[1] // q = &x[2]

A p mutat az x[1] elemre fog mutatni, mert a 3 az els olyan eleme az x-nek, amely megtallhat az y-ban. Hasonlan a q az x[2]-re fog mutatni. Az adjacent_find() algoritmus kt egyms utni, egyez elemet keres:
template<class For> For adjacent_find(For first, For last); template<class For, class BinPred> For adjacent_find(For first, For last, BinPred p);

A visszatrsi rtk egy bejr, amely az els megfelel elemre mutat:


void f(vector<string>& text) { vector<string>::iterator p = adjacent_find(text.begin(),text.end()); if (p!=text.end() && *p=="az") { // Mr megint ktszer szerepel az "az"! text.erase(p); // ... } }

18.5.3. A count()
A count() s a count_if() fggvny egy rtk elfordulsainak szmt adja meg egy sorozatban:
template<class In, class T> typename iterator_traits<In>::difference_type count(In first, In last, const T& val); template<class In, class Pred> typename iterator_traits<In>::difference_type count_if(In first, In last, Pred p);

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

701

A count() visszatrsi rtke nagyon rdekes. Kpzeljk el, hogy a count()-ot az albbi egyszer fggvnnyel valstjuk meg:
template<class In, class T> int count(In first, In last, const T& val) { int res = 0; while (first != last) if (*first++ == val) ++res; return res; }

A gond az, hogy egy int lehet, hogy nem felel meg visszatrsi rtknek. Egy olyan szmtgpen, ahol az int tpus elg kicsi, elkpzelhet, hogy tl sok elem van a sorozatban, gy a count() azt nem tudja egy int-be helyezni. Egy nagyteljestmny programban, egyedi rendszeren viszont rdemesebb a szmll ltal visszaadott rtket egy short-ban trolni. Abban biztosak lehetnk, hogy egy sorozat elemeinek szma nem nagyobb, mint a kt bejrja kztti legnagyobb klnbsg (19.2.1). Ezrt az els gondolatunk a problma megoldsra az lehet, hogy a visszatrsi rtk tpust a kvetkezkppen hatrozzuk meg:
typename In::difference_type

Egy szabvnyos algoritmusnak azonban a beptett tmbkre ugyangy kell mkdnie, mint a szabvnyos trolkra:
void f(const char* p, int size) { int n = count(p,p+size,'e'); }

// az 'e' bet elfordulsainak megszmllsa

Sajnos az int*::difference_type kifejezs a C++-ban nem rtelmezhet. Ez a problma az iterator_traits tpus (19.2.2) rszleges specializcijval oldhat meg.

18.5.4. Egyenlsg s eltrs


Az equal() s a mismatch() fggvny kt sorozatot hasonlt ssze:
template<class In, class In2> bool equal(In first, In last, In2 first2); template<class In, class In2, class BinPred> bool equal(In first, In last, In2 first2, BinPred p);

Forrs: http://www.doksi.hu

702

A standard knyvtr

template<class In, class In2> pair<In, In2> mismatch(In first, In last, In2 first2); template<class In, class In2, class BinPred> pair<In, In2> mismatch(In first, In last, In2 first2, BinPred p);

Az equal() algoritmus egyszeren azt mondja meg, hogy a kt sorozat minden kt megfelel eleme megegyezik-e, mg a mismatch() az els olyan elemprt keresi, melyek nem egyenlek, s ezekre mutat bejrkat ad vissza. A msodik sorozat vgt nem kell megadnunk (teht nincs last2), mert a rendszer azt felttelezi, hogy az legalbb olyan hossz, mint az els sorozat s a last2 rtket a first2+(last-first) kifejezssel szmtja ki. Ezt a mdszert gyakran lthatjuk a standard knyvtrban, amikor kt sorozat sszetartoz elemei kztt vgznk valamilyen mveletet. A 18.5.1 pontban mr emltettk, hogy ezek az algoritmusok sokkal hasznosabbak, mint azt els rnzsre gondolnnk, mert a programoz hatrozhatja meg azt a prediktumot, melyet az elemek egyenrtksgnek eldntshez akar hasznlni. A sorozatoknak nem is kell ugyanolyan tpusaknak lennik:
void f(list<int>& li, vector<double>& vd) { bool b = equal(li.begin(),li.end(),vd.begin()); }

Az egyetlen kikts, hogy a prediktum paramtereiknt hasznlhassuk az elemeket. A mismatch() kt vltozata csak a prediktumok hasznlatban klnbzik. Valjban megvalsthatjuk ket egyetlen fggvnnyel is, amelynek van alaprtelmezett sablonparamtere:
template<class In, class In2, class BinPred> pair<In, In2> mismatch(In first, In last, In2 first2, BinPred p = equal_to<In::value_type>()) { while (first != last && p(*first,*first2)) { ++first; ++first2; } return pair<In,In2>(first,first2); }

// 18.4.2.1

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

703

A kt kln fggvny megadsa s az egyetlen, alaprtelmezett paramterrel meghatrozott fggvny kztt akkor lthatjuk a klnbsget, ha mutatkat adunk t a fggvnyeknek. Ennek ellenre, ha gy gondolunk a szabvnyos algoritmusok klnbz vltozataira, mint egy alaprtelmezett prediktummal rendelkez fggvnyre, akkor krlbell feleannyi sablon fggvnyre kell emlkeznnk.

18.5.5. Keress
A search(), a search_n() s a find_end() algoritmus egy rszsorozatot keres egy msik sorozatban:
template<class For, class For2> For search(For first, For last, For2 first2, For2 last2); template<class For, class For2, class BinPred> For search(For first, For last, For2 first2, For2 last2, BinPred p); template<class For, class For2> For find_end(For first, For last, For2 first2, For2 last2); template<class For, class For2, class BinPred> For find_end(For first, For last, For2 first2, For2 last2, BinPred p); template<class For, class Size, class T> For search_n(For first, For last, Size n, const T& val); template<class For, class Size, class T, class BinPred> For search_n(For first, For last, Size n, const T& val, BinPred p);

A search() fggvny a msodikknt megadott sorozatot keresi az elsben. Ha megtallhat ez a rszsorozat, egy bejrt kapunk eredmnyl, amely az els illeszked elemet jelli ki az els sorozatban. Ha a keress sikertelen, akkor a sorozat vgt (last) kapjuk eredmnykppen. Teht a visszatrsi rtk mindig a [first,last] tartomnyban van:
string quote("Minek vesztegessk az idt tanulsra, mikor a tudatlansg azonnali?"); bool in_quote(const string& s) { typedef string::const_iterator I; I p = search(quote.begin(),quote.end(),s.begin(),s.end()); // s keresse az idzetben (quote) return p!=quote.end(); }

Forrs: http://www.doksi.hu

704

A standard knyvtr

void g() { bool b1 = in_quote("tanulsra"); bool b2 = in_quote("tantsra"); }

// b1 = true // b2 = false

Teht a search() mvelettel egy rszsorozatot kereshetnk mindenfle sorozatra ltalnostva. Ebbl mr rezhetjk, hogy a search() egy nagyon hasznos algoritmus. A find_end() is a msodikknt megadott sorozatot keresi rszsorozatknt az elsben. Ha sikeres a keress, a find_end() az illeszked rsz utols elemre mutat bejrt adja vissza. Mondhatjuk azt is, hogy a find_end() visszafel vgzi ugyanazt a keresst, mint a search(). Teht nem a rszsorozat els elfordulst kapjuk meg, hanem az utolst. A search_n() eljrs egy olyan rszsorozatot keres, amely legalbb n hosszon a value paramterben megadott rtket tartalmazza. A visszatrsi rtk egy olyan bejr, amely az n darab illeszkeds els elemre mutat a sorozatban.

18.6. Mdost algoritmusok sorozatokra


Ha meg akarunk vltoztatni egy sorozatot, akkor megtehetjk, hogy egyesvel vgignzzk az elemeket s kzben mdostjuk a megfelel rtkeket. De ha lehetsg van r, akkor ezt a programozsi stlust rdemes elkerlnnk. Helyette egyszerbb s rendszerezettebb megolds ll a rendelkezsnkre: hasznljuk a szabvnyos algoritmusokat a sorozatok bejrsra s a mdost mveletek elvgzsre. A nem mdost algoritmusok (18.5) ugyangy mennek vgig az elemeken, de csak kiolvassk az informcikat. A mdost algoritmusok segtsgvel a leggyakoribb frisstsi feladatokat vgezhetjk el. Ezek egy rsze az eredeti sorozatot mdostja, mg msok j sorozatot hoznak ltre az ltalunk megadott sorozat alapjn. A szabvnyos algoritmusok a bejrk segtsgvel frnek hozz az adatszerkezetekhez. Ebbl kvetkezik, hogy egy j elem beszrsa egy trolba vagy egy elem trlse nem egyszer feladat. Pldul, ha csak egy bejr ll rendelkezsnkre, hogyan tallhatjuk meg azt a trolt, amelybl a kijellt elemet trlni kell? Hacsak nem hasznlunk egyedi bejrkat (pldul beszrkat, inserter, 3.8, 19.2.4), a bejrkon keresztl vgzett mveletek nem vltoztathatjk meg a trol mrett. Az elemek beszrsa s trlse helyett az algoritmusok csak az elemek rtkt vltoztatjk meg, illetve felcserlnek vagy msolnak elemeket. Mg a remove() is gy mkdik, hogy csak fellrja a trlni kvnt elemeket (18.6.5).

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

705

Az alapvet mdost algoritmusok ltalban egy mdostott msolatot hoznak ltre a megadott sorozatbl. Azok az algoritmusok, melyek sorozatmdostknak tnnek, valjban csak a msolk mdostott vltozatai.

18.6.1. Msols
A msols a legegyszerbb mdszer arra, hogy egy sorozatbl egy msikat lltsunk el. Az alapvet msol mveletek rendkvl egyszerek:
template<class In, class Out> Out copy(In first, In last, Out res) { while (first != last) *res++ = *first++; return res; } template<class Bi, class Bi2> Bi2 copy_backward(Bi first, Bi last, Bi2 res) { while (first != last) *--res = *--last; return res; }

A msol algoritmus kimenetnek nem kell felttlenl trolnak lennie. Brmit hasznlhatunk, amihez kimeneti bejr (19.2.6) megadhat:
void f(list<Club>& lc, ostream& os) { copy(lc.begin(),lc.end(),ostream_iterator<Club>(os)); }

Egy sorozat beolvasshoz meg kell adnunk, hogy hol kezddik s hol r vget. Az rshoz csak az az egy bejr kell, amelyik megmondja, hov rjunk. Arra azonban ilyenkor is figyelnnk kell, hogy ne rjunk a fogad trol hatrain tlra. A problma egyik lehetsges megoldsa a beszr (inserter) bejrk (19.2.4) hasznlata, mellyel a fogad adatszerkezetet bvthetjk, ha arra szksg van:
void f(vector<char>& vs) { vector<char> v; copy(vs.begin(),vs.end(),v.begin()); copy(vs.begin(),vs.end(),back_inserter(v)); // tlrhat v vgn // elemek hozzadsa vs-bl v vghez

Forrs: http://www.doksi.hu

706

A standard knyvtr

A bemeneti s a kimeneti sorozat tfedhetik egymst. Ha a sorozatok kztt nincs tfeds vagy a kimeneti sorozat vge a bemeneti sorozat belsejben van, akkor a copy() fggvnyt hasznljuk. A copy_backward() utastsra akkor van szksg, ha a kimeneti sorozat eleje van a bemeneti sorozatban. Ez a fggvny ilyen helyzetben addig nem rja fell az elemeket, amg azokrl msolat nem kszlt (lsd mg 18.13[13]). Termszetesen ahhoz, hogy valamit visszafel msoljunk le, egy ktirny bejrra (19.2.1) van szksgnk, mind a bemeneti, mind a kimeneti sorozatban:
void f(vector<char>& vc) { vector<char> v(vc.size()); copy_backward(vc.begin(),vc.end(),ostream_iterator<char>(cout)); copy_backward(vc.begin(),vc.end(),v.end()); copy(v.begin(),v.end(),ostream_iterator<char>(cout)); } // hiba // rendben // rendben

Gyakran olyan elemeket szeretnnk msolni, amelyek egy bizonyos felttelt teljestenek. Sajnos a copy_if() fggvny valahogy kimaradt a standard knyvtr ltal nyjtott algoritmusok sorbl (mea culpa). De ha szksgnk van r, pillanatok alatt megrhatjuk:
template<class In, class Out, class Pred> Out copy_if(In first, In last, Out res, Pred p) { while (first != last) { if (p(*first)) *res++ = *first; ++first; } return res; }

Ezutn ha az n rtknl nagyobb elemeket akarjuk megjelenteni, a kvetkez eljrst hasznlhatjuk:


void f(list<int>&ld, int n, ostream& os) { copy_if(ld.begin(),ld.end(),ostream_iterator<int>(os),bind2nd(greater<int>(),n)); }

(Lsd mg a remove_copy_if()lerst is a 18.6.5 pontban.)

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

707

18.6.2. Transzformcik
Egy kicsit flrevezet a neve, ugyanis a transform() nem felttlenl vltoztatja meg a bemenett. Ehelyett egy olyan kimenetet llt el, amely a bemeneten vgzett felhasznli mvelet eredmnye:
template<class In, class Out, class Op> Out transform(In first, In last, Out res, Op op) { while (first != last) *res++ = op(*first++); return res; } template<class In, class In2, class Out, class BinOp> Out transform(In first, In last, In2 first2, Out res, BinOp op) { while (first != last) *res++ = op(*first++,*first2++); return res; }

A transform() fggvny els vltozata, amely csak egy sorozatot olvas be, nagyon hasonlt az egyszer msolsra. A klnbsg mindssze annyi, hogy a kzvetlen kirs helyett elbb egy transzformcit (talaktst) is elvgez az elemen. gy a copy() eljrst a transform() fggvnnyel is meghatrozhattuk volna, gy, hogy az elemmdost mvelet egyszeren csak visszaadja a paramtert:
template<class T> T identity(const T& x) { return x; } template<class In, class Out> Out copy(In first, In last, Out res) { return transform(first,last,res,identity); }

Az identity explicit minstse ahhoz szksges, hogy a fggvnysablonbl konkrt fggvnyt kapjunk, az iterator_traits sablont (19.2.2) pedig azrt hasznltuk, hogy az In elemtpushoz jussunk. A transform() fggvnyt tekinthetjk a for_each() egy vltozatnak is, amely kzvetlenl lltja el kimenett. Pldul klubok listjbl a transform() segtsgvel kszthetnk egy olyan listt, amely csak a klubok neveit trolja:
string nameof(const Club& c) { return c.name; } // nv kinyerse

Forrs: http://www.doksi.hu

708

A standard knyvtr

void f(list<Club>& lc) { transform(lc.begin(),lc.end(),ostream_iterator<string>(cout),nameof); }

A transform() fggvny elnevezsnek egyik oka az, hogy az eredmnyt gyakran visszarjuk oda, ahonnan a paramtert kaptuk. Pldul ha olyan objektumokat akarunk trlni, melyekre mutatk hivatkoznak, a kvetkez eljrst hasznlhatjuk:
struct Delete_ptr { //fggvnyobjektum hasznlata helyben kifejtshez (inline fordtshoz) template<class T> T* operator() (T* p) { delete p; return 0; } }; void purge(deque<Shape*>& s) { transform(s.begin(),s.end(),s.begin(),Delete_ptr); }

A transform() algoritmus eredmnye mindig egy kimeneti sorozat. A fenti pldban az eredmnyt az eredeti bemeneti sorozatba irnytottuk vissza, gy a Delete_ptr()(p) jelentse p=Delete_ptr()(p) lesz. Ez indokolja a 0 visszatrsi rtket a Delete_ptr::operator()() fggvnyben. A transform() algoritmus msik vltozata kt bemeneti sorozattal dolgozik. Ez lehetv teszi, hogy kt sorozat adatait hasznljuk fel az j sorozat ltrehozshoz. Egy animcis programban pldul szksg lehet egy olyan eljrsra, amely alakzatok sorozatnak helyt frissti valamilyen talaktssal:
Shape* move_shape(Shape* s, Point p) // *s += p { s->move_to(s->center()+p); return s; } void update_positions(list<Shape*>& ls, vector<Point>& oper) { // mvelet vgrehajtsa a megfelel objektumon transform(ls.begin(),ls.end(),oper.begin(),ls.begin(),move_shape); }

Valjban nem lenne szksgnk arra, hogy a move_shape() fggvnynek visszatrsi rtke legyen, de a transform() ragaszkodik a mvelet eredmnynek felhasznlshoz, gy a move_shape() visszaadja az els operandust, amelyet visszarhatunk az eredeti helyre.

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

709

Bizonyos helyzetekben ezt a problmt nem oldhatjuk meg gy. Ha a mveletet pldul nem mi rtuk vagy nem akarjuk megvltoztatni, akkor nem ll rendelkezsnkre a megfelel visszatrsi rtk. Mskor a bemeneti sorozat const. Ezekben az esetekben egy ktsorozatos for_each() fggvnyt kszthetnk, amely a ktsorozatos transform() prjnak tekinthet:
template<class In, class In2, class BinOp> BinOp for_each(In first, In last, In2 first2, BinOp op) { while (first != last) op(*first++,*first2++); return op; } void update_positions(list<Shape*>& ls, vector<Point>& oper) { for_each(ls.begin(),ls.end(),oper.begin(),move_shape); }

Esetenknt olyan kimeneti bejr is hasznos lehet, amely valjban semmit sem r (19.6[2]). A standard knyvtr nem tartalmaz olyan algoritmusokat, melyek hrom vagy ngy sorozatbl olvasnak, br ezek is knnyen elkszthetk. Helyettk hasznlhatjuk tbbszr egyms utn a transform() fggvnyt.

18.6.3. Ismtld elemek trlse


Amikor informcikat gyjtnk, knnyen elfordulhatnak ismtldsek. A unique() s a unique_copy() algoritmusokkal az egyms utn elfordul azonos rtkeket tvolthatjuk el:
template<class For> For unique(For first, For last); template<class For, class BinPred> For unique(For first, For last, BinPred p); template<class In, class Out> Out unique_copy(In first, In last, Out res); template<class In, class Out, class BinPred> Out unique_copy(In first, In last, Out res, BinPred p);

A unique() megsznteti a sorozatban egyms utn elfordul rtkismtldseket, a unique_copy() pedig egy msolatot kszt az ismtldsek elhagysval:

Forrs: http://www.doksi.hu

710

A standard knyvtr

void f(list<string>& ls, vector<string>& vs) { ls.sort(); // listarendezs (17.2.2.1) unique_copy(ls.begin(),ls.end(),back_inserter(vs)); }

Ezzel a programrszlettel az ls listt tmsolhatjuk a vs vektorba s menet kzben kiszrhetjk az ismtldseket. A sort() utastsra azrt van szksg, hogy az egyenrtk elemek egyms mell kerljenek. A tbbi szabvnyos algoritmushoz hasonlan a unique() is bejrkkal dolgozik. Azt nem lehet megllaptani, hogy ezek a bejrk milyen tpus trolra mutatnak, gy a trolt nem vltoztathatjuk meg, csak az annak elemeiben trolt rtkeket mdosthatjuk. Ebbl kvetkezik, hogy a unique() nem trli az ismtldseket a sorozatbl, ahogy azt navan remlnnk. Ehelyett az egyedi elemeket a sorozat elejre helyezi s visszaad egy bejrt, amely az egyedi elemek rszsorozatnak vgre mutat:
template <class For> For unique(For first, For last) { first = adjacent_find(first,last); // 18.5.2 return unique_copy(first,last,first); }

A rszsorozat utni elemek vltozatlanok maradnak, teht egy vektor esetben ez a megolds nem sznteti meg az ismtldseket:
void f(vector<string>& vs) { sort(vs.begin(),vs.end()); unique(vs.begin(),vs.end()); } // vigyzat: rossz kd! // vektorrendezs // ismtldsek eltvoltsa (nem mkdik!)

St azzal, hogy a unique() a sorozat vgn ll elemeket elre helyezi az rtkismtldsek megszntetshez, akr j ismtldsek is keletkezhetnek:
int main() { char v[ ] = "abbcccde"; char* p = unique(v,v+strlen(v)); cout << v << ' ' << p-v << '\n';

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

711

Az eredmny a kvetkez lesz:


abcdecde 5

Teht a p a msodik c betre mutat. Azoknak az algoritmusoknak, melyeknek trlnie kellene elemeket (de ezt nem tudjk megtenni), ltalban kt vltozata van. Az egyik a uniqe() mdszervel trendezi az elemeket, a msik egy j sorozatot hoz ltre a unique_copy() mkdshez hasonlan. A _copy uttag ezen kt vltozat megklnbztetsre szolgl. Ahhoz hogy az ismtldseket tnylegesen kiszrjk egy trolbl, mg egy tovbbi utastsra van szksg:
template<class C> void eliminate_duplicates(C& c) { sort(c.begin(),c.end()); typename C::iterator p = unique(c.begin(),c.end()); c.erase(p,c.end()); }

// rendezs // tmrts // zsugorts

Sajnos az eliminate_duplicates() fggvny a beptett tmbkhz nem hasznlhat, mg a unique() azoknl is mkdik. A unique_copy() hasznlatra a 3.8.3. pontban mutattunk pldt.

18.6.3.1. Rendezsi szempont Az sszes ismtlds megszntetshez a bemeneti sorozatot rendeznnk kell (18.7.1). Alaprtelmezs szerint a unique() s a unique_copy() is az == opertort hasznlja az egyenlsgvizsglathoz, de lehetv teszik, hogy a programoz ms mveletet adjon meg. A 18.5.1 pontban szerepl programrszletet pldul talakthatjuk gy, hogy kiszrje az ismtld neveket. Miutn a klubok hivatalnokainak nevt sszegyjtttk, az off listhoz jutottunk, melynek tpusa list<Person*> (18.5.1). Ebbl a listbl az ismtldseket a kvetkez utastssal trlhetjk:
eliminate_duplicates(off);

Ez a megolds azonban mutatkat rendez, s csak akkor fog cljainknak megfelelen mkdni, ha felttelezzk, hogy minden Person objektumra egyetlen mutat hivatkozik.

Forrs: http://www.doksi.hu

712

A standard knyvtr

Az egyenrtksg eldntshez ltalban magukat a Person rekordokat kell megvizsglnunk. Ehhez a kvetkez programrszletet kell megrnunk:
bool operator==(const Person& x, const Person& y) { // x s y sszehasonltsa egyenlsgre } bool operator<(const Person& x, const Person& y) { // x s y sszehasonltsa rendezettsgre } bool Person_eq(const Person* x, const Person* y) { return *x == *y; } bool Person_lt(const Person* x, const Person* y) { return *x < *y; } // "egyenlsg" objektumra

// "kisebb mint" objektumra

// "egyenlsg" mutatn keresztl

// "kisebb mint" mutatn keresztl

void extract_and_print(const list<Club>& lc) { list<Person*> off; extract(lc,off); off.sort(off,Person_lt); list<Club>::iterator p = unique(off.begin(),off.end(),Person_eq); for_each(off.begin(),p,Print_name(cout)); }

rdemes mindig gondoskodnunk arrl, hogy a rendezshez hasznlt felttel ugyanaz legyen, mint az ismtldsek kiszrsre szolgl eljrs. A < s az == opertorok alaprtelmezett jelentse mutatk esetben ltalban nem felel meg szmunkra a mutatott objektumok sszehasonltshoz.

18.6.4. Helyettests
A replace() algoritmusok vgighaladnak a megadott sorozaton, s egyes rtkeket jakra cserlnek, a mi ignyeinknek megfelelen. Ugyanazt a mintt kvetik, mint a find/find_if s a unique/unique_copy, gy sszesen ngy vltozatra van szksg. A fggvnyek megvalstsa most is elg egyszer ahhoz, hogy jl magyarzza szerepket:

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

713

template<class For, class T> void replace(For first, For last, const T& val, const T& new_val) { while (first != last) { if (*first == val) *first = new_val; ++first; } } template<class For, class Pred, class T> void replace_if(For first, For last, Pred p, const T& new_val) { while (first != last) { if (p(*first)) *first = new_val; ++first; } } template<class In, class Out, class T> Out replace_copy(In first, In last, Out res, const T& val, const T& new_val) { while (first != last) { *res++ = (*first == val) ? new_val : *first; ++first; } return res; } template<class In, class Out, class Pred, class T> Out replace_copy_if(In first, In last, Out res, Pred p, const T& new_val) { while (first != last) { *res++ = p(*first) ? new_val : *first; ++first; } return res; }

Srn elfordul, hogy karakterlncok egy listjn vgighaladva szlvrosom szoksos angol trst (Aarhus) a helyes rhus vltozatra kell cserlnem:
void f(list<string>& towns) { replace(towns.begin(),towns.end(),"Aarhus","rhus"); }

Termszetesen ehhez egy kibvtett karakterkszletre van szksg (C.3.3)

Forrs: http://www.doksi.hu

714

A standard knyvtr

18.6.5. Trls
A remove() algoritmusok elemeket trlnek egy sorozatbl, rtk vagy felttel alapjn:
template<class For, class T> For remove(For first, For last, const T& val); template<class For, class Pred> For remove_if(For first, For last, Pred p); template<class In, class Out, class T> Out remove_copy(In first, In last, Out res, const T& val); template<class In, class Out, class Pred> Out remove_copy_if(In first, In last, Out res, Pred p);

Tegyk fel pldul, hogy a Club osztlyban szerepel egy cm mez is s feladatunk az, hogy sszegyjtsk a koppenhgai klubokat:
class located_in : public unary_function<Club,bool> { string town; public: located_in(const string& ss) :town(ss) { } bool operator()(const Club& c) const { return c.town == town; } }; void f(list<Club>& lc) { remove_copy_if(lc.begin(),lc.end(), ostream_iterator<Club>(cout),not1(located_in("Kbenhavn"))); }

A remove_copy_if() ugyanaz, mint a copy_if(), csak fordtott felttellel. Teht a remove_copy_if() akkor helyez egy elemet a kimenetre, ha az nem elgti ki a felttelt. A sima remove()a sorozat elejre gyjti a nem trlend elemeket s az gy kpzett rszsorozat vgre mutat bejrt ad vissza (lsd mg 18.6.3).

18.6.6. Feltlts s ltrehozs


A fill() s a generate() algoritmusok segtsgvel rendszerezetten tlthetnk fel egy sorozatot rtkekkel:
template<class For, class T> void fill(For first, For last, const T& val); template<class Out, class Size, class T> void fill_n(Out res, Size n, const T& val);

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

715

template<class For, class Gen> void generate(For first, For last, Gen g); template<class Out, class Size, class Gen> void generate_n(Out res, Size n, Gen g);

A fill() fggvny megadott rtkeket ad a sorozat elemeinek, mg a generate() algoritmus gy lltja el az rtkeket, hogy mindig meghvja a megadott fggvnyt. Teht a fill() a generate() egyedi vltozata, amikor az rtkeket elllt (ltrehoz, genertor) fggvny mindig ugyanazt az rtket adja vissza. Az _n vltozatok a sorozat els n elemnek adnak rtket. A 22.7 pont Randint s Urand vletlenszm-ellltjnak felhasznlsval pldul a kvetkezt rhatjuk:
int v1[900]; int v2[900]; vector v3; void f() { fill(v1,&v1[900],99); generate(v2,&v2[900],Randint());

// v1 minden elemnek 99-re lltsa // vletlen rtkekre llts (22.7)

// 200 vletlen egsz kldse a kimenetre a [0..99] tartomnybl generate_n(ostream_iterator<int>(cout),200,Urand(100)); } fill_n(back_inserter(v3),20,99); // 20 darab 99 rtk elem hozzadsa v3-hoz

A generate() s a fill() nem kezdeti, hanem egyszer rtkadst vgez. Ha nyers trolterletet akarunk felhasznlni (pldul egy memriaszeletet meghatrozott tpus s llapot objektumm szeretnnk alaktani), hasznlhatjuk pldul az uninitialized_fill() fggvnyt, a <memory> fejllomnybl (19.4.4). Az <algorithm> llomny algoritmusai ilyen clokra nem felelnek meg.

18.6.7. Megfordts s elforgats


Idnknt szksgnk van r, hogy egy sorozat elemeit trendezzk:
template<class Bi> void reverse(Bi first, Bi last); template<class Bi, class Out> Out reverse_copy(Bi first, Bi last, Out res); template<class For> void rotate(For first, For middle, For last); template<class For, class Out> Out rotate_copy(For first, For middle, For last, Out res);

Forrs: http://www.doksi.hu

716

A standard knyvtr

template<class Ran> void random_shuffle(Ran first, Ran last); template<class Ran, class Gen> void random_shuffle(Ran first, Ran last, Gen& g);

A reverse() algoritmus megfordtja az elemek sorrendjt, teht az els elem lesz az utols stb. A reverse_copy() szoks szerint egy msolatban lltja er bemeneti sorozatnak megfordtst. A rotate() algoritmus a [first, last[ sorozatot krknt kezeli, s addig forgatja az elemeket, mg a korbbi middle elem a sorozat elejre nem kerl. Teht az az elem, amely eddig a first+i pozcin volt, a mvelet utn a first+ (i+ (lastmiddle)) %(last-first) helyre kerl. A % (modulus) opertor teszi a forgatst ciklikuss az egyszer balralptets helyett:
void f() { string v[ ] = { "Bka", "s","Barack" }; reverse(v,v+3); rotate(v,v+1,v+3); // Barack s Bka // s Bka Barack

A rotate_copy() egy msolatban lltja el bemeneti sorozatnak elforgatott vltozatt. Alaprtelmezs szerint a random_shuffle() megkeveri a paramterben megadott sorozatot egy egyenletes eloszls vletlenszmokat elllt eljrs segtsgvel. Teht az elemek sorozatnak egy permutcijt (lehetsges elemsorrendjt) vlasztja ki gy, hogy mindegyik permutcinak ugyanakkora eslye van. Ha ms eloszlst szeretnnk elrni vagy egyszeren csak jobb vletlenszm-ellltnk van, akkor azt megadhatjuk a random_shuffle() fggvnynek. A 22.7. pont Urand eljrsval pldul a kvetkezkppen keverhetjk meg egy krtyapakli lapjait:
void f(deque<Card>& dc, My_rand& r) { random_shuffle(dc.begin(),dc.end(),r); // ... }

Az elemek thelyezst a rotate() s ms fggvnyek a swap() fggvny (18.6.8) segtsgvel hajtjk vgre.

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

717

18.6.8. Elemek felcserlse


Ha brmi rdekeset szeretnnk vgrehajtani egy trol elemeivel, akkor mindig t kell helyeznnk azokat. Ezt az thelyezst legjobban teht a legegyszerbben s a leghatkonyabban a swap() fggvnnyel fejezhetjk ki:
template<class T> void swap(T& a, T& b) { T tmp = a; a = b; b = tmp; } template<class For, class For2> void iter_swap(For x, For2 y); template<class For, class For2> For2 swap_ranges(For first, For last, For2 first2) { while (first != last) iter_swap(first++, first2++); return first2; }

Ahhoz, hogy felcserljnk elemeket, egy ideiglenes trolra van szksgnk. Egyes esetekben lehetnek gyes trkkk, melyekkel ez elkerlhet, de az egyszersg s rthetsg rdekben rdemes elkerlni az ilyesmit. A swap() algoritmus rendelkezik egyedi cl vltozatokkal azokhoz a tpusokhoz, ahol erre szksg lehet (16.3.9, 13.5.2). Az iter_swap() algoritmus bejrkkal kijellt elemeket cserl fel, a swap_ranges() pedig kt bemeneti paramtere ltal meghatrozott tartomnya elemeit cserli fel.

18.7. Rendezett sorozatok


Miutn sszegyjtttk az adatokat, ltalban szeretnnk rendezni azokat. Ha rendezett sorozat ll rendelkezsnkre, sokkal tbb lehetsgnk lesz arra, hogy adatainkat knyelmesen kezeljk. Egy sorozat rendezshez valahogyan ssze kell hasonltanunk az elemeket. Ehhez egy ktparamter prediktumot (18.4.2) hasznlhatunk. Az alaprtelmezett sszehasonlt mvelet a less (18.4.2), amely viszont alaprtelmezs szerint a < opertort hasznlja.

Forrs: http://www.doksi.hu

718

A standard knyvtr

18.7.1. Rendezs
A sort() rendez algoritmusoknak kzvetlen elrs (vletlen elrs) bejrkra (19.2.1) van szksgk. Ebbl kvetkezik, hogy a vektorok (16.3) s az ahhoz hasonl szerkezetek esetben mkdnek a leghatkonyabban:
template<class Ran> void sort(Ran first, Ran last); template<class Ran, class Cmp> void sort(Ran first, Ran last, Cmp cmp); template<class Ran> void stable_sort(Ran first, Ran last); template<class Ran, class Cmp> void stable_sort(Ran first, Ran last, Cmp cmp);

A szabvnyos list (17.2.2) osztly nem biztost kzvetlen hozzfrs bejrkat, gy azokat megfelel listamveletekkel (17.2.2.1) kell rendeznnk. Az egyszer sort() eljrs elg hatkony tlagosan N*log(N) de a legrosszabb esetre vett hatkonysg elg rossz: O(N*N). Szerencsre a legrosszabb eset elg ritka. Ha a legrosszabb esetben is garantlt mkdsre vagy stabil rendezsre van szksgnk, hasznljuk a stable_sort() fggvnyt. Ennek hatkonysga N*log(N)*log(N), ami N*log(N) rtkre javul, ha a rendszerben elg memria ll rendelkezsnkre. A stable_sort() a sort() fggvnnyel ellenttben megtartja az egyenrtknek minstett elemek sorrendjt. Bizonyos helyzetekben a rendezett sorozatnak csak els nhny elemre van szksgnk. Ebben az esetben van rtelme annak, hogy a sorozatot csak olyan hosszon rendezzk, amilyenre ppen szksgnk van. Ezt nevezzk rszleges (parcilis) rendezsnek:
template<class Ran> void partial_sort(Ran first, Ran middle, Ran last); template<class Ran, class Cmp> void partial_sort(Ran first, Ran middle, Ran last, Cmp cmp); template<class In, class Ran> Ran partial_sort_copy(In first, In last, Ran first2, Ran last2); template<class In, class Ran, class Cmp> Ran partial_sort_copy(In first, In last, Ran first2, Ran last2, Cmp cmp);

A partial_sort() algoritmus alapvltozata a first s a middle kztti elemeket rendezi el. A partial_sort_copy() algoritmusok egy N elem sorozatot hoznak ltre, ahol N a bemeneti s kimeneti sorozat elemei szmnak minimuma. Ebbl kvetkezik, hogy meg kell adnunk az eredmnysorozat elejt s vgt is, hiszen ez hatrozza meg, hny elemet kell elrendezni:

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

719

class Compare_copies_sold { public: int operator()(const Book& b1, const Book& b2) const { return b1.copies_sold()>b2.copies_sold(); } // rendezs cskken sorrendben }; void f(const vector<Book>& sales) // a tz legjobban fogy knyv megkerse { vector<Book> bestsellers(10); partial_sort_copy(sales.begin(),sales.end(), bestsellers.begin(),bestsellers.end(),Compare_copies_sold()); copy(bestsellers.begin(),bestsellers.end(),ostream_iterator<Book>(cout,"\n")); }

Mivel a partial_sort_copy() kimenetnek egy vletlen elrs bejrnak kell lennie, nem rendezhetnk egy sorozatot egyenesen a cout adatfolyamra. Vgl nzzk azokat az algoritmusokat, melyekkel pontosan csak annyi elemet rendezhetnk el, amennyi az N-edik elem helynek megllaptshoz szksges. Ezek azonnal befejezik mkdsket, ha a kvnt elemet megtalltk:
template<class Ran> void nth_element(Ran first, Ran nth, Ran last); template<class Ran, class Cmp> void nth_element(Ran first, Ran nth, Ran last, Cmp cmp);

Ez a fggvny klnsen hasznos azoknak, akiknek kzprtkre, szzalkarnyra stb. van szksgk (mrnkknek, szociolgusoknak, tanroknak).

18.7.2. Binris keress


A sorban trtn (soros, szekvencilis) keress, amit a find() is vgez, szrnyen rossz hatsfok nagy sorozatok esetben, mgis ez a legjobb megolds, ha sem rendezs, sem hasts (17.6) nem ll rendelkezsnkre. Ha azonban rendezett sorozatban keresnk, akkor annak megllaptsra, hogy egy rtk szerepel-e a sorozatban, hasznlhatjuk a binris keresst:
template<class For, class T> bool binary_search(For first, For last, const T& val); template<class For, class T, class Cmp> bool binary_search(For first, For last, const T& value, Cmp cmp);

Forrs: http://www.doksi.hu

720

A standard knyvtr

Pldul:
void f(list<int>& c) { if (binary_search(c.begin(),c.end(),7)) { // ... } // ... }

// Van 7 a c-ben?

A binary_search() egy logikai rtket ad vissza, jelezvn, hogy az rtk szerepel-e a sorozatban. Ugyangy, mint a find() esetben, itt is gyakran tudni szeretnnk, hogy az adott rtkkel rendelkez elem hol tallhat. Egy sorozatban azonban tbb megfelel rtk is lehet, s neknk ltalban vagy az elsre, vagy az sszesre van szksgnk. Ezrt szerepelnek a standard knyvtrban azok az eljrsok, melyekkel az els (lower_bound()), az utols (upper_bound()) vagy az sszes (equal_range()) megfelel (egyenrtk) elemet kivlaszthatjuk:
template<class For, class T> For lower_bound(For first, For last, const T& val); template<class For, class T, class Cmp> For lower_bound(For first, For last, const T& val, Cmp cmp); template<class For, class T> For upper_bound(For first, For last, const T& val); template<class For, class T, class Cmp> For upper_bound(For first, For last, const T& val, Cmp cmp); template<class For, class T> pair<For, For> equal_range(For first, For last, const T& val); template<class For, class T, class Cmp> pair<For, For> equal_range(For first, For last, const T& val, Cmp cmp);

Ezek az algoritmusok a multimap (17.4.2) mveletei kz tartoznak. A lower_bound() fggvnyt gy kpzelhetjk el, mint a find(), illetve a find_if() gyors vltozatt rendezett sorozatokra:
void g(vector<int>& c) { typedef vector<int>::iterator VI; VI p = find(c.begin(),c.end(),7); VI q = lower_bound(c.begin(),c.end(),7); } // ... // valsznleg lass: O(N); c-t nem kell // rendezni // valsznleg gyors: O(log(N)); c-t // rendezni kell

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

721

Ha a lower_bound(first, last, k) nem tallja meg a k rtket, akkor egy olyan bejrt ad vissza, amely az els, k-nl nagyobb kulccsal rendelkez elemre mutat, vagy a last bejrt, ha nincs k-nl nagyobb rtk. Az upper_bound() s az equal_range() szintn ezt a hibajelzsi mdszert hasznlja. Ezekkel az algoritmusokkal meghatrozhatjuk, hogy hov kell az j elemet beszrnunk, ha a sorozat rendezettsgt nem akarjuk elrontani.

18.7.3. sszefsls
Ha van kt rendezett sorozatunk, akkor a merge() segtsgvel egy j rendezett sorozatot hozhatunk ltre bellk, az inplace_merge() fggvny felhasznlsval pedig egy sorozat kt rszt fslhetjk ssze:
template<class In, class In2, class Out> Out merge(In first, In last, In2 first2, In2 last2, Out res); template<class In, class In2, class Out, class Cmp> Out merge(In first, In last, In2 first2, In2 last2, Out res, Cmp cmp); template<class Bi> void inplace_merge(Bi first, Bi middle, Bi last); template<class Bi, class Cmp> void inplace_merge(Bi first, Bi middle, Bi last, Cmp cmp);

Ezek az sszefsl algoritmusok abban trnek el jelentsen a list osztly hasonl eljrsaitl (17.2.2.1), hogy nem trlik az elemeket a bemeneti sorozatokbl. Minden elemrl kln msolat kszl. Az egyenrtk elemek sorrendjrl azt mondhatjuk el, hogy az els sorozatban lv elemek mindig megelzik a msodik sorozat azonos elemeit. Az inplace_merge() algoritmus elssorban akkor hasznos, ha egy sorozatot tbb szempont szerint is rendezni akarunk. Kpzeljk el pldul halaknak egy vektort, amely fajok (hering, tkehal stb.) szerint rendezett. Ha a halak minden fajon bell sly szerint rendezettek, akkor az inplace_merge() segtsgvel knnyen rendezhetjk az egsz vektort a slyok alapjn, hiszen csak az egyes fajtk rszsorozatait kell sszefslnnk (18.13[20]).

18.7.4. Feloszts
Egy sorozat felosztsa (partition) azt jelenti, hogy minden olyan elemet, amely kielgt egy adott felttelt, a sorozat elejre helyeznk, a felttelt ki nem elgtket pedig a vgre. A standard knyvtrban rendelkezsnkre ll a stable_partition() fggvny, amely megtartja azon elemek egymshoz viszonytott sorrendjt, melyek egyformn megfelelnek vagy

Forrs: http://www.doksi.hu

722

A standard knyvtr

egyformn nem felelnek meg a prediktumnak. A knyvtrban szerepel a partition() eljrs is, amely ezt a sorrendet nem rzi meg, de egy kicsit gyorsabban fut le, ha kevesebb memria ll rendelkezsnkre.
template<class Bi, class Pred> Bi partition(Bi first, Bi last, Pred p); template<class Bi, class Pred> Bi stable_partition(Bi first, Bi last, Pred p);

A felosztst gy kpzelhetjk el, mint egy nagyon egyszer felttel szerinti rendezst:
void f(list<Club>& lc) { list<Club>::iterator p = partition(lc.begin(),lc.end(),located_in("Kbenhavn")); // ... }

Ez az eljrs gy rendezi a listt, hogy a koppenhgai klubok szerepeljenek elszr. A visszatrsi rtk (esetnkben a p) az els olyan elemet hatrozza meg, amely nem elgti ki a felttelt, illetve ha ilyen nincs, akkor a sorozat vgre mutat.

18.7.5. Halmazmveletek sorozatokon


A sorozatokat tekinthetjk halmaznak is. Ebbl a szempontbl viszont illik a sorozatokhoz is elksztennk az olyan alapvet halmazmveleteket, mint az uni vagy a metszet. Msrszt viszont ezek a mveletek rendkvl kltsgesek, hacsak nem rendezett sorozatokkal dolgozunk. Ezrt a standard knyvtr halmazkezel algoritmusai csak rendezett sorozatokkal hasznlhatk. Termszetesen nagyon jl mkdnek a set (17.4.3) s a multiset (17.4.4) trol esetben is, melyek szintn rendezettek. Ha ezeket az algoritmusokat nem rendezett sorozatokra alkalmazzuk, az eredmnysorozatok nem fognak megfelelni a szoksos halmazelmleti szablyoknak. A fggvnyek nem vltoztatjk meg bemeneti sorozataikat s a kimeneti sorozat rendezett lesz. Az includes() algoritmus azt vizsglja, hogy a msodik sorozat minden eleme (a [first2,last2[ tartomnybl) megtallhat-e az els sorozat elemei kztt (a [first, last[ tartomnyban):
template<class In, class In2> bool includes(In first, In last, In2 first2, In2 last2); template<class In, class In2, class Cmp> bool includes(In first, In last, In2 first2, In2 last2, Cmp cmp);

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

723

A set_union() rendezett sorozatok unijt, a set_intersection() fggvny pedig metszetket lltja el:
template<class In, class In2, class Out> Out set_union(In first, In last, In2 first2, In2 last2, Out res); template<class In, class In2, class Out, class Cmp> Out set_union(In first, In last, In2 first2, In2 last2, Out res, Cmp cmp); template<class In, class In2, class Out> Out set_intersection(In first, In last, In2 first2, In2 last2, Out res); template<class In, class In2, class Out, class Cmp> Out set_intersection(In first, In last, In2 first2, In2 last2, Out res, Cmp cmp);

A set_difference() algoritmus olyan elemek sorozatt hozza ltre, melyek az els bemeneti sorozatban megtallhatk, de a msodikban nem. A set_symmetric_difference() algoritmus olyan sorozatot llt el, melynek elemei a kt bemeneti sorozat kzl csak az egyikben szerepelnek:
template<class In, class In2, class Out> Out set_difference(In first, In last, In2 first2, In2 last2, Out res); template<class In, class In2, class Out, class Cmp> Out set_difference(In first, In last, In2 first2, In2 last2, Out res, Cmp cmp); template<class In, class In2, class Out> Out set_symmetric_difference(In first, In last, In2 first2, In2 last2, Out res); template<class In, class In2, class Out, class Cmp> Out set_symmetric_difference(In first, In last, In2 first2, In2 last2, Out res, Cmp cmp);

Pldul:
char v1[ ] = "abcd"; char v2[ ] = "cdef"; void f(char v3[ ]) { set_difference(v1,v1+4,v2,v2+4,v3); set_symmetric_difference(v1,v1+4,v2,v2+4,v3); }

// v3 = "ab" // v3 = "abef"

Forrs: http://www.doksi.hu

724

A standard knyvtr

18.8. A kupac
A kupac (halom, heap) szt klnbz helyzetekben klnbz dolgokra hasznljuk a szmtstechnikban. Amikor algoritmusokrl beszlnk, a kupac egy sorozat elemeinek olyan elrendezst jelenti, melyben az els elem a sorozat legnagyobb rtk eleme. Ebben az adatszerkezetben viszonylag gyorsan lehet elvgezni az elemek beszrst (a push_heap() fggvny segtsgvel), illetve trlst (a pop_heap() eljrssal). Mindkett a legrosszabb esetben is O(log(N)) hatkonysggal mkdik, ahol N a sorozat elemeinek szma. A rendezs (a sort_heap() felhasznlsval) szintn j hatkonysg: O(N*log(N)). A kupacot ezek a fggvnyek valstjk meg:
template<class Ran> void push_heap(Ran first, Ran last); template<class Ran, class Cmp> void push_heap(Ran first, Ran last, Cmp cmp); template<class Ran> void pop_heap(Ran first, Ran last); template<class Ran, class Cmp> void pop_heap(Ran first, Ran last, Cmp cmp); template<class Ran> void make_heap(Ran first, Ran last); // sorozat kupacc // alaktsa template<class Ran, class Cmp> void make_heap(Ran first, Ran last, Cmp cmp); template<class Ran> void sort_heap(Ran first, Ran last); // kupac sorozatt // alaktsa template<class Ran, class Cmp> void sort_heap(Ran first, Ran last, Cmp cmp);

A kupac-algoritmusok stlusa egy kiss meglep. Termszetes megolds lenne, hogy ezt a ngy fggvnyt egy osztlyba foglaljuk ssze. Ha ezt tennnk, a priority_queue (17.3.3) trolhoz nagyon hasonl szerkezetet kapnnk. Valjban a priority_queue-t szinte majdnem biztosan egy kupac kpviseli rendszernkben. A push_heap(first, last) ltal beillesztett elem a *(last-1). Azt felttelezzk, hogy a [first, last1[ tartomny mr kupac, gy a push_heap() csak kiegszti a sorozatot a [first, last[ tartomnyra a kvetkez elem beszrsval. Teht egy kupacot ltrehozhatunk egy sorozatbl gy, hogy minden elemre meghvjuk a push_heap() mveletet. A pop_heap(first, last) gy tvoltja el a kupac els elemt, hogy felcserli azt a (*(last-1)) elemmel, majd a [first, last1[ tartomnyt jra kupacc alaktja.

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

725

18.9. Minimum s maximum


Az itt lert algoritmusok sszehasonlts alapjn vlasztanak ki rtkeket egy sorozatbl. Nagyon sokszor van szksgnk arra, hogy kt rtk kzl kivlasszuk a kisebbet vagy nagyobbat:
template<class T> const T& max(const T& a, const T& b) { return (a<b) ? b : a; } template<class T, class Cmp> const T& max(const T& a, const T& b, Cmp cmp) { return (cmp(a,b)) ? b : a; } template<class T> const T& min(const T& a, const T& b); template<class T, class Cmp> const T& min(const T& a, const T& b, Cmp cmp);

A min() s a max() fggvny ltalnosthat gy, hogy teljes sorozatokbl vlasszk ki a megfelel rtket:
template<class For> For max_element(For first, For last); template<class For, class Cmp> For max_element(For first, For last, Cmp cmp); template<class For> For min_element(For first, For last); template<class For, class Cmp> For min_element(For first, For last, Cmp cmp);

Vgl az bcsorrend (lexikografikus) rendezs ltalnostst is bemutatjuk, amely karakterlncok helyett tetszleges rtksorozatra ad meg sszehasonltsi felttelt:
template<class In, class In2> bool lexicographical_compare(In first, In last, In2 first2, In2 last2); template<class In, class In2, class Cmp> bool lexicographical_compare(In first, In last, In2 first2, In2 last2, Cmp cmp) { while (first != last && first2 != last2) { if (cmp(*first,*first2)) return true; if (cmp(*first2++,*first++)) return false; } return first == last && first2 != last2; }

Forrs: http://www.doksi.hu

726

A standard knyvtr

Ez nagyon hasonlt az ltalnos karakterlncoknl (13.4.1) bemutatott fggvnyre, de a lexicographical_compare() tetszleges kt sorozatot kpes sszehasonltani, nem csak karakterlncokat. Egy msik klnbsg, hogy ez az algoritmus csak egy bool s nem egy int rtket ad vissza, br az tbb informcit tartalmazhatna. Az eredmny kizrlag akkor lesz true, ha az els sorozat kisebb (<), mint a msodik. Teht akkor is false visszatrsi rtket kapunk, ha a kt sorozat egyenl. A C stlus karakterlncok s a string osztly is sorozat, gy a lexicographical_compare() hasznlhat ezekre is sszehasonlt felttelknt:
char v1[ ] = "igen"; char v2[ ] = "nem"; string s1 = "Igen"; string s2 = "Nem"; void f() { bool b1 = lexicographical_compare(v1,v1+strlen(v1),v2,v2+strlen(v2)); bool b2 = lexicographical_compare(s1.begin(),s1.end(),s2.begin(),s2.end()); bool b3 = lexicographical_compare(v1,v1+strlen(v1),s1.begin(),s1.end()); bool b4 = lexicographical_compare(s1.begin(),s1.end(),v1,v1+strlen(v1),Nocase());

A sorozatoknak nem kell azonos tpusaknak lennik, hiszen csak elemeket kell tudnunk sszehasonltani, s az sszehasonlt prediktumot mi adhatjuk meg. Ez a lehetsg a lexicographical_compare() eljrst sokkal ltalnosabb s egy kicsit lassabb teszi a string osztly sszehasonlt mveleteinl. Lsd mg: 20.3.8.

18.10. Permutcik
Egy ngy elembl ll sorozatot sszesen 4*3*2 flekppen rendezhetnk el. Mindegyik elrendezs egy-egy permutcija a ngy elemnek. Ngy karakterbl (abcd) pldul az albbi 24 permutcit llthatjuk el:
abcd bcad cdab abdc bcda cdba acbd bdac dabc acdb bdca dacb adbc cabd dbac adcb cadb dbca bacd cbad dcab badc cbda dcba

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

727

A next_permutation() s a prev_permutation() fggvny ilyen permutcikat llt el egy sorozatbl:


template<class Bi> bool next_permutation(Bi first, Bi last); template<class Bi, class Cmp> bool next_permutation(Bi first, Bi last, Cmp cmp); template<class Bi> bool prev_permutation(Bi first, Bi last); template<class Bi, class Cmp> bool prev_permutation(Bi first, Bi last, Cmp cmp);

Az abcd sorozat permutciit a kvetkezkppen rathatjuk ki:


int main() { char v[ ] = "abcd"; cout << v << '\t'; while(next_permutation(v,v+4)) cout << v << '\t'; }

A permutcikat bcsorrendben kapjuk meg (18.9). A next_permutation() fggvny visszatrsi rtke azt hatrozza meg, hogy van-e mg tovbbi permutci. Ha nincs, false rtket kapunk s azt a permutcit, melyben az elemek bcsorrendben llnak. A prev_permutation() fggvny visszatrsi rtke azt adja meg, hogy van-e elz permutci, s ha nincs, akkor az elemeket fordtott bcsorrendben tartalmaz permutcit kapjuk.

18.11. C stlus algoritmusok


A C++ standard knyvtra rklt nhny algoritmust a C standard knyvtrtl is. Ezek a C stlus karakterlncokat kezel fggvnyek (20.4.1), illetve a gyorsrendezs (quicksort) s a binris keress, melyek csak tmbkre hasznlhatk. A qsort() s a bsearch() fggvny a <cstdlib> s az <stdlib.h> fejllomnyban tallhat meg. Mindkett tmbkn mkdik s n darab elem_size mret elemet dolgoznak fel egy fggvnyre hivatkoz mutatval megadott kisebb, mint sszehasonlt mvelet segtsgvel. Az elemek tpusnak nem lehet felhasznl ltal megadott msol konstruktora, msol rtkadsa, illetve destruktora:

Forrs: http://www.doksi.hu

728

A standard knyvtr

typedef int(*__cmp)(const void*, const void*); // a typedef-et csak a bemutatshoz // hasznljuk void qsort(void* p, size_t n, size_t elem_size, __cmp); void* bsearch(const void* key, void* p, size_t n, size_t elem_size, __cmp); // p rendezse // kulcs keresse // p-ben

A qsort() fggvny hasznlatrl a 7.7 pontban mr rszletesebben is sz volt. Ezek az algoritmusok kizrlag a C-vel val kompatibilits miatt maradtak meg a nyelvben. A sort() (18.7.1) s a search() (18.5.5) sokkal ltalnosabbak s ltalban sokkal hatkonyabbak is.

18.12. Tancsok
[1] Ciklusok helyett hasznljunk inkbb algoritmusokat (18.5.1). [2] Ha ciklust runk, mindig gondoljuk vgig, hogy feladatunk nem fogalmazhat-e meg egy ltalnos algoritmussal (18.2). [3] Rendszeresen nzzk t az algoritmusokat, hogy felismerjk, ha valamilyen feladat trivilis (18.2). [4] Mindig ellenrizzk, hogy az ltalunk megadott bejr-pr tnyleg meghatroz-e egy sorozatot (18.3.1). [5] Tervezzk gy programjainkat, hogy a leggyakrabban hasznlt eljrsok legyenek a legegyszerbbek s leggyorsabbak (18.3, 18.3.1). [6] Az rtkvizsglatokat gy fogalmazzuk meg, hogy prediktumknt is hasznlhassuk azokat (18.4.2). [7] Mindig gondoljunk arra, hogy a prediktumok fggvnyek vagy objektumok, de semmikppen sem tpusok (18.4.2). [8] A lektk (binder) segtsgvel a ktparamter prediktumokbl egyparamter prediktumokat llthatunk el (18.4.4.1). [9] A mem_fun() s a mem_fun_ref() fggvny segt abban, hogy az algoritmusokat trolkra alkalmazzuk (18.4.4.2). [10] Ha egy fggvny valamelyik paramtert rgztennk kell, hasznljuk a ptr_fun() fggvnyt. [11] A strcmp() legnagyobb eltrse az == opertortl, hogy 0 visszatrsi rtk jelzi az egyenlsget (18.4.4.4).

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

729

[12] A for_each() s a transform() algoritmust csak akkor hasznljuk, ha nem tallunk feladatunkhoz jobban illeszked eljrst (18.5.1). [13] Hasznljunk prediktumokat, ha egy algoritmusban egyedi sszehasonltsi felttelre van szksgnk (184.2.1, 18.6.3.1). [14] A prediktumok s ms fggvnyobjektumok segtsgvel a szabvnyos algoritmusok sokkal tbb terleten hasznlhatk, mint els rnzsre gondolnnk (18.4.2). [15] A mutatk esetben az alaprtelmezett == s < opertor igen ritkn felel meg ignyeinknek a szabvnyos algoritmusokban (18.6.3.1). [16] Az algoritmusok nem tudnak kzvetlenl beilleszteni vagy kivenni elemet a paramterknt megadott sorozatokbl. [17] Mindig ellenrizzk, hogy a sorozatok sszehasonltsakor a megfelel kisebb, mint, illetve egyenlsg mveletet hasznljuk-e (18.6.3.1). [18] A rendezett sorozatok gyakran hatkonyabb s elegnsabb teszik programjainkat. [19] A qsort() s a bsearch() fggvnyeket csak a C programokkal val sszeegyeztethetsg biztostsa rdekben hasznljuk (18.11).

18.13. Gyakorlatok
A fejezetben elfordul gyakorlatok tbbsgnek megoldst megtallhatjuk a standard knyvtr brmely vltozatnak forrskdjban, de mieltt megnznnk, hogy a knyvtr alkoti hogyan kzeltettk meg a problmt, prblkozzunk sajt megoldsok kidolgozsval. 1. (*2)Ismerkedjnk az O( ) jellssel. Adjunk megvalsthat pldt arra, amikor N>10 rtkre egy O(N*N) algoritmus gyorsabb, mint egy O(N) algoritmus. 2. (*2)Ksztsk el s ellenrizzk a ngy mem_fun(), illetve mem_fun_ref() fggvnyt (18.4.4.2) 3. (*1)rjuk meg a match() algoritmust, amely hasonlt a mismatch() fggvnyre, csak ppen azoknak az elemeknek a bejrjt adja vissza, amelyek elsknt elgtik ki a prediktumot. 4. (*1.5)rjuk meg s prbljuk ki a 18.5.1 pontban emltett Print_name() fggvnyt. 5. (*1)Rendezznk egy listt a standard knyvtr algoritmusaival. 6. (*2.5)rjuk meg az iseq() fggvnynek (18.3.1) azon vltozatait, amelyek beptett tmbkre, istream tpusokra, illetve bejr-prokra hasznlhatk. Adjuk meg a szksges tlterhelseket is a standard knyvtr nem mdost algorit-

Forrs: http://www.doksi.hu

730

A standard knyvtr

musainak (18.5) szmra. Gondolkodjunk el rajta, hogyan kerlhetek el a tbbrtelmsgek, s hogyan kerlhetjk el tl nagy szm sablon fggvny ltrehozst. 7. (*2)Hatrozzuk meg az iseq() komplementert, az oseq() fggvnyt. A kimeneti sorozatot, amelyet az oseq()paramterknt kap, t kell alaktanunk az algoritmus ltal visszaadott kimenetre. Adjuk meg a szksges tlterhelseket is, legalbb hrom, ltalunk kivlasztott szabvnyos algoritmusra. 8. (*1.5)Ksztsnk egy vektort, amely 1-tl 100-ig trolja a szmok ngyzeteit. Jelentsk meg a ngyzetszmokat egy tblzatban. Szmtsuk ki ezen vektor elemeinek ngyzetgykt s jelentsk meg az gy kapott eredmnyeket is. 9. (*2)rjuk meg azokat a fggvnyobjektumokat, melyek bitenknti logikai mveleteket vgeznek operandusaikon. Prbljuk ki ezeket az objektumokat olyan vektorokon, melyek elemeinek tpusa char, int, illetve bitset<67>. 10. (*1)rjuk meg a binder3() eljrst, amely rgzti egy hromparamter fggvny msodik s harmadik paramtert, s gy llt el belle egyparamter prediktumot. Adjunk pldt olyan esetre, ahol a binder3() hasznos lehet. 11. (*1.5)rjunk egy rvid programot, amely amely eltvoltja egy fjlbl fjlbl a szomszdos szomszdos ismtld szavakat. (A programnak az elz mondatbl pldul el kell tvoltania az amely, a fjlbl s a szomszdos sz egy-egy elfordulst.) 12. (*2.5)Hozzunk ltre egy tpust, amellyel jsgokra s knyvekre mutat hivatkozsok rekordjait trolhatjuk egy fjlban. rjunk olyan programot, amely ki tudja rni a fjlbl azokat a rekordokat, amelyeknek megadjuk a kiadsi vt, a szerz nevt, egy, a cmben szerepl kulcsszavt, vagy a kiad nevt. Adjunk lehetsget arra is, hogy a felhasznl az eredmnyeket klnbz szempontok szerint rendezhesse. 13. (*2)Ksztsk el a move() algoritmust a copy() stlusban, gondolva arra, hogy a bemeneti s kimeneti sorozat esetleg tfedik egymst. Gondoskodjunk az eljrs megfelel hatkonysgrl arra az esetre, ha paramterekknt kzvetlen hozzfrs bejrkat kapnnk. 14. (*1.5)lltsuk el a food sz anagrammit, azaz az f, o, o s d betk ngybets kombinciit. ltalnostsuk ezt a programot gy, hogy egy felhasznltl bekrt sz anagrammit lltsa el. 15. (*1.5)Ksztsnk programot, amely mondatok anagrammit lltja el, azaz a mondat szavainak minden permutcijt elkszti. (Ne a szavak betinek permutciival foglalkozzunk!) 16. (*1.5)rjuk meg a find_if() (18.5.2) fggvnyt, majd ennek felhasznlsval a find() algoritmust. Talljunk ki olyan megoldst, hogy a kt fggvnynek ne kelljen klnbz nevet adnunk.

Forrs: http://www.doksi.hu

18. Algoritmusok s fggvnyobjektumok

731

17. (*2)Ksztsk el a search() (18.5.5) algoritmust. Ksztsnk egy kifejezetten kzvetlen hozzfrs bejrkhoz igaztott vltozatot is. 18. (*2)Vlasszunk egy rendezsi eljrst (pldul a qsort()-ot a standard knyvtrbl vagy a 13.5.2 pontbl a Shell rendezst) s alaktsuk t gy, hogy minden elemcsere utn jelenjen meg a sorozat aktulis llapota. 19. (*2)A ktirny bejrkhoz nem ll rendelkezsnkre rendez eljrs. Sejtsnk az, hogy gyorsabb az elemeket tmsolni egy vektorba, s ott rendezni azokat, mint ktirny bejrkkal kzvetlenl rendezni a sorozatot. Ksztsnk egy ltalnos rendez eljrst ktirny bejrkkal s ellenrizzk a felttelezst. 20. (*2.5)Kpzeljk el, hogy sporthorgszok egy csoportjnak rekordjait tartjuk nyilvn. Minden fogs esetben troljuk a hal fajtjt, hosszt, slyt, a fogs idpontjt, dtumt, a horgsz nevt stb. Rendezzk a rekordokat klnbz szempontok szerint az inplace_merge() algoritmus felhasznlsval. 21. (*2)Ksztsnk listt azokrl a tanulkrl, akik matematikt, angolt, szmtstechnikt vagy biolgit tanulnak. Mindegyik trgyhoz vlasszunk ki 20 nevet egy 40 fs osztlybl. Soroljuk fel azokat a tanulkat, akik matematikt s szmtstechnikt is tanulnak. Vlasszuk ki azokat, akik tanulnak szmtstechnikt, de matematikt s biolgit nem. rassuk ki azokat, akik tanulnak szmtstechnikt s matematikt, de nem tanulnak sem angolt, sem biolgit. 22. (*1.5)Ksztsnk egy remove() fggvnyt, amely tnyleg eltvoltja a trlend elemeket egy trolbl.

Forrs: http://www.doksi.hu

19
Bejrk s memriafoglalk
Annak oka, hogy az adatszerkezetek s az algoritmusok ilyen tkletesen egytt tudnak mkdni az, hogy semmit sem tudnak egymsrl. (Alex Stepanov) Bejrk s sorozatok Mveletek bejrkon Bejr-jellemzk Bejr-kategrik Beszrk Visszafel halad bejrk Adatfolyam-bejrk Ellenrztt bejrk A kivtelek s az algoritmusok Memriafoglalk A szabvnyos allocator Felhasznli memriafoglalk Alacsonyszint memriamveletek Tancsok Gyakorlatok

Forrs: http://www.doksi.hu

734

A standard knyvtr

19.1. Bevezets
A bejr (itertor, iterator) az a csavar, amely a trolkat s az algoritmusokat sszetartja. Ezek segtsgvel az adatokat elvont formban rhetjk el, gy az algoritmusok ksztinek nem kell rengeteg adatszerkezet konkrt rszleteivel foglalkozniuk. A msik oldalrl nzve pedig a bejrk ltal knlt szabvnyos adatelrsi modell megkmli a trolkat attl, hogy sokkal bvebb adatelrsi mvelethalmazt kelljen biztostaniuk. A memriafoglalk (alloktor, allocator) ugyangy szigetelik el a trolk konkrt megvalstsait a memriaelrs rszleteitl. A bejrk elvont adatmodellje az objektumsorozat (19.2). A memriafoglalk azt teszik lehetv, hogy az alacsonyszint bjttmb adatmodell helyett a sokkal magasabb szint objektummodellt hasznljuk (19.4) A leggyakoribb alacsonyszint memriamodell hasznlatt nhny szabvnyos fggvny tmogatja (19.4.4). A bejrk olyan fogalmat kpviselnek, amellyel bizonyra minden programoz tisztban van. Ezzel ellenttben a memriafoglalk olyan eljrsokat biztostanak, amelyekkel egy programoznak igen ritkn kell foglalkoznia, s igen kevs olyan programoz van, akinek letben akr csak egyszer is memriafoglalt kellene rnia.

19.2. Bejrk s sorozatok


A bejr (itertor, iterator) tisztn elvont fogalom. Azt mondhatjuk, hogy minden, ami bejrknt viselkedik, bejrnak tekinthet (3.8.2). A bejr a sorozat elemeire hivatkoz mutat fogalmnak elvont brzolsa. Legfontosabb fogalmai a kvetkezk: az ppen kijellt elem (indirekci, jele a * s a ->) a kvetkez elem kivlasztsa (nvels, jele a ++) egyenlsg (jele az ==) A beptett int* tpus pldul az int[ ]-nek, mg a list<int>::iterator egy listaosztlynak a bejrja.

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

735

A sorozat (sequence) is elvont fogalom: olyan valamit jell, amelyen vgighaladhatunk az elejtl a vgig, a kvetkez elem mvelettel: begin() elem[0] elem[1] elem[2] ... elem[n-1] end()

Ilyen sorozat a tmb (5.2), a vektor (16.3), az egyirny (lncolt) lista (17.8[17]), a ktirny (lncolt) lista (17.2.2), a fa (17.4.1), a bemeneti sorozat (21.3.1) s a kimeneti sorozat (21.2.1). Mindegyiknek megvan a sajt, megfelel bejrja. A bejr-osztlyok s -fggvnyek az std nvtrhez tartoznak s az <iterator> fejllomnyban tallhatk. A bejr nem ltalnostott mutat. Sokkal inkbb a tmbre alkalmazott mutat elvont brzolsa. Nincs olyan fogalom, hogy null-bejr (teht elemre nem mutat bejr). Ha meg akarjuk llaptani, hogy egy bejr ltez elemre mutat-e vagy sem, ltalban a sorozat end elemhez kell hasonltanunk (teht nem valamilyen null elemhez). Ez a szemllet sok algoritmust leegyszerst, mert nem kell az emltett egyedi esettel foglalkozniuk, radsul tkletesen megvalsthatk brmilyen tpus elemek sorozatra. Ha egy bejr ltez elemre mutat, akkor rvnyesnek (valid) nevezzk s alkalmazhat r az indirekci (a *, a [ ] vagy a -> opertor). Egy bejr rvnytelensgnek tbb oka is lehet: mg nem adtunk neki kezdrtket; olyan trolba mutat, amelynek mrete megvltozott (16.3.6., 16.3.8); teljes egszben trltk a trolt, amelybe mutat; vagy a sorozat vgre mutat (18.2). A sorozat vgt gy kpzelhetjk el, mint egy bejrt, amely egy olyan kpzeletbeli elemre mutat, melynek helye a sorozatban az utols elem utn van.

19.2.1. A bejrk mveletei


Nem minden bejr (iterator) engedi meg pontosan ugyanannak a mvelethalmaznak a hasznlatt. Az olvasshoz pldul msfajta mveletekre van szksg, mint az rshoz, a vektorok pedig lehetv teszik, hogy brmikor brmelyik elemet knyelmesen s hatkonyan elrhessk, mg a listknl s adatfolyamoknl ez a mvelet rendkvl kltsges. Ezrt a bejrkat t osztlyba soroljuk, aszerint, hogy milyen mveleteket kpesek hatkonyan (azaz lland (konstans) id alatt, 17.1) megvalstani:

Forrs: http://www.doksi.hu

736

A standard knyvtr

Bejr-mveletek s -kategrik Kategria: Rvidts: Olvass: Hozzfrs: rs: Halads: sszehasonlts: r olvas (kimeneti) (bemeneti) Out In =*p -> *p= ++ ++ == != elre halad For =*p -> *p= ++ == != ktirny kzvetlen elrs Bi Ran =*p =*p -> -> [ ] *p= *p= ++ -++ -- + - += -= == != == != < > <= >=

Az rs s az olvass is a * indirekci opertorral lekpezett bejrn keresztl trtnik:


*p = x; x = *p; // x rsa p-n keresztl // olvass p-n keresztl x-be

Ahhoz, hogy egy tpust bejrnak nevezznk, biztostania kell a megfelel mveleteket. Ezeknek a mveleteknek pedig a hagyomnyos jelents szerint kell viselkednik, teht ugyanazt az eredmnyt kell adniuk, mint a szoksos mutatknak. Egy bejrnak kategrijtl fggetlenl lehetv kell tennie const s nem konstans elrst is ahhoz az objektumhoz, amelyre mutat. Egy elemet nem vltoztathatunk meg egy const bejrn keresztl, legyen az brmilyen kategrij. A bejr knl bizonyos opertorokat, de a mutatott elem tpusa hatrozza meg, hogy mely mveleteket lehet tnylegesen vgrehajtani. Az olvass s az rs vgrehajtshoz az objektumok msolsra van szksg, gy ilyenkor az elemek tpusnak a szoksos msolst kell biztostaniuk (17.1.4). Csak kzvetlen elrs (random access) bejrkat nvelhetnk, illetve cskkenthetnk tetszleges egsz rtkkel a relatv cmzs megvalstshoz. A kimeneti (output) bejrk kivtelvel azonban kt bejr kztti tvolsg mindig megllapthat gy, hogy vgighaladunk a kzttk lv elemeken, gy a distance() fggvny megvalsthat:
template<class In> typename iterator_traits<In>::difference_type distance(In first, In last) { typename iterator_traits<In>::difference_type d = 0; while (first++!=last) d++; return d; }

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

737

Az iterator_traits<In>::difference_type minden bemeneti (input) bejrhoz rendelkezsre ll, hogy kt elem kztti tvolsgot trolhassunk (19.2.2). A fggvny neve distance() s nem operator-(), mert elg kltsges mvelet lehet s a bejrk esetben az opertorokat fenntartjuk azoknak az eljrsoknak, melyek konstans id alatt elvgezhetk (17.1). Az elemek egyesvel trtn megszmllsa nem olyan mvelet, amelyet nagy sorozatokon jhiszemen vgre szeretnnk hajtani. A kzvetlen elrs bejrkhoz a standard knyvtr a distance() fggvny sokkal hatkonyabb megvalstst biztostja. Hasonlan, az advance() fggvny szolgl a += opertor lass, de mindenhol alkalmazhat vltozatnak megnevezsre:
template <class In, class Dist> void advance(In& i, Dist n); // i+=n

19.2.2. A bejr jellemzi


A bejrkat arra hasznljuk, hogy informcikat tudjunk meg az objektumrl, amelyre mutatnak, illetve a sorozatrl, amelyhez kapcsoldnak. A bejr hivatkozsn keresztl kapott objektumot pldul talakthatjuk, vagy megllapthatjuk a sorozatban szerepl elemek szmt a sorozatot kijell bejrk segtsgvel. Az ilyen mveletek kifejezshez kpesnek kell lennnk megnevezni a bejrkhoz kapcsold tpusokat. Pldul azon objektum tpusa, melyre a bejr mutat, vagy kt bejr kztti tvolsg tpusa. Ezeket a tpusokat egy bejr szmra az iterator_traits (bejr jellemzk) sablon osztly rja le:
template<class Iter> struct iterator_traits { typedef typename Iter::iterator_category iterator_category; // 19.2.3 typedef typename Iter::value_type value_type; // az elem tpusa typedef typename Iter::difference_type difference_type; typedef typename Iter::pointer pointer; // a ->() opertor visszatrsi tpusa typedef typename Iter::reference reference; // a *() opertor visszatrsi tpusa };

A difference_type kt bejr kztti tvolsg megjellsre szolgl tpus, az iterator_category pedig olyan, amellyel lerhat, hogy a bejr milyen mveleteket tmogat. A szoksos mutatk esetben egy-egy egyedi cl vltozat (specializci, 13.5) ll rendelkezsnkre a <T*> s a <const T*> tpushoz:

Forrs: http://www.doksi.hu

738

A standard knyvtr

template<class T> struct iterator_traits<T*> { // specializci mutatkhoz typedef random_access_iterator_tag iterator_category; typedef T value_type; typedef ptrdiff_t difference_type; typedef T* pointer; typedef T& reference; };

Teht a mutatk kzvetlen elrst (vletlen elrst, random-access, 19.2.3) tesznek lehetv, s a kzttk lv tvolsgot a standard knyvtr ptrdiff_t tpusval brzolhatjuk, amelyet a <cstddef> (6.2.1) fejllomnyban tallhatunk meg. Az iterator_traits osztly segtsgvel olyan eljrsokat rhatunk, amelyek a bejrk paramtereinek jellemzitl fggnek. Erre klasszikus plda a count() fggvny:
template<class In, class T> typename iterator_traits<In>::difference_type count(In first, In last, const T& val) { typename iterator_traits<In>::difference_type res = 0; while (first != last) if (*first++ == val) ++res; return res; }

Itt az eredmny tpust az iterator_traits<In> osztlynak fogalmaival hatroztuk meg. Erre a megoldsra azrt van szksg, mert nincs olyan nyelvi elem, amellyel egy tetszleges tpust egy msik tpus fogalmaival fejezhetnnk ki. Mutatk esetben az iterator_traits osztly hasznlata helyett specializcit kszthetnnk a count() fggvnybl:
template<class In, class T> typename In::difference_type count(In first, In last, const T& val); template<class In, class T> ptrdiff_t count<T*,T>(T* first, T* last, const T& val);

Ez a megolds azonban csak a count() problmjn segtene. Ha a mdszert egy tucat algoritmushoz hasznltuk volna, a tvolsgok tpusnak informciit tucatszor kellene megismtelnnk. ltalban sokkal jobb megkzelts, ha egy tervezsi dntst egyszer, egy helyen runk le (23.4.2); gy ha ezt a dntst ksbb knytelenek vagyunk megvltoztatni, csak erre az egy helyre kell figyelnnk.

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

739

Mivel az itrator_traits<Iterator> minden bejr esetben definilt, j bejr ksztsekor mindig ltre kell hoznunk a httrben egy iterator_traits osztlyt is. Ha az alaprtelmezett osztly amely az iterator_traits sablon pldnya nem felel meg ignyeinknek, szabadon kszthetnk belle specializlt vltozatot, gy, ahogy a standard knyvtr teszi a mutatk esetben. Az automatikusan ltrehozott iterator_traits osztly felttelezi, hogy a bejr-osztlyban kifejtettk a difference_type, value_type stb. tagtpusokat. Az <iterator> fejllomnyban a knyvtr megad egy tpust, melyet felhasznlhatunk a tagtpusok ltrehozshoz:
template<class Cat, class T, class Dist = ptrdiff_t, class Ptr = T*, class Ref = T&> struct iterator { typedef Cat iterator_category; // 19.2.3 typedef T value_type; // az elem tpusa typedef Dist difference_type; // a bejr-klnbsg tpusa typedef Ptr pointer; // a -> visszatrsi tpusa typedef Ref reference; // a * visszatrsi tpusa };

Jegyezzk meg, hogy itt a reference s a pointer nem bejrk, csak bizonyos bejrk esetben egyszer visszatrsi rtkknt szolglnak az operator*(), illetve az operator->() mvelethez. Az iterator_traits osztly szmos olyan fellet elksztst leegyszersti, amely bejrkkal dolgozik, s sok algoritmus esetben hatkony megoldsokat tesz lehetv.

19.2.3. Bejr-kategrik
A bejrk klnbz fajti (kategrii) hierarchikus rend szerint csoportostottak:

Bemeneti (Input) Elre halad (Forward) Kimeneti (Output) Ktirny (Bidirectional) Kzvetlen lrs (Random-access)

Forrs: http://www.doksi.hu

740

A standard knyvtr

Itt nem osztlyok leszrmazsi hierarchijrl van sz. A bejrk kategrii csak egy csoportostst jelentenek a tpus ltal knlt mveletek alapjn. Nagyon sok, egymstl teljesen fggetlen tpus tartozhat ugyanahhoz a bejr-kategrihoz. A kznsges mutatk (19.2.2) vagy a Checked_iters (19.3) pldul egyarnt kzvetlen elrs bejrk. A 18. fejezetben mr felhvtuk r a figyelmet, hogy a klnbz algoritmusok klnbz fajtj bejrkat hasznlnak paramterknt. St, mg ugyanannak az algoritmusnak is lehet tbb vltozata, klnbz bejr-fajtkkal, hogy mindig a leghatkonyabb megoldst hasznlhassuk. Annak rdekben, hogy a bejr-kategrik alapjn knnyen tlterhelhessnk fggvnyeket, a standard knyvtr t osztlyt knl, amelyek az t bejr-kategrit brzoljk:
struct input_iterator_tag {}; struct output_iterator_tag {}; struct forward_iterator_tag : public input_iterator_tag {}; struct bidirectional_iterator_tag : public forward_iterator_tag {}; struct random_access_iterator_tag : public bidirectional_iterator_tag {};

Ha megnzzk a bemeneti vagy az elre halad bejrk utastskszlett (19.2.1), akkor azt vrhatnnk, hogy a forward_iterator_tag az output_iterator_tag osztly leszrmazottja legyen, st az input_iterator_tag- is. A knyvtrban mgsem ez a megolds szerepel, aminek az az oka, hogy ez tl zavaross s valsznleg hibss teheti a rendszert. Ennek ellenre mr lttam olyan megvalstst, ahol az ilyen szrmaztats tnylegesen leegyszerstette a programkdot. Ezen osztlyok egymsbl val szrmaztatsnak egyetlen elnye, hogy fggvnyeinknek nem kell tbb vltozatt elksztennk, ha ugyanazt az algoritmust szeretnnk alkalmazhatv tenni tbb, de nem az sszes bejrra. Pldul gondoljuk vgig, hogyan valsthat meg a distance:
template<class In> typename iterator_traits<In>::difference_type distance(In first, In last);

Kt nyilvnval lehetsgnk van: 1. Ha az In kzvetlen elrs bejr, egyszeren kivonhatjuk a first rtket a last rtkbl. 2. Ellenkez esetben a tvolsg megllaptshoz kln bejrval vgig kell haladnunk a sorozaton a first elemtl a last rtkig. Ezt a kt vltozatot kt segdfggvny hasznlatval fejezhetjk ki:

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

741

template<class In> typename iterator_traits<In>::difference_type dist_helper(In first, In last, input_iterator_tag) { typename iterator_traits<In>::difference_type d = 0; while (first++!=last) d++; // csak nvels return d; } template<class Ran> typename iterator_traits<Ran>::difference_type dist_helper(Ran first, Ran last, random_access_iterator_tag) { return last-first; // vletlen elrsre tmaszkodik }

A bejr kategrijt jelz paramter pontosan meghatrozza, milyen bejrra van szksg, s msra nem is hasznljuk a fggvnyben, csak arra, hogy a tlterhelt fggvny megfelel vltozatt kivlasszuk. A paramterre a szmtsok elvgzshez nincs szksg. Ez a megolds tisztn fordtsi idej vlasztst tesz lehetv, s amellett, hogy automatikusan kivlasztja a megfelel segdfggvnyt, mg tpusellenrzst is vgez (13.2.5). Ezutn a distance() fggvny megrsa mr nagyon egyszer, csak a megfelel segdfggvnyt kell meghvnunk:
template<class In> typename iterator_traits<In>::difference_type distance(In first, In last) { return dist_helper(first,last,iterator_traits<In>::iterator_category()); }

Egy dist_helper() fggvny meghvshoz a megadott iterator_traits<In>::iterator_category osztlynak vagy input_iterator_tag, vagy random_access-iterator_tag tpusnak kell lennie. Ennek ellenre nincs szksg kln dist_helper() fggvnyre az elre halad (forward) s a ktirny (bidirectional) bejrk esetben, mert a szrmaztatsnak ksznheten a dist_helper() fggvny azon vltozata, amely input_iterator_tag paramtert vr, ezeket is kezeli. Az output_iterator_tag tpust fogad vltozat hinya pedig pontosan azt fejezi ki, hogy a distance() fggvny nem rtelmezhet kimeneti (output) bejrkra:
void f(vector<int>& vi, list<double>& ld, istream_iterator<string>& is1, istream_iterator<string>& is2, ostream_iterator<char>& os1, ostream_iterator<char>& os2) {

Forrs: http://www.doksi.hu

742

A standard knyvtr

distance(vi.begin(),vi.end()); distance(ld.begin(),ld.end()); distance(is1,is2); distance(os1,os2); }

// kivon algoritmus hasznlata // nvel algoritmus hasznlata // nvel algoritmus hasznlata // hiba: rossz bejr-kategria, // a dist_helper() paramternek tpusa // nem megfelel

A distance() fggvny meghvsa egy istream_iterator osztlyra mkdik, de valsznleg teljesen rtelmetlen egy vals programban, hiszen vgigolvassuk vele a bemenetet, de minden elemet azonnal el is dobunk, s az eldobott elemek szmt adjuk vissza. Az iterator_traits<T>::iterator_category lehetv teszi, hogy a programoz olyan vltozatokat rjon egy algoritmushoz, melyek kzl mindig automatikusan a megfelel kerl vgrehajtsra, amikor a megvalstssal egyltaln nem foglalkoz felhasznl valamilyen adatszerkezettel meghvja a fggvnyt. Ms szval elrejthetjk a megvalsts rszleteit egy egysges fellet mgtt, a helyben kifejtett (inline) fggvnyek hasznlata pedig biztostja, hogy ez az elegancia nem megy a futsi idej hatkonysg rovsra.

19.2.4. Beszr bejrk


Ha egy trolba egy bejr segtsgvel rni akarunk, fel kell kszlnnk arra, hogy a bejr ltal kijellt elemtl kezdve a trol megvltozik. Ennek kvetkeztben tlcsorduls s gy hibs memriars is bekvetkezhet:
void f(vector<int>& vi) { fill_n(vi.begin(),200,7); }

// 7 rtkl adsa vi[0]..[199]-nek

Ha a vi trolnak 200-nl kevesebb eleme van, ez az eljrs nagy bajokat okozhat. Az <iterator> fejllomnyban a standard knyvtr ler hrom sablon osztlyt, amely ezzel a problmval foglalkozik, knyelmes hasznlatukhoz pedig hrom fggvny is rendelkezsnkre ll:
template <class Cont> back_insert_iterator<Cont> back_inserter(Cont& c); template <class Cont> front_insert_iterator<Cont> front_inserter(Cont& c); template <class Cont, class Out> insert_iterator<Cont> inserter(Cont& c, Out p);

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

743

A back_inserter() arra szolgl, hogy elemeket adjunk egy trol vghez, a front_inserter() segtsgvel a sorozat elejre helyezhetnk elemeket, mg az egyszer inserter() a paramterknt megadott bejr el szrja be az elemeket. ppen ezrt az inserter(c,p) utastsban a p-nek rvnyes bejrnak kell lennie a c trolban. Termszetesen a trol mrete mindig megn, amikor egy beszr bejr segtsgvel rtket runk bele. A beszr bejrk (inserter) nem rnak fell ltez elemeket, hanem jakat szrnak be a push_back(), a push_front(), illetve az insert() (16.3.6) utastssal:
void g(vector<int>& vi) { fill_n(back_inserter(vi),200,7); }

// 200 darab 7-es hozzadsa vi vghez

A beszr bejrk nem csak hasznosak, hanem egyszerek s hatkonyak is:


template <class Cont> class insert_iterator : public iterator<output_iterator_tag,void,void,void,void> { protected: Cont& container; // a cltrol, amelybe beszrunk typename Cont::iterator iter; // a trolba mutat public: explicit insert_iterator(Cont& x, typename Cont::iterator i) : container(x), iter(i) {} insert_iterator& operator=(const typename Cont::value_type& val) { iter = container.insert(iter,val); ++iter; return *this; } insert_iterator& operator*() { return *this; } insert_iterator& operator++() { return *this; } insert_iterator operator++(int) { return *this; } // prefix ++ // postfix ++

};

Teht a beszrk valjban kimeneti (output) bejrk. Az insert_iterator a kimeneti sorozatok klnleges esete. A 18.3.1 pontban bevezetett iseq fggvnyhez hasonlan megrhatjuk a kvetkezt:

Forrs: http://www.doksi.hu

744

A standard knyvtr

template<class Cont> insert_iterator<Cont> oseq(Cont& c, typename Cont::iterator first, typename Cont::iterator last) { return insert_iterator<Cont>(c,c.erase(first,last)); // az erase magyarzatt lsd 16.3.6 }

Teht a kimeneti sorozat trli a rgi elemeket, majd az aktulis eredmnnyel helyettesti azokat:
void f(list<int>& li,vector<int>& vi) // vi msodik felnek helyettestse li msolatval { copy(li.begin(),li.end(),oseq(vi,vi+vi.size()/2,vi.end())); }

Az oseq() fggvnynek t kell adnunk paramterknt magt a trolt is, mert csak bejrk segtsgvel nem lehet cskkenteni egy trol mrett (18.6., 18.6.3).

19.2.5. Visszafel halad bejrk


A szabvnyos trolkban megtallhat az rbegin() s az rend() fggvny, melyekkel fordtott sorrendben haladhatunk vgig az elemeken (16.3.2). Ezek az eljrsok reverse_iterator rtket adnak vissza:
template <class Iter> class reverse_iterator : public iterator<iterator_traits<Iter>::iterator_category, iterator_traits<Iter>::value_type, iterator_traits<Iter>::difference_type, iterator_traits<Iter>::pointer, iterator_traits<Iter>::reference> { protected: Iter current; // a current a *this ltal hivatkozott utni elemre mutat public: typedef Iter iterator_type; reverse_iterator() : current() { } explicit reverse_iterator(Iter x) : current(x) { } template<class U> reverse_iterator(const reverse_iterator<U>& x) : current(x.base()) { } Iter base() const { return current; } // a bejr aktulis rtke

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

745

reference operator*() const { Iter tmp = current; return *--tmp; } pointer operator->() const; reference operator[ ](difference_type n) const; reverse_iterator& operator++() { --current; return *this; } // vigyzzunk: nem ++ reverse_iterator operator++(int) { reverse_iterator t = current; --current; return t; } reverse_iterator& operator--() { ++current; return *this; } // vigyzzunk: nem -reverse_iterator operator--(int) { reverse_iterator t = current; ++current; return t; } reverse_iterator operator+(difference_type n) const; reverse_iterator& operator+=(difference_type n); reverse_iterator operator-(difference_type n) const; reverse_iterator& operator-=(difference_type n);

};

A reverse_iterator tpust egy iterator segtsgvel valstjuk meg, melynek neve current. Ez a bejr csak a megfelel sorozat elemeire vagy az utols utni elemre mutathat, de neknk a reverse_iterator utols utni eleme az eredeti sorozat els eltti eleme, amit nem rhetnk el. gy a tiltott hozzfrsek elkerlse rdekben a current valjban a reverse_iterator ltal kijellt elem utni rtkre mutat. Ezrt a * opertor a *(current-1) elemet adja vissza. Az osztly jellegnek megfelelen a ++ mveletet a current rtkre alkalmazott -- eljrs valstja meg. A reverse_iterator csak azokat a mveleteket teszi elrhetv, amelyeket a current bejr tmogat:
void f(vector<int>& v, list<char>& lst) { v.rbegin()[3] = 7; // rendben: kzvetlen elrs bejr lst.rbegin()[3] = '4'; // hiba: a ktirny bejrk nem tmogatjk // a [ ] hasznlatt *(++++++lst.rbegin()) = '4'; // rendben! }

Ezeken kvl a knyvtr a reverse_iterator tpushoz az ==, a !=, a <, a >, a <=, a >=, a + s a - mveleteket is tartalmazza.

19.2.6. Adatfolyamok bejri


Az adatok ki- s bevitelt hrom mdszer valamelyikvel valstjuk meg: vagy az adatfolyamok knyvtrval (21. fejezet), vagy a grafikus felhasznli fellet eszkzeivel (ezt a C++szabvny nem tartalmazza), vagy a C bemeneti/kimeneti (I/O) mveleteivel (21.8). Ezek

Forrs: http://www.doksi.hu

746

A standard knyvtr

a ki- s bemeneti eszkzk elssorban arra szolglnak, hogy klnbz tpus rtkeket egyesvel rjunk vagy olvassunk. A standard knyvtr ngy bejr-tpussal a ki- s bemeneti adatfolyamokat is beilleszti a trolk s az algoritmusok ltal krvonalazott egysges rendszerbe: 1. Az ostream_iterator segtsgvel egy ostream adatfolyamba rhatunk (3.4, 21.2.1). 2. Az istream_iterator segtsgvel egy istream adatfolyamba rhatunk (3.6, 21.3.1). 3. Az ostreambuf_iterator egy adatfolyam-puffer (tmeneti tr) rshoz hasznlhat (21.6.1). 4. Az istreambuf_iterator egy adatfolyam-puffer olvasshoz hasznlhat (21.6.2). Az tlet csak annyi, hogy a gyjtemnyek ki- s bevitelt sorozatokknt jelentjk meg:
template <class T, class Ch = char, class Tr = char_traits<Ch> > class ostream_iterator : public iterator<output_iterator_tag,void,void,void,void> { public: typedef Ch char_type; typedef Tr traits_type; typedef basic_ostream<Ch,Tr> ostream_type; ostream_iterator(ostream_type& s); ostream_iterator(ostream_type& s, const Ch* delim); ostream_iterator(const ostream_iterator&); ~ostream_iterator(); ostream_iterator& operator=(const T& val); ostream_iterator& operator*(); ostream_iterator& operator++(); ostream_iterator& operator++(int); // delim rsa minden kimeneti // rtk utn

// val rsa a kimenetre

};

Ez a bejr a kimeneti bejrk szoksos rs s tovbblps mvelett gy hajtja vgre, hogy az ostream megfelel mveleteiv alaktja azokat:
void f() { ostream_iterator<int> os(cout); // int-ek rsa a cout-ra os-en keresztl *os = 7; // a kimenet 7 ++os; // felkszls a kvetkez kimenetre *os = 79; // a kimenet 79 }

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

747

A ++ opertor tnyleges kimeneti mveletet is vgrehajthat, de elkpzelhet olyan megolds is, ahol semmilyen hatsa sincs. A klnbz nyelvi vltozatok klnbz mdszereket alkalmazhatnak, ezrt ha hordozhat programot akarunk kszteni, mindenkppen hasznljuk a ++ mveletet kt ostream_iterator-rtkads kztt. Termszetesen minden szabvnyos algoritmus gy kszlt, hiszen ellenkez esetben mr vektorra sem lennnek hasznlhatk, s ez az oka az ostream_iterator osztly ilyetn meghatrozsnak is. Az ostream_iterator elksztse nagyon egyszer, gy feladatknt is szerepel a fejezet vgn (19.6[4]). A szabvnyos ki- s bemenet tbbfle karaktertpust is tmogat. A char_traits osztly (20.2) egy karaktertpus azon jellemzit rja le, melyek fontosak lehetnek a string osztly vagy a ki- s bemenet szempontjbl. Az istream bemeneti bejrjt hasonlan hatrozhatjuk meg:
template <class T, class Ch = char, class Tr = char_traits<Ch>, class Dist = ptrdiff_t> class istream_iterator : public iterator<input_iterator_tag, T, Dist, const T*, const T&> { public: typedef Ch char_type; typedef Tr traits_type; typedef basic_istream<Ch,Tr> istream_type; istream_iterator(); istream_iterator(istream_type& s); istream_iterator(const istream_iterator&); ~istream_iterator(); const T& operator*() const; const T* operator->() const; istream_iterator& operator++(); istream_iterator operator++(int); // a bemenet vge

};

Ez a bejr is olyan formj, hogy knyelmesen olvashassunk be adatokat (a >> segtsgvel) a bemeneti adatfolyambl egy trolba:
void f() { istream_iterator<int> is(cin); int i1 = *is; ++is; int i2 = *is; }

// int-ek beolvassa a cin-rl az is-en keresztl // egy int beolvassa // felkszls a kvetkez bemenetre // egy int beolvassa

Forrs: http://www.doksi.hu

748

A standard knyvtr

Az alaprtelmezett istream_iterator a bemenet vgt brzolja, gy ennek felhasznlsval megadhatunk bemeneti sorozatot is:
void f(vector<int>& v) { copy(istream_iterator<int>(cin),istream_iterator<int>(),back_inserter(v)); }

Ahhoz, hogy ez az eljrs mkdjn, a standard knyvtr az istream_iterator osztlyhoz tartalmazza az == s a != mveletet is. A fentiekbl lthat, hogy az istream_iterator megvalstsa nem annyira egyszer, mint az ostream_iterator osztly, de azrt nem is tlsgosan bonyolult. Egy istream_iterator megrsa szintn szerepel feladatknt (19.6[5]).

19.2.6.1. tmeneti trak adatfolyamokhoz A 21.6 pontban rjuk le rszletesen, hogy az adatfolyam alap ki- s bemenet amelyet az istream s az ostream megvalst valjban egy tmeneti trral (pufferrel) tartja a kapcsolatot, amely az alacsonyszint, fizikai ki- s bemenetet kpviseli. Lehetsgnk van azonban arra, hogy a szabvnyos adatfolyamok formzsi mveleteit elkerljk, s kzvetlenl az tmeneti trakkal dolgozzunk (21.6.4). Ez a lehetsg a szabvnyos algoritmusok szmra is adott az istrembuf_iterator s az ostreambuf_iterator segtsgvel:
template<class Ch, class Tr = char_traits<Ch> > class istreambuf_iterator : public iterator<input_iterator_tag,Ch,typename Tr::off_type,Ch*,Ch&> { public: typedef Ch char_type; typedef Tr traits_type; typedef typename Tr::int_type int_type; typedef basic_streambuf<Ch,Tr> streambuf_type; typedef basic_istream<Ch,Tr> istream_type; class proxy; istreambuf_iterator() throw(); istreambuf_iterator(istream_type& is) throw(); istreambuf_iterator(streambuf_type*) throw(); istreambuf_iterator(const proxy& p) throw(); // segdtpus // tmeneti tr vge // olvass is streambuf-jbl // olvass p streambuf-jbl

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

749

Ch operator*() const; istreambuf_iterator& operator++(); proxy operator++(int);

// eltag // uttag

bool equal(istreambuf_iterator&); // mindkt streambuf-nak vge (eof) vagy egyiknek sem };

A fentieken kvl rendelkezsnkre ll az == s a != opertor is. Egy streambuf olvassa alacsonyabb szint mvelet, mint egy istream olvassa. Ennek kvetkeztben az istreambuf_iterator fellete kicsit bonyolultabb, mint az istream_iterator -. Ennek ellenre, ha egyszer sikerlt hibtlanul kezdrtket adnunk egy istreambuf_iterator objektumnak, akkor a *, a ++ s az = mveletek a szoksos helyeken, a szoksos jelentssel hasznlhatk. A proxy tpus egy megvalststl fgg segdtpus, amely lehetv teszi az uttagknt hasznlt ++ opertor alkalmazst anlkl, hogy felesleges korltozsokat tenne a streambuf-ra vonatkozan. A proxy trolja az eredmnyrtket addig, amg a bejrt tovbblptetjk:
template<class Ch, class Tr = char_traits<Ch> > class istreambuf_iterator<Ch,Tr>::proxy { Ch val; basic_streambuf<Ch,Tr>* buf; proxy(Ch v, basic_streambuf<Ch,Tr>* b) :val(v), buf(b) { } public: Ch operator*() { return val; } };

Az ostreambuf_iterator osztlyt hasonlan definilhatjuk:


template <class Ch, class Tr = char_traits<Ch> > class ostreambuf_iterator : public iterator<output_iterator_tag,void,void,void,void>{ public: typedef Ch char_type; typedef Tr traits_type; typedef basic_streambuf<Ch,Tr> streambuf_type; typedef basic_ostream<Ch,Tr> ostream_type; ostreambuf_iterator(ostream_type& os) throw(); ostreambuf_iterator(streambuf_type*) throw(); ostreambuf_iterator& operator=(Ch); // rs os streambuf-jba

Forrs: http://www.doksi.hu

750

A standard knyvtr

ostreambuf_iterator& operator*(); ostreambuf_iterator& operator++(); ostreambuf_iterator& operator++(int); }; bool failed() const throw(); // igaz, ha Tr::eof()-hoz rtnk

19.3. Ellenrztt bejrk


A standard knyvtr ltal biztostottak mellett a programoz sajt maga is ltrehozhat bejrkat. Erre gyakran szksg is van, amikor egy j tpus trolt hozunk ltre. Pldakppen bemutatunk egy olyan bejrt, amely ellenrzi, hogy rvnyes tartomnyban frnk-e hozz a trolhoz. A szabvnyos trolkat hasznlva sokkal ritkbban kell magunknak foglalkozni a memriakezelssel, a szabvnyos algoritmusok segtsgvel pedig a trolk elemeinek megcmzsre sem kell olyan gyakran gondolnunk. A standard knyvtr s a nyelvi eszkzk egyttes hasznlatval fordtsi idben elvgezhetnk sok tpusellenrzsi feladatot, gy a futsi idej hibk szma jelentsen kevesebb, mint a hagyomnyos, C stlus programokban. Ennek ellenre a standard knyvtr mg mindig a programozra hrtja azt a feladatot, hogy elkerlje egy trol memriahatrainak tlpst. Ha pldul vletlenl valamilyen x trolnak az x[x.size()+7] elemt prbljuk meg elrni, akkor megjsolhatatlan de ltalban rossz esemnyek kvetkezhetnek be. Egy tartomnyellenrztt vector hasznlata (pldul a 3.7.1 pontban bemutatott Vec-) nha segthet ezen a problmn, sokkal ltalnosabb megoldst jelent azonban, ha a bejrkon keresztl trtn minden hozzfrst ellenrznk. Ahhoz, hogy ilyen szint ellenrzst rhessnk el anlkl, hogy jelents terhet helyeznnk a programoz vllra, ellenrztt bejrkra van szksgnk, s valamilyen knyelmes mdszerre, amellyel ezeket a trolkhoz kapcsolhatjuk. A Checked_iter megvalstshoz szksgnk van egy trolra s egy bejrra, amely ebbe a trolba mutat. Ugyangy, mint a lektk (binder, 18.4.4.1) vagy a beszr bejrk (inserter, 19.2.4) esetben, most is kln fggvnyekkel segtjk az ellenrztt bejrk ltrehozst:
template<class Cont, class Iter> Checked_iter<Cont,Iter> make_checked(Cont& c, Iter i) { return Checked_iter<Cont,Iter>(c,i); }

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

751

template<class Cont> Checked_iter<Cont,typename Cont::iterator> make_checked(Cont& c) { return Checked_iter<Cont,typename Cont::iterator>(c,c.begin()); }

Ezek a fggvnyek sokat segtenek abban, hogy a tpusokat a paramterekbl llaptsuk meg, s ne kln kelljen azokat megadni:
void f(vector<int>& v, const vector<int>& vc) { typedef Checked_iter<vector<int>,vector<int>::iterator> CI; CI p1 = make_checked(v,v.begin()+3); CI p2 = make_checked(v); // alaprtelmezs szerint az els elemre mutat typedef Checked_iter<const vector<int>,vector<int>::const_iterator> CIC; CIC p3 = make_checked(vc,vc.begin()+3); CIC p4 = make_checked(vc); const vector<int>& vv = v; CIC p5 = make_checked(v,vv.begin());

Alaprtelmezs szerint a const trolknak minden bejrja is konstans, gy az ezekhez tartoz Checked_iter osztlyoknak is llandnak kell lennik. A p5 bejr arra mutat pldt, hogyan hozhatunk ltre const bejrt nem konstans trolbl. Ez magyarzza azt is, hogy mirt van szksge a Checked_iter tpusnak kt sablonparamterre: az egyik a trol tpust adja meg, a msik a konstans/nem konstans klnbsget fejezi ki. Ezen Checked_iter tpusok nevei elg hosszak (s csnyk), de ez nem szmt, ha a bejrkat ltalnostott (generikus) algoritmusok paramtereknt hasznljuk:
template<class Iter> void mysort(Iter first, Iter last); void f(vector<int>& c) { try { mysort(make_checked(c), make_checked(c,c.end()); } catch (out_of_bounds) { cerr<<"hopp: hiba a mysort()-ban\n"; abort(); } }

Forrs: http://www.doksi.hu

752

A standard knyvtr

Ez pont egy olyan algoritmus, melynek els vltozataiban knnyen elfordulhat, hogy kilpnk a megengedett tartomnybl, gy az ellenrztt bejrk hasznlata nagyon is indokolt. A Checked_iter osztlyt egy trolra hivatkoz mutatval s egy olyan bejrval brzolhatjuk, amely ebbe a trolba mutat:
template<class Cont, class Iter = typename Cont::iterator> class Checked_iter : public iterator_traits<Iter> { Iter curr; // az aktulis pozcira mutat bejr Cont* c; // az aktulis trolra hivatkoz mutat }; // ...

Az iterator_traits osztlybl val szrmaztats az egyik lehetsges mdszer a szksges tpusok (typedef) meghatrozshoz. A msik egyszer megolds az iterator osztlybl val szrmaztats ebben az esetben tlzott lenne (A reverse_iterator esetben ezt a megoldst vlasztottuk a 19.2.5 pontban). Ugyangy, ahogy egy bejrnak nem kell felttlenl osztlynak lennie, egy osztlyknt meghatrozott bejrnak sem kell felttlenl az iterator osztly leszrmazottjnak lennie. A Checked_iter osztly minden mvelete egyrtelm:
template<class Cont, class Iter = typename Cont::iterator> class Checked_iter : public iterator_traits<Iter> { // ... public: void valid(Iter p) const { if (c->end() == p) return; for (Iter pp = c->begin(); pp!=c->end(); ++pp) if (pp == p) return; throw out_of_bounds(); } friend bool operator==(const Checked_iter& i, const Checked_iter& j) { return i.c==j.c && i.curr==j.curr; } // nincs alaprtelmezett kezdrtk-ad // alaprtelmezett msol konstruktor s rtkads hasznlata

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

753

Checked_iter(Cont& x, Iter p) : c(&x), curr(p) { valid(p); } reference_type operator*() const { if (curr==c->end()) throw out_of_bounds(); return *curr; } pointer_type operator->() const { if (curr==c->end()) throw out_of_bounds(); return &*curr; } Checked_iter operator+(Dist d) const // csak kzvetlen elrs bejrkhoz { if (c->end()-curr<d || d<curr-c->begin()) throw out_of_bounds(); return Checked_iter(c,curr+d); } reference_type operator[ ](Dist d) const // csak kzvetlen elrs bejrkhoz { if (c->end()-curr<=d || d<curr-c->begin()) throw out_of_bounds(); return curr[d]; } Checked_iter& operator++() // prefix ++ { if (curr == c->end()) throw out_of_bounds(); ++curr; return *this; } Checked_iter operator++(int) // postfix ++ { Checked_iter tmp = *this; ++*this; // prefix ++ ltal ellenrztt return tmp; } Checked_iter& operator--() // prefix -{ if (curr == c->begin()) throw out_of_bounds(); --curr; return *this; }

Forrs: http://www.doksi.hu

754

A standard knyvtr

Checked_iter operator--(int) { Checked_iter tmp = *this; --*this; return tmp; }

// postfix -// prefix -- ltal ellenrztt

difference_type index() const { return curr-c.begin(); } // csak kzvetlen elrs esetn Iter unchecked() const { return curr; } }; // +, -, < stb. (19.6[6])

Egy Checked_iter objektumot mindig egy bizonyos trol egy bizonyos bejrjhoz ktnk. Egy alaposabb megvalstsban a valid() fggvnynek hatkonyabb vltozatt kell ltrehoznunk a kzvetlen elrs bejrkhoz (19.6[6]). Miutn kezdrtket adtunk a Checked_iter objektumnak, minden mveletet ellenrznk, amely elmozdthatja a bejrt, gy mindig biztosak lehetnk abban, hogy az a trol egy ltez elemre mutat. Ha megprblunk egy ilyen bejrt a trol rvnyes tartomnyn kvlre vinni, out_of_bounds kivtelt kapunk:
void f(list<string>& ls) { int count = 0; try { Checked_iter< list<string> > p(ls,ls.begin()); while (true) { ++p; // elbb-utbb a vgre r ++count; } } catch(out_of_bounds) { cout << "Tllps a troln " << count << " prblkozs utn.\n"; } }

A Checked_iter objektum pontosan tudja, melyik trolba mutat. Ez lehetv teszi, hogy az olyan eseteket kezeljk, amikor egy trolba mutat bejr rvnytelenn vlik valamilyen mvelet hatsra (16.3.8). Mg gy sem vesznk minden lehetsget figyelembe, teht ha az sszes hibalehetsgre fel akarunk kszlni, akkor egy msik, bonyolultabb bejrt kell ksztennk (lsd 19.6[7]).

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

755

Figyeljk meg, hogy az uttagknt hasznlt ++ opertor megvalstshoz szksg van egy ideiglenes trolelemre, mg az eltag-formnl erre nincs szksg. Ezrt bejrk esetben mindig rdemes a p++ forma helyett a ++p alakot hasznlni, ha az ellenkezjre nincs klnsebb okunk. Mivel a Checked_iter egy trolra hivatkoz mutatt trol, kzvetlenl nem hasznlhat a beptett tmbk kezelshez. Ha mgis ilyesmire van szksgnk, a c_array tpust (17.5.4) hasznlhatjuk. Ahhoz, hogy az ellenrztt bejrk megvalstst teljess tegyk, hasznlatukat egyszerv kell tennnk. Kt megkzeltssel prblkozhatunk: 1. Meghatrozunk egy ellenrztt trol tpust, amely ugyangy viselkedik, mint az tlagos trolk, attl eltekintve, hogy kevesebb konstruktor ll benne rendelkezsnkre s a begin(), end() stb. eljrsok mind Check_iter objektumot adnak vissza a szoksos bejrk helyett. 2. Ksztnk egy kezelosztlyt, amelynek valamilyen trolval adhatunk kezdrtket, s amely ehhez a trolhoz a ksbbiekben csak ellenrztt hozzfrseket engedlyez (19.6[8]). Az albbi sablon egy ellenrztt bejrt kt egy trolhoz:
template<class C> class Checked : public C { public: explicit Checked(size_t n) :C(n) { } Checked() :C() { } typedef Checked_iter<C> iterator; typedef Checked_iter<C,C::const_iterator> const_iterator; iterator begin() { return iterator(*this,C::begin()); } iterator end() { return iterator(*this,C::end()); } const_iterator begin() const { return const_iterator(*this,C::begin()); } const_iterator end() const { return const_iterator(*this,C::end()); } reference_type operator[ ](size_t n) { return Checked_iter<C>(*this)[n]; } }; C& base() { return static_cast<C&>(*this); } // rgzts az alaptrolhoz

Forrs: http://www.doksi.hu

756

A standard knyvtr

Ezutn hasznlhatjuk az albbi programrszletet:


Checked< vector<int> > vec(10); Checked< list<double> > lst; void f() { int v1 = vec[5]; // rendben int v2 = vec[15]; // out_of_bounds kivtelt vlt ki // ... lst.push_back(v2); mysort(vec.begin(),vec.end()); copy(vec.begin(),vec.end(),lst.begin()); }

A ltszlag felesleges base() fggvny szerepe az, hogy a Checked() fellett a trolk kezelinek (handle) fellethez igaztsa, ezek ugyanis ltalban nem tesznek lehetv automatikus konverzikat. Ha a trol mrete megvltozik, bejri rvnytelenn vlnak. Ez trtnik a Checked_iter objektumokkal is. Ebben az esetben a Checked_iter-nek a kvetkez kdrszlettel adhatunk j kezdrtket:
void g(vector<int>& vi) { Checked_iter<int> p(vi,vi.begin()); // .. int i = p.index(); vi.resize(100); p = Checked_iter<int>(vi,vi.begin()+i); }

// aktulis pozci lekrse // p rvnytelen lesz // az aktulis pozci visszalltsa

A rgi aktulis pozci rvnytelenn vlik, ezrt szksgnk van az index() fggvnyre, amely egy Checked_iter trolsra s visszalltsra hasznlhat.

19.3.1. Kivtelek, trolk s algoritmusok


Knnyen gy tnhet, hogy a szabvnyos algoritmusok s az ellenrztt bejrk egyttes hasznlata olyan felesleges, mint az v s a nadrgtart egyttes viselse: mindkett nmagban is megvd minket a balesetektl. Ennek ellenre a tapasztalat azt mutatja, hogy sok ember s sok alkalmazs szmra indokolt ilyen szint paranoia, klnsen akkor, ha egy gyakran vltoz programot sokan hasznlnak rendszeresen.

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

757

A futsi idej ellenrzsek hasznlatnak egyik mdja, hogy csak a tesztels idejre hagyjuk azokat programunkban. Teht ezek az ellenrzsek eltnnek a programbl, mieltt az les krlmnyek kz kerlne. Ez az eljrs ahhoz hasonlthat, mint amikor a mentmellnyt addig hordjuk, amg partkzelben pancsolunk, s levesszk, amikor a nylt tengerre merszkednk. Ugyanakkor igaz, hogy a futsi idej ellenrzsek jelents mennyisg idt s memrit ignyelhetnek, gy folyamatosan ilyen felgyeletet biztostani nem lehet vals elvrs. Jelentktelen haszon rdekben optimalizlni mindig felesleges, teht mieltt vglegesen trlnk bizonyos ellenrzseket, prbljuk ki, hogy jelents mrtk teljestmnyjavulst rnk-e el gy. Ahhoz, hogy a programoz tnyleg prblkozhasson ilyesmivel, knnyv kell tennnk szmra a futsi idej ellenrzsek eltvoltst (24.3.7.1). Ha elvgeztk a szksges mrseket, a futsi idej ellenrzseket a ltfontossg s remlhetleg a legalaposabban tesztelt helyekrl trlhetjk, mg mshol az ellenrzst egy viszonylag olcs biztostsnak tekinthetjk. A Checked_iter hasznlata szmtalan hibra felhvhatja figyelmnket, de nem sokat segt abban, hogy ezeket a hibkat kijavtsuk. A programozk ritkn rnak olyan programokat, amelyek minden lehetsgre felkszlnek s ellenriznek minden mveletet, amely kivtelt okozhat (++, --, *, [ ], -> s =). gy kt nyilvnval stratgia kzl vlaszthatunk: 1. A kivteleket keletkezsi helyk kzelben kapjuk el, gy a kivtelkezel megrja pontosabban megllapthatja a hiba okt s hatkony ellenlpseket tehet. 2. A kivteleket a programban viszonylag magas szinten kapjuk el, az eddigi szmtsoknak egy jelents rszt eldobjuk, s minden adatszerkezetet gyansnak minstnk, amelybe rs trtnt a hibt kivlt szmts kzben. (Lehet, hogy ilyen adatszerkezetek nincsenek is, esetleg knnyen kizrhatk a hibaforrsok krbl.) Feleltlensg elkapni egy kivtelt, amelyrl azt sem tudjuk, hogy a program mely rszn keletkezett, s tovbblpni azzal a felttelezssel, hogy egyetlen adatszerkezetnk sem kerlt nemkvnatos llapotba, hacsak nincs egy tovbbi hibakezel, amely az ezutn keletkez hibkat feldolgozza. Egy egyszer plda az ilyen helyzetre, amikor az eredmnyek tovbbadsa eltt egy vgs ellenrzst vgznk (akr szmtgprl, akr emberi munkavgzsrl van sz). Ilyenkor egyszerbb s olcsbb rtatlanul tovbbhaladni, minthogy alacsony szinten minden hibalehetsget megvizsgljunk. Ezzel egyben arra is pldt mutattunk, hogy a tbbszint hibakezels (14.9) hogyan teszi lehetv a programok egyszerstst.

Forrs: http://www.doksi.hu

758

A standard knyvtr

19.4. Memriafoglalk
A memriafoglalk (alloktorok, allocator) szerepe az, hogy a fizikai memria kezelsnek gondjt levegyk azon algoritmusok s trolk ksztinek vllrl, melyek szmra memrit kell lefoglalnunk. A memriafoglalk szabvnyos felletet adnak a memriaterletek lefoglalshoz s felszabadtshoz, valamint szabvnyos neveket adnak azoknak a tpusoknak, melyeket mutatknt vagy referenciaknt hasznlhatunk. A bejrhoz hasonlan a memriafoglal fogalma is tisztn elvont, gy mindent memriafoglalnak neveznk, ami memriafoglalknt viselkedik. A standard knyvtr egy szabvnyos memriafoglalt biztost, amely a legtbb felhasznl szmra megfelel, de ha szksg van r, a programozk maguk is ltrehozhatnak egyni vltozatokat, melyekkel a memrit ms szerkezetnek tntethetik fel. Kszthetnk pldul olyan memriafoglalt, amely osztott memrit hasznl, szemtgyjt algoritmust valst meg, elre lefoglalt memriaszeletbl hozza ltre az objektumokat (19.4.2) s gy tovbb. A szabvnyos trolk s algoritmusok a mkdskhz szksges memrit mindig memriafoglal segtsgvel foglaljk le s rik el. gy ha j memriafoglalt ksztnk, a szabvnyos trolkat is felruhzzuk azzal a kpessggel, hogy a memrit megvltozott szemszgbl lssk.

19.4.1. A szabvnyos memriafoglal


A szabvnyos allocator sablon a <memory> fejllomnyban tallhat s a memriafoglalst a new() opertor (6.2.6) segtsgvel vgzi. Alaprtelmezs szerint mindegyik szabvnyos trol ezt hasznlja:
template <class T> class std::allocator { public: typedef T value_type; typedef size_t size_type; typedef ptrdiff_t difference_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; pointer address(reference r) const { return &r; }

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk


const_pointer address(const_reference r) const { return &r; } allocator() throw(); template <class U> allocator(const allocator<U>&) throw(); ~allocator() throw(); pointer allocate(size_type n, allocator<void>::const_pointer hint = 0); void deallocate(pointer p, size_type n);

759

// hely n darab // Ts szmra // n darab Ts helynek felszabadtsa, // megsemmists nlkl

void construct(pointer p, const T& val) { new(p) T(val); } // *p feltltse val-lal void destroy(pointer p) { p->~T(); } // *p megsemmistse a hely felszabadtsa // nlkl size_type max_size() const throw(); template <class U> struct rebind { typedef allocator<U> other; }; // valjban typedef allocator<U> other

}; template<class T> bool operator==(const allocator<T>&, const allocator<T>&) throw(); template<class T> bool operator!=(const allocator<T>&, const allocator<T>&) throw();

Az allocate(n) mvelettel n objektum szmra foglalhatunk helyet, prja pedig a deallocate(p,n), mellyel felszabadthatjuk az gy lefoglalt terletet. Figyeljk meg, hogy a deallocate() fggvnynek is tadjuk az n paramtert. Ezzel a megoldssal kzel optimlis memriafoglalkat kszthetnk, amelyek csak a felttlenl szksges informcikat troljk a lefoglalt terletrl. Msrszt viszont ezek a memriafoglalk megkvetelik, hogy a programoz mindig a megfelel n rtket adja meg a deallocate() hvsakor. Megjegyzend, hogy a deallocate() klnbzik az operator delete()-tl, amennyiben mutat paramtere nem lehet nulla. Az alaprtelmezett allocator a new(size_t) opertort hasznlja a memria lefoglalshoz s a delete(void*) mveletet a felszabadtshoz. Ebbl kvetkezik, hogy szksg lehet a new_handler() meghvsra s a std::bad_alloc kivtel kivltsra, ha elfogyott a memria (6.2.6.2). Jegyezzk meg, hogy az allocate() fggvnynek nem kell felttlenl meghvnia egy alacsonyszint memriafoglalt. Gyakran jobb megoldst jelent egy memriafoglal szmra, ha maga tartja nyilvn a kiosztsra ksz, szabad memriaszeleteket, s ezeket minimlis idvesztesggel adja ki (19.4.2). A nem ktelez hint paramter az allocate() fggvnyben mindig az adott megvalststl fgg. Mindenkppen arra szolgl, hogy segtse a memriafoglalt azokban a rendszerek-

Forrs: http://www.doksi.hu

760

A standard knyvtr

ben, ahol fontos a lokalits (vagyis az adott rendszerhez igazods). Egy memriafoglaltl pldul elvrhatjuk, hogy egy lapozs rendszerben az sszefgg objektumoknak azonos lapon foglaljon helyet. A hint paramter tpusa pointer, az albbi erteljesen egyszerstett specializcinak megfelelen:
template <> class allocator<void> { public: typedef void* pointer; typedef const void* const_pointer; // megj.: nincs referencia typedef void value_type; template <class U> struct rebind { typedef allocator<U> other; }; // valjban typedef allocator<U> other };

Az allocator<void>::pointer tpus ltalnos mutattpusknt szolgl s minden szabvnyos memriafoglal esetben a const void* tpusnak felel meg. Ha a memriafoglal dokumentcija mst nem mond, a programoz kt lehetsg kzl vlaszthat az allocate() fggvny hasznlatakor: 1. Nem adja meg a hint paramtert. 2. A hint paramterben egy olyan objektumra hivatkoz mutatt ad meg, amelyet gyakran hasznl majd az j objektummal egytt. Egy sorozatban pldul megadhatjuk az elz elem cmt. A memriafoglalk arra szolglnak, hogy megkmljk a trolk ksztit a fizikai memria kzvetlen kezelstl. Pldakppen vizsgljuk meg, hogy egy vector megvalstsban hogyan hasznljuk a memrit:
template <class T, class A = allocator<T> > class vector { public: typedef typename A::pointer iterator; // ... private: A alloc; // memriafoglal objektum iterator v; // mutat az elemekre // ... public: explicit vector(size_type n, const T& val = T(), const A& a = A()) : alloc(a) {

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

761

v = alloc.allocate(n); for(iterator p = v; p<v+n; ++p) alloc.construct(p,val); // ...

void reserve(size_type n) { if (n<=capacity()) return; iterator p = alloc.allocate(n); iterator q = v; while (q<v+size()) { alloc.construct(p++,*q); alloc.destroy(q++); } alloc.deallocate(v,capacity()); v = p-size(); // ... // ltez elemek msolsa

// rgi hely felszabadtsa

} }; // ...

A memriafoglalk mveleteit a pointer s reference typedef-ek segtsgvel fejezzk ki, gy megadjuk a programoznak azt a lehetsget, hogy ms tpusokat hasznljon a memria elrshez. Ezt ltalban nagyon nehz megoldani. A C++ nyelv segtsgvel pldul nincs lehetsgnk arra, hogy egy tkletes hivatkozstpust hatrozzunk meg. A nyelv s a knyvtrak alkoti viszont ezeket a typedef-eket olyan tpusok ltrehozshoz hasznlhatjk, amelyeket egy tlagos felhasznl nem tudna elkszteni. Pldaknt egy olyan memriafoglalt emlthetnk, amely lland adatterlet elrsre szolgl, de gondolhatunk egy nagy mutatra is, amellyel a memrinak azt a terlett is elrhetjk, amit a szoksos (ltalban 32 bites) mutatk mr nem kpesek megcmezni. Egy tlagos felhasznl a memriafoglalnak egyedi clra megadhat a szoksostl eltr mutattpust is. Ugyanezt nem tehetjk meg referencikkal, de ez a korltozs ksrletezsnl vagy egyedi rendszerekben nem okoz nagy gondot. Memriafoglalt azrt hozunk ltre, hogy knnyen kezelhessnk olyan objektumokat, melyeknek tpust a memriafoglal sablonparamtereknt adtuk meg. A legtbb trol megvalstsban azonban szksg van ms tpus objektumok ltrehozsra is. Pldul a list megvalstshoz szksg van Link objektumok ltrehozsra. Az ilyen Link jelleg objektumokat a megfelel list osztly memriafoglaljval hozzuk ltre.

Forrs: http://www.doksi.hu

762

A standard knyvtr

A furcsa rebind tpus arra szolgl, hogy memriafoglal kpes legyen brmilyen tpus objektum helynek lefoglalsra:
typedef typename A::template rebind<Link>::other Link_alloc; // "sablon", lsd C.13.6

Ha A egy allocator, akkor a rebind<Link>::other tpus-meghatrozs jelentse allocator<Link>, teht a fenti typedef egy kzvetett megfogalmazsa az albbinak:
typedef allocator<Link> Link_alloc;

A kzvetett megfogalmazs azonban megkml minket attl, hogy az allocator kulcsszt kzvetlenl hasznljuk. A Link_alloc tpust csak az A sablonparamteren keresztl hatrozzuk meg. Pldul:
template <class T, class A = allocator<T> > class list { private: class Link { /* ... */ }; typedef typename A::rebind<Link>::other Link_alloc; Link_alloc a; A alloc; // "list" memriafoglal // ... public: typedef typename A::pointer iterator; // ... // allocator<Link> // "link" memriafoglal

};

iterator insert(iterator pos, const T& x ) { Link_alloc::pointer p = a.allocate(1); // egy Link megszerzse // ... } // ...

Mivel a Link a list osztly tagja, paramtere egy memriafoglal. Ennek kvetkezben a klnbz memriafoglalkkal rendelkez listk Link objektumai klnbz tpusak, ugyangy, ahogy maguk a listk is klnbznek (17.3.3).

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

763

19.4.2. Felhasznli memriafoglalk


A trol ksztjnek gyakran van szksge arra, hogy egyesvel hozzon ltre (allocate()) vagy semmistsen meg (deallocate()) objektumokat. Ha az allocate() fggvnyt meggondolatlanul ksztjk el, nagyon sokszor kell meghvnunk a new opertort, pedig a new opertor ilyen hasznlatra gyakran kis hatkonysg. A felhasznli memriafoglalk pldjaknt az albbiakban olyan mdszerrel lnk, mellyel egy kszletet hozunk ltre, amely rgztett mret memriaszeleteket trol. A memriafoglal ennek felhasznlsval hatkonyabban hajtja vgre az allocate() eljrst, mint a hagyomnyos (br ltalnosabb) new() opertor. Vletlenl n mr korbban is ltrehoztam egy ilyen kszletes memriafoglalt, amely krlbell azt csinlja, amire most szksgnk van, de nem a megfelel felletet nyjtja (hiszen vekkel azeltt kszlt, hogy a memriafoglalk tlete megszletett volna). Ez a Pool osztly meghatrozza a rgztett mret elemek kszlett, amelybl a programoz gyorsan foglalhat le terleteket s gyorsan fel is szabadthatja azokat. Ez egy alacsonyszint tpus, amely kzvetlenl a memrit kezeli s az igaztsi (alignment) problmkkal is foglalkozik:
class Pool { struct Link { Link* next; }; struct Chunk { enum { size = 8*1024-16 }; char mem[size]; Chunk* next; }; Chunk* chunks; const unsigned int esize; Link* head; Pool(Pool&); void operator=(Pool&); void grow(); public: Pool(unsigned int n); ~Pool(); void* alloc(); void free(void* b); // msolsvdelem // msolsvdelem // a kszlet nvelse // n az elemek mrete // hely foglalsa egy elem szmra // elem visszahelyezse a kszletbe // valamivel kevesebb 8K-nl, gy egy // "Chunk" belefr 8K-ba // elszr a lefoglaland terlet az igazts // miatt

};

Forrs: http://www.doksi.hu

764

A standard knyvtr

inline void* Pool::alloc() { if (head==0) grow(); Link* p = head; head = p->next; return p; } inline void Pool::free(void* b) { Link* p = static_cast<Link*>(b); p->next = head; head = p; }

// az els elem visszaadsa

// b visszahelyezse els elemknt

Pool::Pool(unsigned int sz) : esize(sz<sizeof(Link)?sizeof(Link):sz) { head = 0; chunks = 0; } Pool::~Pool() { Chunk* n = chunks; while (n) { Chunk* p = n; n = n->next; delete p; } } void Pool::grow() { // minden 'chunk' felszabadtsa

// hely foglalsa j 'chunk' szmra, melyet 'esize' mret elemek // lncolt listjaknt rendeznk

Chunk* n = new Chunk; n->next = chunks; chunks = n; const int nelem = Chunk::size/esize; char* start = n->mem; char* last = &start[(nelem-1)*esize]; for (char* p = start; p<last; p+=esize) reinterpret_cast<Link*>(p)->next = reinterpret_cast<Link*>(p+esize); reinterpret_cast<Link*>(last)->next = 0; head = reinterpret_cast<Link*>(start);

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

765

A vals letben zajl munka szemlltetsre a Pool osztlyt vltozatlan formban hasznljuk fel az j memriafoglal megvalstshoz, nem csak trjuk, hogy a neknk megfelel felletet nyjtsa. A kszletes memriafoglal clja, hogy objektumaink egyesvel trtn ltrehozsa s megsemmistse gyors legyen. A Pool osztly mindezt biztostja. A megvalstst azzal kell mg kiegsztennk, hogy egyszerre tetszleges szm, illetve ( a rebind() ignyeinek megfelelen) tetszleges mret objektumokat is ltrehozhassunk. Ezt a kt problmt feladatnak hagyjuk (19.6[9]). A Pool osztly felhasznlsval a Pool_alloc megvalstsa mr egyszer:
template <class T> class Pool_alloc { private: static Pool mem; // elemkszlet sizeof(T) mrettel public: // mint a szabvnyos allocator (19.4.1) }; template <class T> Pool Pool_alloc<T>::mem(sizeof(T)); template <class T> Pool_alloc<T>::Pool_alloc() { } template <class T> T* Pool_alloc<T>::allocate(size_type n, void* = 0) { if (n == 1) return static_cast<T*>(mem.alloc()); // ... } template <class T> void Pool_alloc<T>::deallocate(pointer p, size_type n) { if (n == 1) { mem.free(p); return; } // ... }

A memriafoglalt ezutn mr a megszokott formban hasznlhatjuk:


vector< int,Pool_alloc<int> > v; map<string,number,Pool_alloc< pair<const string,number> > > m; // ugyangy mint szoktuk vector<int> v2 = v; // hiba: klnbz memriafoglal-paramterek

Forrs: http://www.doksi.hu

766

A standard knyvtr

A Pool_Alloc megvalstshoz statikus Pool objektumot hasznlunk. Azrt dntttem gy, mert a standard knyvtr a memriafoglalkra egy korltozst knyszert, mgpedig azzal, hogy a szabvnyos trolk megvalstsnak megengedi, hogy minden objektumot egyenrtknek tekintsenek, amelynek tpusa az adott trol memriafoglalja. Ez valjban igen jelents hatkonysgi elnyket jelent: ennek a korltozsnak ksznheten a Link objektumok memriafoglalinak pldul nem kell kln memriaterletet flretennnk (annak ellenre, hogy a Link osztly ltalban paramterknt egy memriafoglalt kap ahhoz a trolhoz, amelyben szerepel, 19.4.1). Egy msik elny, hogy azokban a mveletekben, ahol kt sorozat elemeit kell elrnnk (pldul a swap() fggvnyben), nem kell megvizsglnunk, hogy a hasznlt elemek ugyanolyan memriafoglalkkal rendelkeznek-e. A htrny, hogy a korltozs kvetkeztben az ilyen memriafoglalk nem hasznlhatnak objektumszint adatokat. Mieltt ilyen optimalizcit hasznlnnk, gondoljuk vgig, hogy szksg van-e r. n remlem, hogy az alaprtelmezett allocator legtbb megvalstsban elvgzik ezt a klasszikus C++ optimalizcit, gy megkmlnek minket ettl a problmtl.

19.4.3. ltalnostott memriafoglalk


Az allocator valjban nem ms, mint annak az tletnek az egyszerstett s optimalizlt vltozata, miszerint egy trolnak egy sablonparamteren keresztl adjuk meg az informcikat (13.4.1., 16.2.3). Logikus elvrs pldul, hogy a trol minden elemnek helyt a trol memriafoglaljval foglaljuk le. Ha azonban ilyenkor lehetv tesszk, hogy kt ugyanolyan tpus list trolnak klnbz memriafoglalja legyen, akkor a splice() (17.2.2.1) nem valsthat meg egyszer tlncolssal, hanem szablyos msolst kell meghatroznunk, mert vdekeznnk kell azon (ritka) esetek ellen, amikor a kt listban a memriafoglalk nem azonosak, annak ellenre, hogy tpusuk megegyezik. Hasonl problma, hogy ha a memriafoglalk tkletesen ltalnosak, akkor a rebind() eljrsnak (amely tetszleges tpus elemek ltrehozst teszi lehetv a memriafoglal szmra) sokkal bonyolultabbnak kell lennie. Ezrt a normlis memriafoglalkrl azt felttelezzk, hogy nem trolnak objektumszint adatokat, az algoritmusok pedig kihasznlhatjk ezt. Meglep mdon ez a drki korltozs az objektumszint informcikra nzve nem tlsgosan veszlyes a memriafoglalk esetben. A legtbb memriafoglalnak nincs is szksge objektumszint adatokra, st ilyen adatok nlkl mg gyorsabbak is lehetnek, radsul a memriafoglalk azrt tudnak adatokat trolni, a memriafoglal-tpusok szintjn. Ha kln adatelemekre van szksg, klnbz memriafoglal-tpusokat hasznlhatunk:

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

767

template<class T, class D> class My_alloc { D d; // ...

// T memriafoglaljt D hasznlatval // hozzuk ltre // a My_alloc<T,D> szmra szksges adatok

};

typedef My_alloc<int,Persistent_info> Persistent; typedef My_alloc<int,Shared_info> Shared; typedef My_alloc<int,Default_info> Default; list<int,Persistent> lst1; list<int,Shared> lst2; list<int,Default> lst3;

Az lst1, az lst2 s az lst3 listk klnbz tpusak, gy ha kzlk kettt akarunk felhasznlni valamilyen mveletben, akkor ltalnos algoritmusokat kell hasznlnunk (18.fejezet), nem pedig specializlt lista mveleteket (17.2.2.1). Ebbl kvetkezik, hogy az tlncols helyett msolst kell hasznlnunk, gy a klnbz memriafoglalk hasznlata nem okoz problmt. Az objektumszint adatokra vonatkoz korltozsokra a memriafoglalkban azrt van szksg, mert gy tudjuk kielgteni a standard knyvtr szigor hatkonysgi kvetelmnyeit, mind futsi id, mind trhasznlat szempontjbl. Egy lista memriafoglalja pldul nem foglal jelentsen tbb memrit a szksgesnl, de ha minden listaelemnl jelentkezne egy kis felesleg, akkor ez jelents vesztesget okozna. Gondoljuk vgig, hogyan hasznlhatnnk a memriafoglalkat akkor, ha a standard knyvtr hatkonysgi kvetelmnyeitl eltekintennk. Ez a helyzet olyan nem szabvnyos knyvtr esetben fordulhat el, melyben nem volt fontos tervezsi szempont a nagy hatkonysg az adatszerkezetek s tpusok ltrehozsakor, vagy akr a standard knyvtr bizonyos egyedi cl megvalstsaiban. Ilyenkor a memriafoglal felhasznlhat olyan jelleg informcik trolsra is, melyek ltalban ltalnos bzisosztlyokban kapnak helyet (16.2.2). Kszthetnk pldul olyan memriafoglalkat, melyek vlaszt tudnak adni arra a krdsre, hogy az objektumok hol kaptak helyet, knlhatnak olyan informcikat, melyek az objektumok elrendezsre vonatkoznak, vagy megkrdezhetjk tlk, hogy egy adott elem benne van-e a trolban. Segtsgkkel elkszthet egy olyan trolfelgyel is, amely gyorsttrknt szolgl a httrtrhoz vagy kapcsolatokat biztost a trol s ms objektumok kztt s gy tovbb. Ezzel a mdszerrel teht tetszleges szolgltatsokat biztosthatunk a szoksos trolmveletek htterben. Ennek ellenre rdemes klnbsget tennnk az adatok tro-

Forrs: http://www.doksi.hu

768

A standard knyvtr

lsnak s az adatok felhasznlsnak feladatai kztt. Ez utbbi nem tartozik egy ltalnostott memriafoglal hatskrbe, gy inkbb egy kln sablonparamter segtsgvel illik megvalstanunk.

19.4.4. Elksztetlen memria


A szabvnyos allocator mellett a <memory> fejllomnyban tallhatunk nhny olyan fggvnyt is, melyek az elksztetlen (rtkkel nem feltlttt) memria problmival foglalkoznak. Azt a veszlyes, de gyakran nagyon fontos lehetsget biztostjk, hogy a T tpusnvvel hivatkozhatunk egy olyan memriaterletre, amely elg nagy egy T tpus objektum trolsra, de nem teljesen szablyosan ltrehozott T objektumot tartalmaz. A knyvtrban hrom fggvnyt tallunk, mellyel elksztetlen terletre rtkeket msolhatunk:
template <class In, class For> For uninitialized_copy(In first, In last, For res) { typedef typename iterator_traits<For>::value_type V; // msols res-be

while (first != last) new (static_cast<void*>(&*res++)) V(*first++); // ltrehozs res-ben (10.4.11) return res;

template <class For, class T> void uninitialized_fill(For first, For last, const T& val) { typedef typename iterator_traits<For>::value_type V; }

//msols [first,last)-ba

while (first != last) new (static_cast<void*>(&*first++)) V(val);

// ltrehozs first-ben

template <class For, class Size, class T> void uninitialized_fill_n(For first, Size n, const T& val) { typedef typename iterator_traits<For>::value_type V; } while (n--) new (static_cast<void*>(&*first++)) V(val);

//msols [first,first+n)-be

// ltrehozs first-ben

Ezek a fggvnyek elssorban trolk s algoritmusok ksztsnl hasznosak. A reserve() s a resize() (16.3.8) pldul legknnyebben ezen fggvnyek felhasznlsval valstha-

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

769

t meg (19.6[10]). Igen nagy problma, ha egy ilyen kezdrtkkel nem rendelkez objektum valahogy kiszabadul a trol bels megvalstsbl s tlagos felhasznlk kezbe kerl (lsd mg E.4.4). Az algoritmusoknak gyakran van szksgk ideiglenes trterletre feladataik helyes vgrehajtshoz. Ezeket a terleteket gyakran rdemes egyetlen mvelettel lefoglalni, annak ellenre, hogy rtkkel feltlteni csak akkor fogjuk azokat, amikor tnylegesen szksg lesz rjuk. Ezrt a knyvtr tartalmaz egy fggvnyprt, mellyel elksztetlen memriaterletet foglalhatunk le, illetve szabadthatunk fel:
template <class T> pair<T*,ptrdiff_t> get_temporary_buffer(ptrdiff_t); // lefoglals // kezdeti rtkads // nlkl template <class T> void return_temporary_buffer(T*); // felszabadts // megsemmists // nlkl

A get_temporary_buffer<X>(n) mvelet megprbl helyet foglalni n vagy tbb X tpus objektum szmra. Ha ez sikerl, akkor az els kezdrtk nlkli objektumra hivatkoz mutatt s a lefoglalt terleten elfr, X tpus objektumok szmt adja vissza. Ha nincs elg memria, a visszaadott pr msodik (second) tagja nulla lesz. Az tlet az, hogy a rendszer a gyors helyfoglals rdekben rgztett mret tmeneti trat tart kszenltben. Ennek kvetkeztben elfordulhat, hogy n objektum szmra ignylnk terletet, de az tmeneti trban ennl tbb is elfr. A gond az, hogy az is elkpzelhet, hogy kevesebb terletet kapunk az ignyeltnl, gy a get_temporary_buffer() felhasznlsi mdja az, hogy kellen nagy trat ignylnk, aztn ebbl annyit hasznlunk fel, amennyit megkaptunk. A get_temporary_buffer() ltal lefoglalt terletet fel kell szabadtanunk a return_temporary_buffer() fggvny segtsgvel. Ugyangy, ahogy a get_temporary_buffer() a konstruktor meghvsa nlkl foglal le terletet, a return_temporary_buffer() a destruktor meghvsa nlkl szabadt fel. Mivel a get_temporary_buffer() alacsonyszint mvelet s kifejezetten ideiglenes trak lefoglalsra szolgl, nem hasznlhatjuk a new vagy az allocator::allocate() helyett, hosszabb let objektumok ltrehozsra. Azok a szabvnyos algoritmusok, melyek egy sorozatba rnak, felttelezik, hogy a sorozat elemei korbban mr kaptak kezdrtket. Teht az algoritmusok az rshoz egyszer rtkadst hasznlnak s nem msol konstruktort. Ennek kvetkeztben viszont nem hasznlhatunk elksztetlen terletet egy algoritmus kimenete cljra. Ez sokszor igen kellemetlen, mert az egyszer rtkads jval kltsgesebb, mint a kezdeti, radsul nem is rdekel minket, milyen rtkeket fogunk fellrni (ha rdekelne, nem rnnk fell). A megoldst a raw_storage_iterator jelenti, amely szintn a <memory> fejllomnyban tallhat s egyszer helyett kezdeti rtkadst vgez:

Forrs: http://www.doksi.hu

770

A standard knyvtr

template <class Out, class T> class raw_storage_iterator : public iterator<output_iterator_tag,void,void,void,void> { Out p; public: explicit raw_storage_iterator(Out pp) : p(pp) { } raw_storage_iterator& operator*() { return *this; } raw_storage_iterator& operator=(const T& val) { T* pp = &*p; new(pp) T(val); // val pp-be helyezse (10.4.11) return *this; } raw_storage_iterator& operator++() {++p; return *this; } raw_storage_iterator operator++(int) { raw_storage_iterator t = *this; ++p; return t; } };

Pldul kszthetnk egy sablon fggvnyt, amely egy vector elemeit egy tmeneti trba msolja:
template<class T, class A> T* temporary_dup(vector<T,A>& v) { pair<T*,ptrdiff_t> p = get_temporary_buffer<T>(v.size()); if (p.second < v.size()) { // ellenrizzk, hogy elg volt-e az elrhet memria if (p.first != 0) return_temporary_buffer(p.first); return 0; } copy(v.begin(),v.end(),raw_storage_iterator<T*,T>(p.first)); return p.first; }

Ha a get_temporary_buffer() helyett a new opertort hasznltuk volna, az tmeneti tr kezdeti feltltsre sor kerlt volna. Mivel kikerltk az elksztst, a raw_storage_iterator osztlyra van szksgnk a nem feltlttt terlet kezelshez. Ebben a pldban a temporary_dump() fggvny meghvjnak feladata marad, hogy meghvja a return_temporary_buffer() eljrst a visszaadott mutatra.

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

771

19.4.5. Dinamikus memria


A <new> fejllomnyban olyan lehetsgek szerepelnek, melyekkel a new s delete opertort valsthatjuk meg:
class bad_alloc : public exception { /* ... */ }; struct nothrow_t {}; extern const nothrow_t nothrow; // kivtelt ki nem vlt memriafoglal

typedef void (*new_handler)(); new_handler set_new_handler(new_handler new_p) throw(); void* operator new(size_t) throw(bad_alloc); void operator delete(void*) throw(); void* operator new(size_t, const nothrow_t&) throw(); void operator delete(void*, const nothrow_t&) throw(); void* operator new[ ](size_t) throw(bad_alloc); void operator delete[ ](void*) throw(); void* operator new[ ](size_t, const nothrow_t&) throw(); void operator delete[ ](void*, const nothrow_t&) throw(); void* operator new (size_t, void* p) throw() { return p; } void operator delete (void* p, void*) throw() { } // elhelyezs (10.4.11) //nem csinl semmit

void* operator new[ ](size_t, void* p) throw() { return p; } void operator delete[ ](void* p, void*) throw() { } //nem csinl semmit

Egy res kivtel-specifikcival (14.6) definilt operator new() vagy operator new[ ]() nem jelezheti a memria elfogyst az std::bad_alloc kivtel kivltsval. Ehelyett, ha sikertelen a helyfoglals, 0 rtket adnak vissza. A new kifejezs (6.2.6.2) az res kivtel-specifikcival rendelkez memriafoglalk ltal visszaadott rtket mindig ellenrzi, s ha 0 rtket kap, nem hvja meg a konstruktort, hanem azonnal szintn 0 rtket ad vissza. A nothrow memriafoglalk pldul 0 rtket adnak vissza a sikertelen helyfoglals jelzsre, s nem egy bad_alloc kivtelt vltanak ki:
void f() { int* p = new int[100000];

// bad_alloc-ot vlthat ki // nem vlt ki kivtelt

if (int* q = new(nothrow) int[100000]) { // lefoglals sikeres }

Forrs: http://www.doksi.hu

772

A standard knyvtr

else { } }

// lefoglals sikertelen

Ez a mdszer lehetv teszi, hogy kivtel eltti hibakezelst hasznljunk a helyfoglals sorn.

19.4.6. C stlus helyfoglals


A C++ a C-tl rklt egy dinamikusmemria-kezel felletet, melyet a <cstdlib> fejllomnyban tallhatunk meg:
void* malloc(size_t s); // s bjt lefoglalsa void* calloc(size_t n, size_t s); // n-szer s bjt feltltse 0 kezdrtkkel void free(void* p); // a malloc() vagy calloc() ltal lefoglalt szabad terlet felszabadtsa void* realloc(void* p, size_t s); // a p ltal mutatott tmb mretnek mdostsa s-re;

Ezen fggvnyek helyett hasznljuk inkbb a new vagy a delete opertort, vagy a szabvnyos trolk mg magasabb szint szolgltatsait. A fenti eljrsok elksztetlen memrival foglalkoznak, gy a free() pldul nem hv meg semmilyen destruktort a memriaterlet felszabadtsra. A new s a delete megvalstsai hasznlhatjk ezeket a fggvnyeket, de szmukra sem ktelez. Ha egy objektumot pldul a new opertorral hozunk ltre, majd a free() fggvnnyel prblunk meg megsemmisteni, slyos problmkba tkzhetnk. Ha a realloc() fggvny szolgltatsaira lenne szksgnk, hasznljunk inkbb szabvnyos trolkat, melyek az ilyen jelleg feladatokat ltalban sokkal egyszerbben s legalbb olyan hatkonyan hajtjk vgre (16.3.5). A knyvtr tartalmaz nhny olyan fggvnyt is, melyekkel hatkonyan vgezhetnk bjtszint mveleteket. Mivel a C a tpus nlkli bjtokat eredetileg char* mutatkon keresztl rte el, ezek a fggvnyek a <cstring> fejllomnyban tallhatk. Ezekben a fggvnyekben a void* mutatkat char* mutatkknt kezeljk:
void* memcpy(void* p, const void* q, size_t n); void* memmove(void* p, const void* q, size_t n); // nem tfed terletek msolsa // esetleg tfed terletek msolsa

A strcpy() (20.4.1) fggvnyhez hasonlan ezek a mveletek is n darab bjtot msolnak a q cmrl a p cmre, majd a p mutatt adjk vissza. A memove() ltal kezelt memriaterletek tfedhetik egymst, de a memcpy() felttelezi, hogy a kt terlet teljesen klnll, s ltalban ki is hasznlja ezt a felttelezst.

Forrs: http://www.doksi.hu

19. Bejrk s memriafoglalk

773

Hasonlan mkdnek az albbi fggvnyek is:


void* memchr(const void* p, int b, size_t n); int memcmp(const void* p, const void* q, size_t n); void* memset(void* p, int b, size_t n); // mint a strchr() (20.4.1): // b keresse p[0]..p[n-1]-ben // mint a strcmp(): bjtsorozatok // sszehasonltsa // n bjt b-re lltsa, p // visszaadsa

Szmos C++-vltozatban ezeknek az eljrsoknak ersen optimalizlt vltozatai is megtallhatk.

19.5. Tancsok
[1] Amikor egy algoritmust megrunk, dntsk el, milyen bejrra van szksgnk az eljrs kellen hatkony megvalstshoz, majd folyamatosan figyeljnk, hogy csak olyan mveleteket hasznljunk, melyek az adott bejr-kategria esetben rendelkezsnkre llnak. 19.2.1 [2] Az algoritmusok hatkonyabb megvalstsra hasznljunk tlterhelst, ha a paramterknt megadott bejr a minimlis kvetelmnynl tbb szolgltatst nyjt. 19.2.3. [3] A klnbz bejr-kategrikra hasznlhat algoritmusok megrshoz hasznljuk az iterator_traits osztlyokat. 19.2.2. [4] Ne felejtsk el hasznlni a ++ opertort az istream_iterator s az ostream_iterator objektumok esetben sem. 19.2.6. [5] A trolk tlcsordulsnak elkerlse rdekben hasznljunk beszr (inserter) bejrkat. 19.2.4. [6] Tesztels alatt vgezznk minl tbb ellenrzst, s a vgleges vltozatbl is csak azokat tvoltsuk el, melyeket felttlenl szksges. 19.3.1. [7] Ha tehetjk, hasznljuk a ++p formt a p++ helyett. 19.3. [8] Az adatszerkezeteket bvt algoritmusok hatkonysgt nvelhetjk, ha elksztetlen memriaterleteket hasznlunk. 19.4.4. [9] Ha egy algoritmus ideiglenes adatszerkezeteket hasznl, a hatkonysgot ideiglenes tr (puffer) hasznlatval nvelhetjk. 19.4.4. [10] Ktszer is gondoljuk meg, mieltt sajt memriafoglal rsba kezdnk. 19.4. [11] Kerljk a malloc(), a free(), a realloc() stb. fggvnyek hasznlatt. 19.4.6. [12] A sablonok typedef-jeinek hasznlatt a rebind fggvnynl bemutatott mdszer segtsgvel utnozhatjuk. 19.4.1.

Forrs: http://www.doksi.hu

774

A standard knyvtr

19.6. Gyakorlatok
1. (*1.5) Ksztsk el a 18.6.7. reverse() fggvnyt. Segtsg: 19.2.3. 2. (*1.5) Ksztsnk egy kimeneti bejrt, amely valjban sehova sem r. Mikor lehet rtelme egy ilyen bejrnak? 3. (*2) rjuk meg a reverse_iterator osztlyt (19.2.5). 4. (*1.5) Ksztsk el az ostream_iterator osztlyt (19.2.6). 5. (*2) Ksztsk el az istream_iterator osztlyt (19.2.6). 6. (*2.5) Fejezzk be a Checked_iter osztlyt (19.3). 7. (*2.5) Alaktsuk t a Checked_iter osztlyt gy, hogy ellenrizze az rvnytelenn vlt bejrkat is. 8. (*2) Tervezznk meg s ksztsnk el egy olyan kezelosztlyt, amely egy trolt kpes helyettesteni gy, hogy annak teljes fellett biztostja. Az brzolsban trolnunk kell egy mutatt egy trolra, valamint meg kell rnunk a trol mveleteit tartomnyellenrzssel. 9. (*2.5) Fejezzk be vagy rjuk jra a Pool_alloc (19.4.2) osztlyt gy, hogy az a standard knyvtr allocator (19.4.1) osztlynak minden lehetsgt tmogassa. Hasonltsuk ssze az allocator s a Pool_alloc hatkonysgt, s llaptsuk meg, hogy sajt rendszernkben szksg van-e a Pool_alloc hasznlatra. 10. (*2.5) Ksztsnk el egy vector osztlyt gy, hogy memriafoglalkat hasznlunk a new s a delete opertor helyett.

Forrs: http://www.doksi.hu

20
Karakterlncok
Jrt utat a jratlanrt el ne hagyj! Karakterlncok Karakterek char_traits basic_string Bejrk Elemek elrse Konstruktorok Hibakezels rtkads Koverzi sszehasonlts Beszrs sszefzs Keress s csere Mret s kapacits Karakterlncok ki- s bevitele C stlus karakterlncok Karakterek osztlyozsa A C knyvtr fggvnyei Tancsok Gyakorlatok

20.1. Bevezets
A karakterlnc (string) karakterek sorozata. A standard knyvtr string osztlya mindazokat az eljrsokat elrhetv teszi, amelyekre a karakterlncokkal kapcsolatban szksgnk lehet: indexels (20.3.3), rtkads (20.3.6), sszehasonlts (20.3.8), hozzfzs (20.3.9), sszefzs (20.3.10), rszlncok keresse (20.3.11). Nincs viszont ltalnos rszlnc-kezel, ezrt a szabvnyos string hasznlatt is bemutatand ksztnk majd egyet (20.3.11). Egy szabvnyos karakterlnc szinte brmilyen karakterek sorozata lehet (20.2).

Forrs: http://www.doksi.hu

776

A standard knyvtr

A tapasztalatok azt mutatjk, hogy nem lehet tkletes string tpust megvalstani. Ehhez az emberek zlse, elvrsaik, ignyeik tl nagy mrtkben klnbznek. gy a standard knyvtr string osztlya sem tkletes. Bizonyos tervezsi krdsekben mshogy is dnthettem volna az osztly ltrehozsakor. Ennek ellenre azt hiszem, sok elvrst kielgt s knnyen elkszthetk azok a kiegszt fggvnyek, melyekkel a tovbbi feladatok megoldhatk. Nagy elnyt jelent az is, hogy az std::string osztly ltalnosan ismert s mindenhol elrhet. Ezek a jellemzk a legtbb esetben fontosabbak, mint azok tulajdonsgok, amelyekkel az osztlyt esetleg mg kiegszthetnnk. A klnbz karakterlnc-osztlyok elksztse gyakorlsnak nagyszer (11.12, 13.2), de ha szles krben hasznlhat vltozatot akarunk, akkor a standard knyvtr string osztlyra lesz szksgnk. A C++ a C-tl rklt, nullval lezrt karaktertmbknt rtelmezett karakterlncok (vagyis a C stlus karakterlncok) kezelsre a standard knyvtrban szmos kln fggvnyt biztost (20.4.1).

20.2. Karakterek
A karakter (character) mr nmagban is rdekes fogalom. Figyeljk meg pldul a C karaktert. Ezt a C bett, amely valjban egy egyszer flkrv a papron (vagy a kpernyn), hnapokkal ezeltt gpeltem be a szmtgpembe. Ott egy 8 bites bjtban a 67 szmrtkknt troldott. Elmondhatjuk rla, hogy a latin bc harmadik betje, a hatodik atom (a szn, carbon) szoksos rvidtse s mellesleg egy programozsi nyelv neve is (1.6). A karakterlncok programokban val felhasznlsakor csak az szmt, hogy e kacskarings alakzathoz kapcsoldik egy hagyomnyos jelents, amit karakternek neveznk, s egy szmrtk, amit a szmtgp hasznl. Hogy bonyoltsuk a dolgokat, ugyanahhoz a karakterhez a klnbz karakterkszletekben ms-ms szmrtk tartozhat, st, nem is minden karakterkszletben tallunk szmrtket minden karakterhez, radsul sok klnbz karakterkszletet hasznlunk rendszeresen. A karakterkszlet nem ms, mint a karakterek (a hagyomnyos szimblumok) egy lekpezse egsz rtkekre. A C++ programozk ltalban felttelezik, hogy a szabvnyos amerikai karakterkszlet (ASCII) rendelkezsnkre ll, de a C++ felkszlt arra az esetre is, ha bizonyos karakterek hinyoznnak a programozsi krnyezetbl. Pldul ha olyan karakterek hinyoznak, mint a [ vagy a {, hasznlhatunk helyettk kulcsszavakat vagy digrf kt tagbl ll jeleket (C.3.1).

Forrs: http://www.doksi.hu

20. Karakterlncok

777

Nagy kihvst jelentenek azok a karakterkszletek is, melyekben olyan karakterek szerepelnek, amelyek az ASCII-ban nem fordulnak el. Az olyan nyelvek karakterei, mint a knai, a dn, a francia, az izlandi vagy a japn, nem rhatk le hibtlanul az ASCII karakterkszlet segtsgvel. Mg nagyobb problma, hogy az e nyelvekhez hasznlt karakterkszletek is klnbznek. A latin bct hasznl eurpai nyelvek karakterei pldul majdnem elfrnek egy 256 karakteres karakterkszletben, de sajnos a klnbz nyelvekhez klnbz kszleteket hasznlunk s ezekben nha klnbz karaktereknek ugyanaz az egsz rtk jutott. A francia karakterkszlet (amely Latin1-et hasznl) pldul nem teljesen egyeztethet ssze az izlandi karakterekkel (gy azok hasznlathoz a Latin2 kszletre van szksgnk). Azok a nagyratr ksrletek, melyek sorn megprbltak minden, emberek ltal ismert karaktert egyetlen karakterkszletben felsorolni, sok problmt megoldottak, de mg a 16 bites kszletek (pldul a Unicode) sem elgtettek ki minden ignyt. A 32 bites karakterkszletek, melyek ismereteim szerint az sszes karakter megjellsre alkalmasak lennnek, mg nem terjedtek el szles krben. A C++ alapveten azt a megkzeltst kveti, hogy a programoznak megengedjk brmelyik karakterkszlet hasznlatt a karakterlncok karaktertpusnak megadshoz. Hasznlhatunk bvtett karakterkszleteket s ms rendszerre tltethet szmkdolst is (C.3.3)

20.2.1. Karakterjellemzk
A 13.2 pontban mr bemutattuk, hogy egy karakterlnc elmletileg tetszleges tpust kpes karakterknt hasznlni, ha az megfelel msolsi mveleteket biztost. Csak azokat a tpusokat tudjuk azonban igazn hatkonyan s egyszeren kiaknzni, melyeknek nincs felhasznli msol mvelete. Ezrt a szabvnyos string osztly megkveteli, hogy a benne karakterknt hasznlt tpusnak ne legyen felhasznli msol mvelete. Ez azt is lehetv teszi, hogy a karakterlncok ki- s bevitele egyszer s hatkony legyen. Egy karaktertpus jellemzit (traits) a hozz tartoz char_traits osztly rja le, amely az albbi sablon specializcija:
template<class Ch> struct char_traits { };

Minden char_traits az std nvtrben szerepel s a szabvnyos vltozatok a <string> fejllomnybl rhetk el. Az ltalnos char_traits osztly egyetlen jellemzt sem tartalmaz, azokkal csak az egyes karaktertpusokhoz ksztett vltozatok rendelkeznek. A char_traits<char> defincija pldul a kvetkez:

Forrs: http://www.doksi.hu

778

A standard knyvtr

template<> struct char_traits<char> { typedef char char_type;

// a char_traits mveleteknek nem szabad // kivtelt kivltaniuk // a karakter tpusa // az = meghatrozsa a // char_type szmra

static void assign(char_type&, const char_type&); // a karakterek egsz rtk brzolsa typedef int int_type;

// a karakter-rtkek egsz tpusa

static char_type to_char_type(const int_type&); // talakts int-rl char-ra static int_type to_int_type(const char_type&); // talakts char-rl int-re static bool eq_int_type(const int_type&, const int_type&); // == // char_type sszehasonltsok static bool eq(const char_type&, const char_type&); static bool lt(const char_type&, const char_type&); // mveletek s[n] tmbn static char_type* move(char_type* s, const char_type* s2, size_t n); static char_type* copy(char_type* s, const char_type* s2, size_t n); static char_type* assign(char_type* s, size_t n, char_type a); static int compare(const char_type* s, const char_type* s2, size_t n); static size_t length(const char_type*); static const char_type* find(const char_type* s, int n, const char_type&); // bemeneti/kimeneti mveletek typedef streamoff off_type; typedef streampos pos_type; typedef mbstate_t state_type; // eltols az adatfolyamban // pozci az adatfolyamban // tbb bjtos adatfolyam llapota // fjl vge // i, hacsak i rtke nem eof(); // p pozcin lev karakter // llapota a tbb bjtos talakts sorn // == // <

static int_type eof(); static int_type not_eof(const int_type& i); static state_type get_state(pos_type p); };

A szabvnyos karakterlnc-sablon, a basic_string (20.3) megvalstsa e tpusokon s fggvnyeken alapul. A basic_string-hez hasznlt karaktertpusnak rendelkeznie kell egy olyan char_traits specializcival, amely mindezeket tmogatja.

Forrs: http://www.doksi.hu

20. Karakterlncok

779

Ahhoz, hogy egy tpus char_type lehessen, kpes kell legyen arra, hogy minden karakterhez hozzrendeljen egy egsz rtket, melynek tpusa int_type. Az int_type s a char_type tpus kztti talaktst a to_char_type() s a to_int_type() fggvny hajtja vgre. A char tpus esetben ez a talakts igen egyszer. A move(s,s2,n) s a copy(s,s2,n) fggvnyek egyarnt az s2 cmrl msolnak t n karaktert az s cmre, s mindketten az assign(s[i], s2[i]) utastst hasznljk. A klnbsg az, hogy a move() akkor is helyesen mkdik, ha s2 az [s,s+n[ tartomnyba esik, a copy() viszont kicsit gyorsabb. Ez a mkdsi elv pontosan megegyezik a C standard knyvtr memcpy(), illetve memmove() (19.4.6) fggvnyeinek mkdsvel. Az assign(s,n,x) fggvnyhvs az x karakter n darab msolatt rja az s cmre az assign(s[i],x) utasts segtsgvel. A compare() fggvny a lt(), illetve az eq() eljrsokat hasznlja a karakterek sszehasonltshoz. Visszatrsi rtke egy int, amely 0, ha a kt karakterlnc pontosan megegyezik; negatv szm, ha az els paramter bcsorrendben elbb kvetkezik, mint a msodik; fordtott esetben pozitv szm. A visszatrsi rtk ilyen hasznlata a C standard knyvtr strcmp() fggvnynek mkdst kveti (20.4.1). A ki- s bemenethez kapcsold fggvnyeket az alacsonyszint I/O mveletek hasznljk (21.6.4). A szles karakterek (amelyek a wchar_t osztly pldnyai, 4.3) nagyon hasonltanak az egyszer char tpushoz, de kett vagy mg tbb bjtot hasznlnak. A wchar_t tpus tulajdonsgait a char_traits<wchar_t> osztly rja le:
template<> struct char_traits<wchar_t> { typedef wchar_t char_type; typedef wint_t int_type; typedef wstreamoff off_type; typedef wstreampos pos_type; }; // mint a char_traits<char>

A wchar_t tpust elssorban a 16 bites karakterkszletek (pldul a Unicode) karaktereinek trolshoz hasznljuk.

Forrs: http://www.doksi.hu

780

A standard knyvtr

20.3. A basic_string osztly


A standard knyvtr karakterlncokhoz kapcsold szolgltatsai a basic_string sablonon (template) alapulnak, amely hasonl tpusokat s mveleteket biztost, mint a szabvnyos trolk (16.3):
template<class Ch, class Tr = char_traits<Ch>, class A = allocator<Ch> > class std::basic_string { public: // ... };

A sablon s a hozz kapcsold szolgltatsok az std nvtrhez tartoznak s a <string> fejllomnyon keresztl rhetk el. A leggyakoribb karakterlnc-tpusok hasznlatt kt typedef knnyti meg:
typedef basic_string<char> string; typedef basic_string<wchar_t> wstring;

A basic_string sok mindenben hasonlt a vector (16.3) osztlyra. A legfontosabb eltrs, hogy a basic_string nem tartalmazza az sszes eljrst, amely a vector osztlyban megtallhat, helyettk nhny, jellegzetesen karakterlncokra vonatkoz mveletet (pldul rszlnc-keresst) biztost. A string osztlyt nem rdemes egy egyszer tmbbel vagy a vector tpussal megvalstani; a karakterlncok nagyon sok felhasznlsi mdja jobban biztosthat gy, ha a msolsok mennyisgt a lehet legkevesebbre cskkentjk, nem hasznlunk dinamikus adatterletet a rvid karakterlncokhoz, a hosszabbaknl egyszer mdosthatsgot biztostunk s gy tovbb (20.6[12]). A string osztly fggvnyeinek szma jelzi a karakterlncok kezelsnek fontossgt, s azt is, hogy bizonyos szmtgpek klnleges hardverutastsokkal segtik ezeket a mveleteket. A knyvtrak kszti akkor hasznlhatjk ki legjobban az ilyen fggvnyek elnyeit, ha a standard knyvtrban tallnak hasonlkat. A standard knyvtr ms tpusaihoz hasonlan a basic_string<T> is egy konkrt tpus (2.5.3, 10.3), virtulis fggvnyek nlkl. Btran felhasznlhatjuk tagknt egy magasabb szint szvegfeldolgoz osztlyban, de arra nem val, hogy ms osztlyok bzisosztlya legyen (25.2.1, lsd mg 20.6[10]).

Forrs: http://www.doksi.hu

20. Karakterlncok

781

20.3.1. Tpusok
A vector osztlyhoz hasonlan a basic_string is tpusneveken keresztl teszi elrhetv a vele kapcsolatban ll tpusokat:
template<class Ch, class Tr = char_traits<Ch>, class A = allocator<Ch> > class basic_string { public: // tpusok (mint a vector-nl, a list-nl stb., 16.3.1) typedef Tr traits_type; // a basic_string-re jellemz

typedef typename Tr::char_type value_type; typedef A allocator_type; typedef typename A::size_type size_type; typedef typename A::difference_type difference_type; typedef typename A::reference reference; typedef typename A::const_reference const_reference; typedef typename A::pointer pointer; typedef typename A::const_pointer const_pointer; typedef megvalsts_fgg iterator; typedef megvalsts_fgg const_iterator; typedef std::reverse_iterator<iterator> reverse_iterator; typedef std::reverse_iterator<const_iterator> const_reverse_iterator; }; // ...

A basic_string az egyszer basic_string<char> tpus mellett (melyet string nven ismernk) szmos karaktertpusbl tud karakterlncot kpezni:
typedef basic_string<unsigned char> Ustring; struct Jchar { /* ... */ }; typedef basic_string<Jchar> Jstring; // japn karaktertpus

Az ilyen karakterekbl kpzett karakterlncok ugyangy hasznlhatk, mint a char tpuson alapulk, csak a karakterek szerepe szabhat hatrt:
Ustring first_word(const Ustring& us) { Ustring::size_type pos = us.find(' ');

// 20.3.11

Forrs: http://www.doksi.hu

782

A standard knyvtr

return Ustring(us,0,pos);

// 20.3.4

Jstring first_word(const Jstring& js) { Jstring::size_type pos = js.find(' '); return Jstring(js,0,pos); }

// 20.3.11 // 20.3.4

Termszetesen hasznlhatunk olyan sablonokat is, melyek karakterlnc-paramtereket hasznlnak:


template<class S> S first_word(const S& s) { typename S::size_type pos = s.find(' '); return S(s,0,pos); }

// 20.3.11 // 20.3.4

A basic_string<Ch> brmilyen karaktert tartalmazhat, amely szerepel a Ch tpusban, teht pldul a 0 (nulla) karakter is elfordulhat a karakterlnc belsejben. A Ch karaktertpusnak gy kell viselkednie, mint egy karakternek, teht nem lehet felhasznli msol konstruktora, destruktora, vagy msol rtkadsa.

20.3.2. Bejrk
A tbbi trolhoz hasonlan a string osztly is biztost nhny bejrt (itertort), melyekkel vgighaladhatunk az elemeken, akr a szoksos, akr fordtott sorrendben:
template<class Ch, class Tr = char_traits<Ch>, class A = allocator<Ch> > class basic_string { public: // ... // bejrk (mint a vector-nl, a list-nl stb., 16.3.2) iterator begin(); const_iterator begin() const; iterator end(); const_iterator end() const; reverse_iterator rbegin(); const_reverse_iterator rbegin() const; reverse_iterator rend(); const_reverse_iterator rend() const; }; // ...

Forrs: http://www.doksi.hu

20. Karakterlncok

783

Mivel a string osztlyban megtallhatk a bejrk kezelshez szksges tagtpusok s -fggvnyek, a szabvnyos algoritmusok (18. fejezet) string objektumokra is hasznlhatk:
void f(string& s) { string::iterator p = find(s.begin(),s.end(),'a'); // ... }

A karakterlncokra leggyakrabban alkalmazott mveleteket kzvetlenl a string osztlyban tallhatjuk meg. Remlhetleg ezeket a mveleteket kifejezetten a karakterlncokhoz igaztottk, gy jobbak, mint az ltalnos algoritmusok. A szabvnyos algoritmusok (18. fejezet) a karakterlncok esetben nem annyira hasznosak, mint els rnzsre gondolnnk. Az ltalnos mveletek azt felttelezik, hogy a trolk elemei nmagukban is rtelmes egysget alkotnak, de a karakterlncok esetben a teljes karaktersorozat hordozza a lnyeges informcit. Egy karakterlnc rendezse (pontosabban a karakterlnc karaktereinek rendezse) szinte teljesen megsemmisti a benne trolt informcikat, annak ellenre, hogy az ltalnos trolkban a rendezs inkbb mg hasznlhatbb szokta tenni az adatokat. A string osztly bejri sem ellenrzik, hogy rvnyes tartomnyban llnak-e.

20.3.3. Az elemek elrse


A string-ek karaktereit egyesvel is elrhetjk, indexels segtsgvel:
template<class Ch, class Tr = char_traits<Ch>, class A = allocator<Ch> > class basic_string { public: // ... // elemek elrse (mint a vector-nl, 16.3.3): const_reference operator[ ](size_type n) const; reference operator[ ](size_type n); const_reference at(size_type n) const; reference at(size_type n); }; // ... // nem ellenrztt hozzfrs // ellenrztt hozzfrs

Forrs: http://www.doksi.hu

784

A standard knyvtr

Ha az at() fggvny hasznlatakor a megengedett tartomnyon kvli indexrtket (sorszmot) adunk meg, out_of_range kivtel vltdik ki. A vector osztlytl eltren a string nem tartalmazza a front() s a back() fggvnyt. Ha a karakterlnc els vagy utols elemre akarunk hivatkozni, az s[0] vagy az s[s.length()-1] kifejezst kell hasznlnunk. A mutattmb egyenrtksg (5.3) a karakterlncok esetben nem teljesl: ha s egy string, akkor a &s[0] nem egyezik meg s rtkvel.

20.3.4. Konstruktorok
A kezdeti rtkadshoz, illetve a msolsi mveletek elvgzshez a string ms fggvnyeket knl, mint az egyb trolk (16.3.4):
template<class Ch, class Tr = char_traits<Ch>, class A = allocator<Ch> > class basic_string { public: // ... // konstruktorok stb. (csak nagyjbl gy, mint a vector-nl s a list-nl, 16.3.4) explicit basic_string(const A& a = A()); basic_string(const basic_string& s, size_type pos = 0, size_type n = npos, const A& a = A()); basic_string(const Ch* p, size_type n, const A& a = A()); basic_string(const Ch* p, const A& a = A()); basic_string(size_type n, Ch c, const A& a = A()); template<class In> basic_string(In first, In last, const A& a = A()); ~basic_string(); static const size_type npos; }; // ... // "minden karakter"

Egy string objektumnak C stlus karakterlnccal, msik string objektummal, annak egy rszvel, vagy karakterek egy sorozatval adhatunk kezdrtket, karakterrel vagy egsz rtkkel nem:
void f(char* p,vector<char>&v) { string s0; // res karakterlnc string s00 = ""; // ez is res karakterlnc

Forrs: http://www.doksi.hu

20. Karakterlncok

785

string s1 = 'a'; string s2 = 7; string s3(7); string s4(7,'a'); string s5 = "Frodo"; string s6 = s5; string s7(s5,3,2); string s8(p+7,3); string s9(p,7,3); }

// hiba: nincs konverzi char-rl string-re // hiba: nincs konverzi int-rl string-re // hiba: a konstruktornak nem lehet egy int paramtere // 7 darab 'a', vagyis "aaaaaaa" // "Frodo" msolata // s5 msolata // s5[3] s s5[4], vagyis "do" // p[7], p[8], s p[9] // string(string(p),7,3), valsznleg "kltsges"

string s10(v.begin(),v.end()); // v minden karakternek msolsa

A karakterek helyt nulltl kezdve indexeljk, teht egy karakterlnc nem ms, mint 0-tl length()-1-ig szmozott karakterek sorozata. A length() fggvny a string esetben a size() megfelelje: mindkett a karakterlncban szerepl karakterek szmt adja meg. Figyeljnk r, hogy ezek nem egy C stlus karakterlnc hosszt llaptjk meg, teht nem szmoljk a lezr nullkaraktert (20.4.1). A basic_string osztlyt a lezr karakter alkalmazsa helyett a karakterlnc hossznak trolsval illik megvalstani. A rszlncokat a kezdpozci s a karakterszm megadsval azonosthatjuk. Az alaprtelmezett npos rtke a lehet legnagyobb szm, jelentse az sszes elem. Nincs olyan konstruktor, amely n darab meghatrozatlan karakterbl hoz ltre karakterlncot. Ehhez legkzelebb taln az a konstruktor ll, amely egy adott karakter n pldnybl pt fel egy karakterlncot. Az olyan konstruktorok hinya, melyeknek egyetlen karaktert vagy csak a karakterlncban lv karakterek szmt adnnk meg, lehetv teszi, hogy a fordt szrevegyen olyan hibalehetsgeket, amilyeneket az s2 s az s3 fenti meghatrozsa rejt magban. A msol konstruktor egy ngyparamter konstruktor. E paramterek kzl hromnak alaprtelmezett rtke van. A hatkonysg rdekben kt klnll konstruktorknt is elkszthetjk; a felhasznl nem lesz kpes klnbsget tenni a kt megolds kztt, ha a lefordtott kdot meg nem nzi.

Forrs: http://www.doksi.hu

786

A standard knyvtr

A legltalnosabb konstruktor egy sablon tagfggvnye. Ez lehetv teszi, hogy a karakterlnc kezdrtkt tetszleges sorozatbl lltsuk el, pldul egy ms karaktertpust hasznl karakterlnc segtsgvel, amennyiben a karaktertpusok kztti konverzi rendelkezsnkre ll:
void f(string s) { wstring ws(s.begin(),s.end()); // ... }

// s minden karakternek msolsa

A ws karakterlnc minden wchar_t karakternek az s megfelel char eleme ad kezdrtket.

20.3.5. Hibk
A karakterlncokat igen egyszeren olvashatjuk, rhatjuk, megjelenthetjk, trolhatjuk, sszehasonlthatjuk, lemsolhatjuk stb. Ez ltalban nem okoz problmkat, legfeljebb a hatkonysggal tmadhatnak gondjaink. Ha azonban elkezdnk karakterlncok egyes rszlncaival, karaktereivel foglalkozni s gy ltez karakterlncokbl akarunk jakat sszelltani, elbb-utbb hibkat fogunk elkvetni, melynek kvetkeztben a karakterlnc hatrain kvlre prblunk meg rni. Az egyes karakterek kzvetlen elrsre szolgl at() fggvny ellenrzi az ilyen hibkat s out-of_range kivtelt vlt ki, ha rvnytelen hivatkozst adunk meg. A [ ] opertor ilyen vizsglatot nem vgez. A legtbb karakterlnc-mveletnek egy karakterpozcit s egy karakterszmot kell megadnunk. Ha a megadott pozcirtk nagyobb, mint a karakterlnc mrete, azonnal out_of_range kivtelt kapunk; ha a karakterszm tl nagy, rtelmezse ltalban az lesz, hogy az sszes htralv karaktert hasznlni akarjuk:
void f() { string s = "Snobol4"; string s2(s,100,2);

// a megadott karakterpozci a lnc vgn tl van: // out_of_range() vltdik ki string s3(s,2,100); // a karakterszm tl nagy: egyenrtk a s3(s,2,s.size()-2) // kifejezssel string s4(s,2,string::npos); // az s[2]-tl kezdd sszes karakter

Forrs: http://www.doksi.hu

20. Karakterlncok

787

A tl nagy karakterpozcikat ki kell szrnnk, de a tl nagy karakterszm hasznos lehet a programokban. Valjban az npos a lehet legnagyobb size_type tpus rtk. rdemes kiprblnunk, mi trtnik akkor, ha negatv karakterpozcit vagy karakterszmot adunk meg:
void g(string& s) { string s5(s,-2,3); // nagy pozcirtk!: out_of_range() string s6(s,3,-2); // nagy karakterszm!: rendben }

Mivel a size_type tpust arra hasznljuk, hogy pozcikat vagy darabszmokat adjunk meg, unsigned tpusknt definilt, gy a negatv szmok hasznlata csak flrevezet mdja a nagy pozitv szmok megadsnak (16.3.4). Azok a fggvnyek, amelyek egy string rszlnct keresik meg (20.3.11), az npos rtket adjk vissza, ha nem talljk meg a megfelel rszt. Teht maguk nem vltanak ki kivtelt, de ha ezt az npos rtket karakterpozciknt akarjuk felhasznlni a kvetkez mveletben, akkor mr kivtelt kapunk. Egy rszlnc kijellsnek msik mdja, hogy kt bejrt (iterator) adunk meg. Az els hatrozza meg a pozcit, mg a kt bejr klnbsge a karakterszmot. Szoks szerint a bejrk nem ellenrzttek. Ha C stlus karakterlncokat hasznlunk, a tartomnyellenrzs nehezebb feladat. Ha paramterknt adunk meg ilyen karakterlncot (teht egy char-ra hivatkoz mutatt), a basic_string fggvnyei felttelezik, hogy a mutat nem 0. Ha egy C stlus karakterlncban pozcit adunk meg, a fggvnyek elvrjk, hogy a karakterlnc elg hossz legyen a pozci rtelmezshez. Mindig legynk nagyon vatosak, st kifejezetten gyanakvak. Az egyetlen kivtel, amikor karakterliterlokat hasznlunk. Minden karakterlnc esetben igaz, hogy length()<npos. Nhny nagyon egyedi helyzetben (igen ritkn) elfordulhat, hogy egy olyan jelleg mvelet, mint egy karakterlnc beszrsa egy msikba, tl hossz karakterlncot eredmnyez, amelyet a rendszer mr nem kpes brzolni. Ebben az esetben length_error kivtel keletkezik:
string s(string::npos,'a'); // length_error() vltdik ki

Forrs: http://www.doksi.hu

788

A standard knyvtr

20.3.6. rtkads
Termszetesen a karakterlncok esetben is rendelkezsnkre ll az (egyszer) rtkads mvelete:
template<class Ch, class Tr = char_traits<Ch>, class A = allocator<Ch> > class basic_string { public: // ... // rtkads (kicsit hasonlt a vector-ra s a list-re, 16.3.4): basic_string& operator=(const basic_string& s); basic_string& operator=(const Ch* p); basic_string& operator=(Ch c); basic_string& assign(const basic_string&); basic_string& assign(const basic_string& s, size_type pos, size_type n); basic_string& assign(const Ch* p, size_type n); basic_string& assign(const Ch* p); basic_string& assign(size_type n, Ch c); template<class In> basic_string& assign(In first, In last); // ...

};

A tbbi trolhoz hasonlan a string osztlyban is rtk szerinti rtelmezs mkdik, ami azt jelenti, hogy amikor egy karakterlncot rtkl adunk egy msiknak, akkor az eredeti karakterlncrl msolat kszl, s az rtkads utn kt, ugyanolyan tartalm, de nll (rtk) karakterlnc ll majd rendelkezsnkre:
void g() { string s1 = "Knold"; string s2 = "Tot"; s1 = s2; // "Tot"-bl kt pldny lesz s2[1] = 'u'; // s2 "Tut", s1 marad "Tot" }

Annak ellenre, hogy egyetlen karakterrel nem adhatunk kezdrtket egy karakterlncnak, az ilyen mdon trtn egyszer rtkads megengedett:
void f() { string s = 'a'; s = 'a'; s = "a"; s = s; }

// hiba: kezdeti rtkads char-ral // rendben: egyszer rtkads

Forrs: http://www.doksi.hu

20. Karakterlncok

789

Az a lehetsg, hogy karakterlncnak rtkl adhatunk egy karaktert, nem tlsgosan hasznos, s sok hibalehetsget rejt magban. Gyakran azonban nlklzhetetlen, hogy egy karaktert a += mvelettel hozzfzhessnk egy karakterlnchoz (20.3.9), s igen furcsn nzne ki, ha az s+='c' utasts vgrehajthat lenne, mg az s='c' nem. Az rtkadshoz az assign() nevet hasznljuk, amely a tbbparamter konstruktorok megfeleljnek tekinthet (176.3.4, 20.3.4). A 11.12 pontban mr emltettk, hogy a string osztly optimalizlhat gy, hogy amg nincs szksg egy karakterlnc kt pldnyra, addig nem hajtjuk vgre a tnyleges msolst. A szabvnyos string felptse tmogatja az ilyen takarkosan msol megvalstsok ltrehozst, mert gy hatkonyan rhatunk le csak olvashat karakterlncokat, s a karakterlncok tadsa fggvnyek paramtereknt sokkal egyszerbben megvalsthat, mint azt els rnzsre gondolnnk. Az azonban meggondolatlansg lenne, ha egy programoz sajt fejlesztkrnyezetnek ellenrzse nlkl olyan programokat rna, amelyek a stringek optimalizlt msolsra tmaszkodnak (20.6[13]).

20.3.7. talakts C stlus karakterlncra


A 20.3.4 pontban bemutattuk, hogy a string objektumoknak val kezdeti s egyszer rtkadsra egyarnt hasznlhatunk C stlus karakterlncokat. Fordtott irny mveletek elvgzsre is van lehetsg, teht egy string karaktereit is elhelyezhetjk egy tmbben:
template<class Ch, class Tr = char_traits<Ch>, class A = allocator<Ch> > class basic_string { public: // ... // talakts C stlus karakterlncc const Ch* c_str() const; const Ch* data() const; size_type copy(Ch* p, size_type n, size_type pos = 0) const; }; // ...

A data() fggvny a string karaktereit egy tmbbe msolja, majd visszaad egy erre a tmbre hivatkoz mutatt. A tmb a string objektumhoz tartozik, teht a felhasznlnak nem szabad trlnie azt s a string egy nem const fggvnynek meghvsa utn mr nem tudhatja, milyen rtk van benne. A c_str() fggvny szinte pontosan ugyangy mkdik, csak a karakterlnc vgn elhelyez egy 0 (null) karaktert is, a C stlus lezrsnak megfelelen:

Forrs: http://www.doksi.hu

790

A standard knyvtr

void f() { string s = "equinox"; // s.length()==7 const char* p1 = s.data(); // p1 ht karakterre mutat printf("p1 = %s\n",p1); // hiba: hinyz lezr p1[2] = 'a'; // hiba: p1 konstans tmbre mutat s[2] = 'a'; char c = p1[1]; // hiba: s.data() elrsnek ksrlete s mdostsa utn const char* p2 = s.c_str(); // p2 nyolc karakterre mutat printf("p2 = %s\n",p2); // rendben: c_str() hozzadja a lezr karaktert

A klnbsget gy is megfogalmazhatjuk, hogy a data() a karakterek egy tmbjt adja vissza, mg a c_str() egy C stlus karakterlncot llt el. Ezen fggvnyek elsdleges feladata, hogy knnyen hasznlhatv tegyk az olyan fggvnyeket, melyek C stlus karakterlncot vrnak paramterknt. gy teht a c_str() fggvnyt sokkal gyakrabban hasznljuk, mint a data() eljrst:
void f(string s) { int i = atoi(s.c_str()); // ... }

// a karakterlnc int rtknek lekrse (20.4.1)

ltalban rdemes a karaktereket mindaddig egy string objektumban trolni, amg nincs rjuk szksgnk, de ha mgsem tudjuk azonnal feldolgozni, akkor is rdemes tmsolni azokat a c_str(), illetve a data() ltal lefoglalt terletrl egy kln tmbbe. A copy() fggvny pontosan erre szolgl:
char* c_string(const string& s) { char* p = new char[s.length()+1]; s.copy(p,string::npos); p[s.length()] = 0; return p; }

// megjegyzs: +1 // megjegyzs: lezr hozzadsa

Az s.copy(p,n,m) fggvny legfeljebb n karaktert msol a p cmre az s[m] pozcitl kezdve. Ha az s karakterlncbl n-nl kevesebb karaktert lehet csak tmsolni, a copy() egyszeren az sszes karaktert tmsolja.

Forrs: http://www.doksi.hu

20. Karakterlncok

791

Figyeljnk r, hogy a string objektumokban szerepelhet a 0 karakter. A C stlus karakterlncokat kezel fggvnyek az els ilyen karaktert tekintik lezrnak. Teht mindig figyeljnk, hogy 0 karaktert csak akkor hasznljunk, ha C stlust hasznl fggvnyekre nem lesz szksgnk, vagy a 0-kat pontosan oda tegyk, ahol a karakterlncot le szeretnnk zrni. A C stlus karakterlncra val talaktst a c_str() helyett megoldhattuk volna egy operator const char* () mvelettel is, az automatikus konverzi knyelmnek azonban az lenne az ra, hogy meglepetsnkre idnknt akkor is vgbemenne, amikor nem is szmtunk r. Ha gy rezzk, hogy programunkban sokszor lesz szksg a c_str() fggvnyre, valsznleg tlsgosan ragaszkodunk a C stlus fellethez. ltalban rendelkezsnkre llnak azok az eszkzk, melyekkel a C stlus karakterlncokra vonatkoz mveletek kzvetlenl string objektumokon is elvgezhetk. Ezek hasznlatval sok konverzit elkerlhetnk. Egy msik lehetsges megolds az explicit konverzik elkerlsre az, hogy tlterheljk azokat a fggvnyeket, melyek a c_str() hasznlatra knyszertenek bennnket:
extern "C" int atoi(const char*); int atoi(const string& s) { return atoi(s.c_str()); }

20.3.8. sszehasonlts
Karakterlncokat azonos tpus karakterlncokkal vagy olyan karaktertmbkkel hasonlthatunk ssze, melyek szintn ugyanolyan tpus karaktereket tartalmaznak:
template<class Ch, class Tr = char_traits<Ch>, class A = allocator<Ch> > class basic_string { public: // ... int compare(const basic_string& s) const; int compare(const Ch* p) const; // > s == hasznlata egytt

int compare(size_type pos, size_type n, const basic_string& s) const; int compare(size_type pos, size_type n, const basic_string& s, size_type pos2, size_type n2) const; int compare(size_type pos, size_type n, const Ch* p, size_type n2 = npos) const; }; // ...

Forrs: http://www.doksi.hu

792

A standard knyvtr

Ha a compare() meghvsakor a pozci s mret paramtereket is megadjuk, csak a kijellt rszsorozat vesz rszt az sszehasonltsban. Pldul s.compare(pos,n,s2) egyenrtk string(s,pos,n).compare(s2)-vel. Az sszehasonlt eljrst a char_traits<Ch> osztly compare() fggvnye adja (20.2.1). gy az s.compare(s2) fggvny 0 rtket ad vissza, ha a karakterlncok egyenl rtkek; negatv szmot kapunk, ha s bcsorrendben s2 eltt ll; fordtott esetben pedig pozitv lesz az eredmny. A felhasznl itt nem adhat meg gy sszehasonltsi felttelt, mint a 13.4 pontban. Ha ilyen szint rugalmassgra van szksgnk, a lexicographical_compare() (18.9) segtsgvel ksztsnk a fenti rszben levhz hasonl sszehasonlt fggvnyt. Egy msik lehetsg, hogy sajt ciklust runk a feladat megoldsra. A toupper() fggvny (20.4.2) pldul lehetv teszi, hogy kis- s nagybetkkel nem foglalkoz sszehasonltst valstsunk meg:
int cmp_nocase(const string& s, const string& s2) { string::const_iterator p = s.begin(); string::const_iterator p2 = s2.begin(); while (p!=s.end() && p2!=s2.end()) { if (toupper(*p)!=toupper(*p2)) return (toupper(*p)<toupper(*p2)) ? -1 : 1; ++p; ++p2; } } return (s2.size()==s.size()) ? 0 : (s.size()<s2.size()) ? -1 : 1; // 'size' eljel nlkli

void f(const string& s, const string& s2) { if (s == s2) { // kis- s nagybetket figyelembe vev sszehasonlts s s s2 kztt // ... } if (cmp_nocase(s,s2) == 0) { } } // ... // ... // kis- s nagybetket figyelmen kvl hagy // sszehasonlts s s s2 kztt

A basic_string osztlyban rendelkezsnkre llnak a szoksos sszehasonlt opertorok is (==, !=, <, >, <=, >=):

Forrs: http://www.doksi.hu

20. Karakterlncok

793

template<class Ch, class Tr, class A> bool operator==(const basic_string<Ch,Tr,A>&, const basic_string<Ch,Tr,A>&); template<class Ch, class Tr, class A> bool operator==(const Ch*, const basic_string<Ch,Tr,A>&); template<class Ch, class Tr, class A> bool operator==(const basic_string<Ch,Tr,A>&, const Ch*); // s ugyanilyen deklarcik a !=, >, <, >=, s a <= szmra

Az sszehasonlt opertorok nem tag fggvnyek, gy a konverzik mindkt operandusra ugyangy vonatkoznak (11.2.3). A C stlus karakterlncokat hasznl vltozatokra azrt volt szksg, hogy a literlokkal val sszehasonltst hatkonyabb tegyk:
void f(const string& name) { if (name =="Obelix" || "Asterix"==name) { // ... } }

// optimalizlt == hasznlata

20.3.9. Beszrs
Miutn ellltottunk egy karakterlncot, sokfle mveletet vgezhetnk vele. A karakterlnc rtkt mdost fggvnyek kzl taln a legfontosabb a hozzfzs, amely a karakterlnc vgn helyez el j karaktereket. Az ltalnos beszr mveletekre ritkbban van szksg:
template<class Ch, class Tr = char_traits<Ch>, class A = allocator<Ch> > class basic_string { public: // ... // karakterek hozzadsa (*this)[length()-1] utn basic_string& operator+=(const basic_string& s); basic_string& operator+=(const Ch* p); basic_string& operator+=(Ch c); void push_back(Ch c); basic_string& append(const basic_string& s); basic_string& append(const basic_string& s, size_type pos, size_type n); basic_string& append(const Ch* p, size_type n);

Forrs: http://www.doksi.hu

794

A standard knyvtr

basic_string& append(const Ch* p); basic_string& append(size_type n, Ch c); template<class In> basic_string& append(In first, In last); // karakterek beszrsa (*this)[pos] el basic_string& basic_string& basic_string& basic_string& basic_string& insert(size_type pos, const basic_string& s); insert(size_type pos, const basic_string& s, size_type pos2, size_type n); insert(size_type pos, const Ch* p, size_type n); insert(size_type pos, const Ch* p); insert(size_type pos, size_type n, Ch c);

// karakterek beszrsa p el iterator insert(iterator p, Ch c); void insert(iterator p, size_type n, Ch c); template<class In> void insert(iterator p, In first, In last); }; // ...

Nagyjbl ugyanazok a fggvnyvltozatok llnak rendelkezsnkre a beszr s a hozzfz eljrsoknl is, mint a konstruktorok s az rtkads esetben. A += opertor hagyomnyos jellse a hozzfzsnek:
string complete_name(const string& first_name, const string& family_name) { string s = first_name; s += ' '; s += family_name; return s; }

A karakterlnc vghez val hozzfzs jelentsen hatkonyabb lehet, mint a ms pozcikra val beszrs:
string complete_name2(const string& first_name, const string& family_name) // szegnyes algoritmus { string s = family_name; s.insert(s.begin(),' '); s.insert(0,first_name); return s; }

Forrs: http://www.doksi.hu

20. Karakterlncok

795

A beszrs gyakran arra knyszerti a string-et, hogy lass memriamveleteket vgezzen s thelyezzen nhny karaktert. Mivel a string osztlyban is szerepel a push_back() mvelet (16.3.5), a back_inserter ugyangy hasznlhat string objektumokhoz, mint brmely ltalnos trolhoz.

20.3.10. sszefzs
A hozzfzs klnleges vltozata az sszefzsnek (konkatencinak). Az sszefzst teht egy karakterlnc ellltst gy, hogy kt msikat egyms utn helyeznk a + opertor valstja meg:
template<class Ch, class Tr, class A> basic_string<Ch,Tr,A> operator+(const basic_string<Ch,Tr,A>&, const basic_string<Ch,Tr,A>&); template<class Ch, class Tr, class A> basic_string<Ch,Tr,A> operator+(const Ch*, const basic_string<Ch,Tr,A>&); template<class Ch, class Tr, class A> basic_string<Ch,Tr,A> operator+(Ch, const basic_string<Ch,Tr,A>&); template<class Ch, class Tr, class A> basic_string<Ch,Tr,A> operator+(const basic_string<Ch,Tr,A>&, const Ch*); template<class Ch, class Tr, class A> basic_string<Ch,Tr,A> operator+(const basic_string<Ch,Tr,A>&, Ch);

Szoks szerint, a + mveletet nem tag fggvnyknt adjuk meg. A tbb paramtert hasznl sablonok esetben ez nmi kellemetlensget okoz, mert a sablonparamtereket meg kell ismtelnnk. Msrszt az sszefzs hasznlata egyszer s knyelmes:
string complete_name3(const string& first_name, const string& family_name) { return first_name + ' ' + family_name; }

Ez a knyelem megr egy kis futsi idej teljestmnyvesztesget a complete_name() fggvnyhez viszonytva. A complete_name3() fggvnyben kln ideiglenes vltozra (11.3.2) van szksgnk. Vlemnyem szerint ez ritkn fontos, de azrt rdemes tudnunk

Forrs: http://www.doksi.hu

796

A standard knyvtr

rla, ha egy nagy ciklust runk egy olyan programban, ahol figyelnnk kell a teljestmnyre. Ilyenkor esetleg rdemes megszntetnnk a fggvnyhvst a complete_name() helyben (inline) kifejtsvel, az eredmnyknt kapott karakterlncot gy helyben pthetjk fel, alacsonyabb szint mveletek segtsgvel (20.6[14]).

20.3.11. Keress
Zavarba ejt mennyisgben llnak rendelkezsnkre olyan fggvnyek, melyekkel egy karakterlnc rszlncait kereshetjk meg:
template<class Ch, class Tr = char_traits<Ch>, class A = allocator<Ch> > class basic_string { public: // ... // rszsorozat kerse (mint a search(), 18.5.5) size_type find(const basic_string& s, size_type i = 0) const; size_type find(const Ch* p, size_type i, size_type n) const; size_type find(const Ch* p, size_type i = 0) const; size_type find(Ch c, size_type i = 0) const; // rszsorozat keresse visszafel (mint a find_end(), 18.5.5) size_type rfind(const basic_string& s, size_type i = npos) const; size_type rfind(const Ch* p, size_type i, size_type n) const; size_type rfind(const Ch* p, size_type i = npos) const; size_type rfind(Ch c, size_type i = npos) const; // karakter keresse (mint a find_first_of(), 18.5.2) size_type find_first_of(const basic_string& s, size_type i = 0) const; size_type find_first_of(const Ch* p, size_type i, size_type n) const; size_type find_first_of(const Ch* p, size_type i = 0) const; size_type find_first_of(Ch c, size_type i = 0) const; // karakter keresse paramter alapjn visszafel size_type find_last_of(const basic_string& s, size_type i = npos) const; size_type find_last_of(const Ch* p, size_type i, size_type n) const; size_type find_last_of(const Ch* p, size_type i = npos) const; size_type find_last_of(Ch c, size_type i = npos) const; // paramterben nem szerepl karakter keresse

Forrs: http://www.doksi.hu

20. Karakterlncok

797

size_type find_first_not_of(const basic_string& s, size_type i = 0) const; size_type find_first_not_of(const Ch* p, size_type i, size_type n) const; size_type find_first_not_of(const Ch* p, size_type i = 0) const; size_type find_first_not_of(Ch c, size_type i = 0) const; // paramterben nem szerepl karakter keresse visszafel size_type find_last_not_of(const basic_string& s, size_type i = npos) const; size_type find_last_not_of(const Ch* p, size_type i, size_type n) const; size_type find_last_not_of(const Ch* p, size_type i = npos) const; size_type find_last_not_of(Ch c, size_type i = npos) const; // ...

};

Az sszes fenti fggvny const. Teht arra hasznlhatk, hogy valamilyen clbl megkeressenek egy rszlncot, de maguk nem vltoztatjk meg azt a karakterlncot, amelyre alkalmaztuk azokat. A basic_string::find mveletek jelentst gy rthetjk meg legjobban, ha megrtjk a velk egyenrtk ltalnos algoritmusokat:
void f() { string s = "accdcde"; typedef ST; string::size_type i1 = s.find("cd"); string::size_type i2 = s.rfind("cd"); string::size_type i3 = s.find_first_of("cd"); string::size_type i4 = s.find_last_of("cd"); string::size_type i5 = s.find_first_not_of("cd"); string::size_type i6 = s.find_last_not_of("cd"); // i1 = 2 // i2 = 4 // i3 = 1 // i4 = 5 // i5 = 0 // i6 = 6 s[2]=='c' && s[3]=='d' s[4]=='c' && s[5]=='d' s[1] == 'c' s[5] == 'd' s[0]!='c' && s[0]!='d' s[6]!='c' && s[6]!='d'

Ha a find() fggvnyek nem talljk meg, amit kellene, npos rtket adnak vissza, amely rvnytelen karakterpozcit jelent. Ha az npos rtket karakterpozciknt hasznljuk, range_error kivtelt kapunk (20.3.5). Figyeljnk r, hogy a find() eredmnye unsigned rtk.

20.3.12. Csere
Miutn meghatroztunk egy karakterpozcit a karakterlncban, az indexels segtsgvel az egyes karaktereket mdosthatjuk, de a replace() mvelet felhasznlsval lecserlhetnk teljes rszlncokat is:

Forrs: http://www.doksi.hu

798

A standard knyvtr

template<class Ch, class Tr = char_traits<Ch>, class A = allocator<Ch> > class basic_string { public: // ... // [ (*this)[i], (*this)[i+n] [ felcserlse ms karakterekkel basic_string& replace(size_type i, size_type n, const basic_string& s); basic_string& replace(size_type i, size_type n, const basic_string& s, size_type i2, size_type n2); basic_string& replace(size_type i, size_type n, const Ch* p, size_type n2); basic_string& replace(size_type i, size_type n, const Ch* p); basic_string& replace(size_type i, size_type n, size_type n2, Ch c); basic_string& replace(iterator i, iterator i2, const basic_string& s); basic_string& replace(iterator i, iterator i2, const Ch* p, size_type n); basic_string& replace(iterator i, iterator i2, const Ch* p); basic_string& replace(iterator i, iterator i2, size_type n, Ch c); template<class In> basic_string& replace(iterator i, iterator i2, In j, In j2); // karakterek trlse a lncbl ("csere semmire") basic_string& erase(size_type i = 0, size_type n = npos); iterator erase(iterator i); iterator erase(iterator first, iterator last); void clear(); // az sszes karakter trlse // ...

};

Az j karakterek szmnak nem kell megegyeznie a karakterlncban eddig szerepl karakterek szmval. A karakterlnc mrete gy vltozik, hogy az j rszlnc pontosan elfrjen benne. Valjban az erase() fggvny sem csinl mst, mint hogy a megadott rszlncot res karakterlncra cserli s az eredeti karakterlnc mrett megfelelen mdostja:
void f() { string s = "Mrpedig ez mkdik, ha elhiszed, ha nem."; s.erase(0,8); // a "Mrpedig " trlse s.replace(s.find("ez"),2,"Csak akkor"); s.replace(s.find(", ha nem"),8,""); // trls "" behelyettestsvel }

Az erase() fggvny egyszer, paramter nlkli meghvsa a teljes karakterlncot res karakterlncc alaktja. Ezt a mveletet az ltalnos trolk esetben a clear() fggvny valstja meg (16.3.6).

Forrs: http://www.doksi.hu

20. Karakterlncok

799

A replace() fggvny vltozatai az rtkads vltozataihoz igazodnak, hiszen a replace() valjban nem ms, mint rtkads egy rszlncnak.

20.3.13. Rszlncok
A substr() fggvny lehetv teszi, hogy kivlasszunk egy rszlncot, amelyet kezdpozcijval s hosszval adunk meg:
template<class Ch, class Tr = char_traits<Ch>, class A = allocator<Ch> > class basic_string { public: // ... // rszlnc cmzse basic_string substr(size_type i = 0, size_type n = npos) const; // ...

};

A substr() fggvny egyszer mdszert biztost a karakterlnc egy rszletnek kiolvassra. Ebbl a szemszgbl a prja a replace() fggvny, mellyel fellrhatjuk a karakterlnc egy rszt. Mindkt fggvnynek a kezdpozcit s a rszlnc hosszt kell megadnunk. A find() segtsgvel viszont mr rtk szerint is megtallhatunk rszlncokat, gy ez a fggvnycsoport lehetv teszi, hogy definiljunk egy rszlnc-osztlyt, amelyet rni s olvasni is tudunk:
template<class Ch> class Basic_substring { public: typedef typename basic_string<Ch>::size_type size_type; Basic_substring(basic_string<Ch>& s, size_type i, size_type n); Basic_substring(basic_string<Ch>& s, const basic_string<Ch>& s2); Basic_substring(basic_string<Ch>& s, const Ch* p); Basic_substring& Basic_substring& Basic_substring& Basic_substring& operator=(const basic_string<Ch>&); operator=(const Basic_substring<Ch>&); operator=(const Ch*); operator=(Ch); // s[i]..s[i+n-1] // s2 az s-ben // *p az s-ben // rs *ps-be

operator basic_string<Ch>() const; operator const Ch* () const;

// olvass *ps-be // a c_str() hasznlata

Forrs: http://www.doksi.hu

800

A standard knyvtr

private: basic_string<Ch>* ps; size_type pos; size_type n; };

Az osztly megvalstsa is nagyon egyszer:


template<class Ch> Basic_substring<Ch>::Basic_substring(basic_string<Ch>& s, const basic_string<Ch>& s2) : ps(&s), n(s2.length()) { pos = s.find(s2); } template<class Ch> Basic_substring<Ch>& Basic_substring<Ch>::operator=(const basic_string<Ch>& s) { ps->replace(pos,n,s); // rs *ps-be return *this; } template<class Ch> Basic_substring<Ch>::operator basic_string<Ch>() const { return basic_string<Ch>(*ps,pos,n); // msols *ps-be }

Ha s2 nem tallhat meg az s karakterlncban, pos rtke npos lesz, gy ha egy ilyen rszlncot prblunk meg rni vagy olvasni, range_error kivtelt kapunk (20.3.5). A Basic_substring osztly a kvetkez formban hasznlhat:
typedef Basic_substring<char> Substring; void f() { string s = "Mary had a little lamb"; Substring(s,"lamb") = "fun"; Substring(s,"a little") = "no"; string s2 = "Joe" + Substring(s,s.find(' '),string::npos); }

Termszetesen sokkal rdekesebb feladatokat is elvgezhetnnk, ha a Substring osztly mintaillesztst is tudna vgezni (20.6[7]).

Forrs: http://www.doksi.hu

20. Karakterlncok

801

20.3.14. Mret s kapacits


A memriakezelssel kapcsolatos krdseket a string nagyjbl ugyangy kezeli, mint a vector (16.3.8):
template<class Ch, class Tr = char_traits<Ch>, class A = allocator<Ch> > class basic_string { public: // ... // mret, kapacits stb. (mint a 16.3.8 pontban) size_type size() const; size_type max_size() const; size_type length() const { return size(); } bool empty() const { return size()==0; } void resize(size_type n, Ch c); void resize(size_type n) { resize(n,Ch()); } size_type capacity() const; void reserve(size_type res_arg = 0); }; allocator_type get_allocator() const; // mint a vector, 16.3.8 // mint a vector, 16.3.8 // karakterek szma (20.3.4) // a legnagyobb lehetsges karakterlnc

A reverse(res_arg) fggvny length_error kivtelt vlt ki, ha res_arg>max_size().

20.3.15. Ki- s bemeneti mveletek


A string osztly egyik legfontosabb felhasznlsi terlete, hogy bemeneti mveletek clja, illetve kimeneti mveletek forrsa legyen. A basic_string I/O opertorait a <string> (s nem az <iostream>) tartalmazza:
template<class Ch, class Tr, class A> basic_istream<Ch,Tr>& operator>>(basic_istream<Ch,Tr>&, basic_string<Ch,Tr,A>&); template<class Ch, class Tr, class A> basic_ostream<Ch,Tr>& operator<<(basic_ostream<Ch,Tr>&,const basic_string<Ch,Tr,A>&); template<class Ch, class Tr, class A> basic_istream<Ch,Tr>& getline(basic_istream<Ch,Tr>&, basic_string<Ch,Tr,A>&, Ch eol); template<class Ch, class Tr, class A> basic_istream<Ch,Tr>& getline(basic_istream<Ch,Tr>&, basic_string<Ch,Tr,A>&);

Forrs: http://www.doksi.hu

802

A standard knyvtr

A << a karakterlncot egy kimeneti adatfolyamra (ostream, 21.2.1) rja, mg a >> opertor egy reshely karakterekkel hatrolt szt olvas be a karakterlncba egy bemeneti adatfolyamrl (3.6, 21.3.1). A sz eltti szkz, tabultor, jsor stb. karaktereket egyszeren tugorja s a sz utn kvetkez ilyen karakterek sem kerlnek bele a karakterlncba. A getline() fggvny meghvsval egy teljes, eol karakterrel lezrt sort olvashatunk be a karakterlncba, a karakterlnc mrete pedig gy nvekszik, hogy a sor elfrjen benne (3.6). Ha nem adjuk meg az eol paramtert, a fggvny az '\n' jsor karaktert hasznlja. A sorzr karaktert beolvassa az adatfolyamrl, de a karakterlncba nem kerl be. Mivel a string mrete gy vltozik, hogy a beolvasott sor elfrjen benne, nincs szksg arra, hogy a lezr karaktert az adatfolyamban hagyjuk vagy visszaadjuk a beolvasott karakterek szmt a hvnak. A karaktertmbk esetben a get() s a getline() knytelen volt ilyen, ellenrzst segt megoldsokat alkalmazni (21.3.4).

20.3.16. Felcserls
Ugyangy, mint a vector-nl (16.3.9), a swap() fggvnybl a karakterlncokhoz is sokkal hatkonyabb specializcit kszthetnk, mint az ltalnos algoritmus:
template<class Ch, class Tr, class A> void swap(basic_string<Ch,Tr,A>&, basic_string<Ch,Tr,A>&);

20.4. A C standard knyvtra


A C++ standard knyvtra rklte a C stlus karakterlncok kezelsvel foglalkoz fggvnyeket. Ebben a pontban felsorolunk nhnyat a legfontosabb, C karakterlncokat kezel fggvnyek kzl. A lers egyltaln nem terjed ki mindre; ha rszletes informcikat szeretnnk megtudni, nzzk meg fejlesztrendszernk kziknyvt. Legynk vatosak ezen fggvnyek hasznlatakor, mert a knyvtrak kszti gyakran sajt, nem szabvnyos fggvnyeket adnak meg a szabvnyos fejllomnyokban, gy nehz megllaptani, hogy melyek a minden rendszerben elrhet fggvnyek. A C standard knyvtrnak lehetsgeit ler fejllomnyok listjt a 16.1.2 pontban adtuk meg. A memriakezel fggvnyekkel a 19.4.6 pontban foglalkozunk, a C ki- s bemeneti fggvnyeivel a 21.8 rszben, a C matematikai knyvtrval pedig a 22.3 pontban.

Forrs: http://www.doksi.hu

20. Karakterlncok

803

A program indtsval s befejezsvel kapcsolatos fggvnyeket a 3.2 s a 9.4.1.1 pontban mutattuk be, mg a meghatrozatlan fggvnyparamterek beolvasst segt fggvnyeket a 7.6 pont trgyalta. Azon C stlus fggvnyek, melyek a szles karaktereket kezelik, a <cwchar> s a <wchar.h> fejllomnyban tallhatk.

20.4.1. C stlus karakterlncok


A C stlus karakterlncok kezelsvel foglalkoz fggvnyek a <string.h> s a <cstring> fejllomnyban tallhatk:
char* char* char* char* strcpy(char* p, const char* q); // msols q-bl p-be (a lezrval egytt) strcat(char* p, const char* q); // hozzfzs q-bl p-be (a lezrval egytt) strncpy(char* p, const char* q, int n); // n karakter msolsa q-bl p-be strncat(char* p, const char* q, int n); // n karakter hozzfzse q-bl p-be // p hossza (a lezr nlkl) // p s q sszehasonltsa // az els n karakter sszehasonltsa // az els c keresse p-ben // az utols c keresse p-ben // az els q keresse p-ben

size_t strlen(const char* p); int strcmp(const char* p, const char* q); int strncmp(const char* p, const char* q, int n); char* strchr(char* p, int c); const char* strchr(const char* p, int c); char* strrchr(char* p, int c); const char* strrchr(const char* p, int c); char* strstr(char* p, const char* q); const char* strstr(const char* p, const char* q);

char* strpbrk(char* p, const char* q); // q els karakternek keresse p-ben const char* strpbrk(const char* p, const char* q); size_t strspn(const char* p, const char* q); size_t strcspn(const char* p, const char* q); nem szerepl karakter eltt // p karaktereinek szma az els, // q-ban szerepl karakter eltt // p karaktereinek szma az els, q-ban

A megadott mutatkat a fggvnyek nem-nullknak felttelezik, azoknak a char tmbknek pedig, melyekre a mutatk mutatnak, 0 karakterrel lezrt sorozatoknak kell lennik. Az strn fggvnyek 0 karakterekkel tltik fel a hinyz terletet, ha nincs n darab feldolgozand karakter. A karakterlnc-sszehasonltsok nullt adnak vissza, ha a kt karakterlnc egyenl; negatv szmot, ha az els paramter bcsorrendben elbb kvetkezik, mint a msodik; fordtott esetben pedig pozitv szmot.

Forrs: http://www.doksi.hu

804

A standard knyvtr

Termszetesen a C nem biztost tlterhelt fggvnyprokat, a C++-ban azonban szksg van ezekre a const biztonsgos megvalstshoz:
void f(const char* pcc, char* pc) // C++ { *strchr(pcc,'a') = 'b'; // hiba: const char-nak nem adhatunk rtket *strchr(pc,'a') = 'b'; // rendben, br nem tkletes: lehet, hogy pc-ben nincs 'a' }

A C++ strchr() fggvnye nem engedi meg, hogy const karakterlncba rjunk, egy C program azonban kihasznlhatja a C-beli strchr() gyengbb tpusellenrzst:
char* strchr(const char* p, int c); /* C standard knyvtrbeli fggvny, nem C++ */

void g(const char* pcc, char* pc) /* C, a C++ nem fordtja le */ { *strchr(pcc,'a') = 'b'; /* const talaktsa nem const-t: C-ben j, C++-ban hiba */ *strchr(pc,'a') = 'b'; /* j C-ben s C++-ban is */ }

Ha tehetjk, felttlenl kerljk a C stlus karakterlncok hasznlatt s hasznljuk helyettk a string osztlyt. A C stlus karakterlncok s a hozzjuk tartoz szabvnyos fggvnyek segtsgvel nagyon hatkony programokat rhatunk, de mg a gyakorlott C s C++ programozk is gyakran kvetnek el buta kis hibkat mikzben ezekkel dolgoznak. Persze minden C++ programoz tallkozni fog olyan rgebbi programokkal, melyek ezeket a lehetsgeket hasznljk. me egy haszontalan kis plda, amellyel a leggyakoribb fggvnyeket mutatjuk be:
void f(char* p, char* q) { if (p==q) return; if (strcmp(p,q)==0) { int i = strlen(p); // ... } char buf[200]; strcpy(buf,p); strncpy(buf,p,200); } // ...

// a mutatk egyenrtkek // a karakterlnc-rtkek egyenrtkek // a karakterek szma (a lezr nlkl)

// p msolsa a buf-ba (a lezrval egytt) // nem tkletes, tlcsordulhat // 200 karakter msolsa p-bl a buf-ba // nem tkletes; lehet, hogy nem msolja t a lezrt

Forrs: http://www.doksi.hu

20. Karakterlncok

805

A C stlus karakterlncok ki- s bevitelt ltalban a printf fggvnycsald segtsgvel valstjuk meg (21.8). Az <stdlib.h> s a <cstdlib> fejllomnyban a standard knyvtr nhny olyan hasznos fggvnyt nyjt, melyekkel szmrtket jell karakterlncokat alakthatunk szmm:
double atof(const char* p); int atoi(const char* p); long atol(const char* p); // p talaktsa double-l // p talaktsa int-t // p talaktsa long-g

A bevezet reshely karaktereket ezek a fggvnyek nem veszik figyelembe. Ha a karakterlnc nem szmot brzol, a visszatrsi rtk 0 lesz. (Az atoi(ht) eredmnye is 0!) Ha a karakterlnc szmot jell, de az nem brzolhat az eredmnyknt vrt szmtpusban, akkor az errno (16.1.2, 22.3) vltoz rtke ERANGE lesz, a visszatrsi rtk pedig egy nagyon nagy vagy nagyon kicsi szm, a konvertlt rtknek megfelelen.

20.4.2. Karakterek osztlyozsa


A <ctype.h> s a <cctype> fejllomnyban a standard knyvtr olyan hasznos fggvnyeket knl, melyek az ASCII, illetve a hasonl karakterkszletek karaktereit osztlyozzk:
int isalpha(int); int isupper(int); int islower(int); int isdigit(int); int isxdigit(int); int isspace(int); int iscntrl(int); int ispunct(int); int isalnum(int); int isprint(int); int isgraph(int); int toupper(int c); int tolower(int c); // 'a'..'z' 'A'..'Z' betk, C-hez (20.2.1, 21.7) // 'A'..'Z' nagybetk, C-hez (20.2.1, 21.7) // 'a'..'z' kisbetk, C-hez (20.2.1, 21.7) // tzes szmrendszer szmok: '0'..'9' // hexadecimlis szmok: '0'..'9' vagy 'a'..'f' vagy 'A'..'F' // ' ', '\t', '\v', kocsivissza, jsor, fggleges tabultor // vezrlkarakter (ASCII 0..31 s 127) // kzpontozs, a fentiek egyike sem tartozik bele // isalpha() | isdigit() // nyomtathat karakterek: ascii ' '..'~' // isalpha() | isdigit() | ispunct() // c nagybets megfelelje // c kisbets megfelelje

A fentieket ltalban egyszer kiolvasssal valstjk meg, gy, hogy a karaktert indexknt hasznljk egy tblban, amely a karakterek jellemzit trolja. Ezrt az albbi kifejezs amellett, hogy tlsgosan hossz s sok hibalehetsget rejt magban (pldul egy EBCDIC karakterkszletet hasznl rendszerben nem alfabetikus karakterek is teljestik a felttelt), nem is hatkony:

Forrs: http://www.doksi.hu

806

A standard knyvtr

if (('a'<=c && c<='z') || ('A'<=c && c<='Z')) { // bet // ... }

Ezek a fggvnyek egy int paramtert vrnak, s az tadott egsznek vagy brzolhatnak kell lennie egy unsigned char tpussal vagy az EOF rtknek kell lennie (ami ltalban a 1). Ez az rtelmezs problmt jelenthet az olyan rendszerekben, ahol a char eljeles tpus (lsd: 20.6[11]). Ugyanilyen szerep fggvnyek rendelkezsre llnak szles karakterekre is, a <cwtype> s a <wtype.h> fejllomnyban.

20.5. Tancsok
[1] A C stlus fggvnyek helyett lehetleg hasznljuk a string osztly eljrsait. 20.4.1. [2] A string lehet vltoz vagy tag, de bzisosztlynak nem val. 20.3, 25.2.1. [3] A karakterlncokat rdemes rtk szerint tadni s visszaadni, mert gy a rendszerre bzzuk a memriakezelst. 20.3.6. [4] A bejrk s a [ ] opertor helyett hasznljuk az at() fggvnyt, ha tartomnyellenrzsre van szksgnk. 20.3.2, 20.3.5. [5] A bejrk s a [ ] opertor gyorsabb, mint az at() fggvny. 20.3.2, 20.3.5. [6] Kzvetve vagy kzvetlenl, a rszlncok olvasshoz hasznljuk a substr(), rsukhoz a replace() fggvnyt. 20.3.12, 20.3.13. [7] Adott rtk megkeresshez egy karakterlncban hasznljuk a find() fggvnyt (s ne rjunk sajt ciklust erre a feladatra). 20.3.1. [8] Ha hatkonyan akarunk egy karakterlncot karakterekkel bvteni, hasznljuk a hozzfzs mveleteit. 20.3.9. [9] Ha az id nem ltfontossg szempont, a karakterbevitelre hasznljunk string objektumokat. 20.3.1.5. [10] Hasznljuk a string::npos rtket a karakterlnc htralv rsznek jelzsre. 20.3.5. [11] Ha gyakran hasznlunk egy alacsonyszint szolgltatst, akkor azt ksztsk el a string osztlyhoz, ne hasznljunk emiatt mindenhol alacsonyszint adatszerkezeteket. 20.3.10.

Forrs: http://www.doksi.hu

20. Karakterlncok

807

[12] Ha a string osztlyt hasznljuk, valahol mindig kapjuk el a range_error s out_of_range kivteleket. 20.3.5. [13] Figyeljnk r, hogy ne adjunk t char* mutatt 0 rtkkel egy karakterlnckezel fggvnynek. 20.3.7. [14] Ha nagyon nagy szksgnk van r, a c_str() fggvny segtsgvel egy string objektumbl elllthatjuk a C stlus karakterlnc megfeleljt. 20.3.7. [15] Hasznljuk az isalpha(), isdigit() stb. fggvnyeket, ha a karaktereket osztlyoznunk kell, ne rjunk sajt ellenrzseket a karakterrtkek alapjn. 20.4.2.

20.6. Gyakorlatok
A fejezet feladatainak tbbsghez a standard knyvtr brmely vltozatnak forrskdjban tallhatunk megoldst, mieltt azonban megnznnk, hogy a knyvtr alkoti hogyan kzeltettk meg a problmt, prbljunk sajt megoldst keresni. 1. (*2) Ksztsnk egy fggvnyt, amely kt string objektumot vr paramterknt s egy jabb karakterlncot ad vissza, amelyben a kt karakterlnc sszefzse szerepel gy, hogy kzttk egy pont ll. A file s a write karakterlncokbl pldul a fggvny a file.write eredmnyt lltsa el. Oldjuk meg ugyanezt a feladatot C stlus karakterlncokkal s a C lehetsgeinek (pldul a malloc() s az strlen() fggvnyek) felhasznlsval. Hasonltsuk ssze a kt fggvnyt. Milyen fontos sszehasonltsi szempontokat kell megvizsglnunk? 2. (*2) Foglaljuk ssze egy listban a vector s a basic_string osztly kztti klnbsgeket. Mely klnbsgek igazn fontosak? 3. (*2) A karakterlnc-kezel lehetsgek nem teljesen szablyosak. Egy karaktert (char) pldul rtkl adhatunk egy karakterlncnak, de kezdeti rtkadsra nem hasznlhatjuk. Ksztsnk egy listt ezekrl a szablytalansgokrl. Melyeket kszblhettk volna ki ezek kzl anlkl, hogy a karakterlncok hasznlatt tlbonyoltottuk volna? Milyen ms szablytalansgokat eredmnyezne ez az talakts? 4. (*1.5) A basic_string osztlynak nagyon sok tagfggvnye van. Melyeket emelhetnnk ki az osztlybl s tehetnnk nem tag fggvnny ezek kzl anlkl, hogy a hatkonysgot s knyelmes hasznlhatsgot feladnnk? 5. (*1.5) rjuk meg a back_inserter() (19.2.4) azon vltozatt, amely a basic_string osztllyal mkdik. 6. (*2) Fejezzk be a 20.3.13 pontban bemutatott Basic_substring osztly megvalstst s ptsk be ezt egy olyan String tpusba, amely tlterheli a () opertort a rszlnca jelentssel, de egybknt ugyangy viselkedik, mint a string.

Forrs: http://www.doksi.hu

808

A standard knyvtr

7. (*2.5) Ksztsnk egy find() fggvnyt, amely egy egyszer szablyos (regulris) kifejezs els elfordulst keresi meg egy string objektumban. A kifejezsben a ? egyetlen, tetszleges karaktert jelent, a * akrhny olyan karaktert, amely a kifejezs tovbbi rszre nem illeszthet, az [abc] azon karakterek brmelyikt, amely a megadott halmazban szerepel (esetnkben a, vagy b, vagy c). A tbbi karakter nmagt brzolja. A find(s,name:) pldul egy olyan mutatt ad vissza, amely a name: els elfordulsra mutat, mg a find(s,[nN]ame(*)) kifejezs azt a rszlncot jelli ki az s karakterlncban, amelynek elejn a name vagy a Name sz szerepel, majd zrjelek kztt egy (esetleg res) karaktersorozat. 8. (*2.5) Gondolkodjunk el rajta, hogy az elbbi (20.6[7]), regulris kifejezsilleszt fggvnynkbl milyen lehetsgek hinyoznak mg. Hatrozzuk meg s valstsuk meg ezeket. Hasonltsuk ssze az ltalunk ltrehozott fggvny kifejezkpessgt egy szles krben elterjedt regulris kifejezs-illeszt kifejezkpessgvel. Hasonltsuk ssze a kt eljrst hatkonysg szempontjbl is. 9. (*2.5) Egy regulris kifejezs-knyvtr segtsgvel ksztsnk mintailleszt mveleteket egy String osztlyban, amelyhez egy Substring osztly is tartozik. 10. Kpzeljnk el egy idelis osztlyt az ltalnos szveg-feldolgozsi feladatok elvgzshez. Legyen az osztly neve Text. Milyen lehetsgeket kell megvalstanunk? Milyen korltozsokat s kltsgeket knyszertenek az osztlyra az ltalunk elkpzelt idelis mveletek? 11. (*1.5) Hatrozzuk meg az isalpha(), isdigit() stb. fggvnyek tlterhelseit gy, hogy megfelelen mkdjenek char, unsigned char s signed char tpusra is. 12. (*2.5) Ksztsnk egy String osztlyt, amelyet arra optimalizlunk, hogy nyolc karakternl rvidebb karakterlncokat kezeljen. Hasonltsuk ssze ennek hatkonysgt a 11.12 pont String osztlyval, illetve a szabvnyos string osztllyal. Kszthet-e olyan karakterlnc osztly, amely egyesti a nagyon rvid karakterlncokra optimalizlt s a tkletesen ltalnos vltozat elnyeit? 13. (*2) Mrjk le egy string msolsnak hatkonysgt. Megfelelen optimalizlt sajt fejlesztrendszernk string osztlya a msolsra? 14. (*2.5) Hasonltsuk ssze a 20.3.9 s a 20.3.10 pontban bemutatott hrom complete_name() fggvny hatkonysgt. Prbljuk elkszteni a complete_name() fggvny azon vltozatt, amely a lehet leggyorsabban fut. Jegyezzk fel azokat a hibkat, amelyeket a megvalsts s tesztels kzben tapasztaltunk. 15. (*2.5) Kpzeljk el, hogy rendszernk legknyesebb mvelete a kzepes hosszsg (5-25 karakteres) karakterlncok beolvassa a cin adatfolyamrl. rjunk egy bemeneti fggvnyt, amely az ilyen karakterlncokat tudja beolvasni olyan gyorsan, ahogy csak el tudjuk kpzelni. Dnthetnk gy, hogy a fgg-

Forrs: http://www.doksi.hu

20. Karakterlncok

809

vny felletnek knyelmes hasznlatt felldozzuk a sebessg rdekben. Hasonltsuk ssze eljrsunk hatkonysgt a string osztly >> opertornak hatkonysgval. 16. (*1.5) rjuk meg az itos(int) fggvnyt, amely a paramterknt megadott int rtk string brzolst adja vissza.

Forrs: http://www.doksi.hu

21
Adatfolyamok
Csak azt kapod, amit ltsz (Brian Kernighan) Kimenet s bemenet Kimeneti adatfolyamok Beptett tpusok kimenete Felhasznli tpusok kimenete Virtulis kimeneti fggvnyek Bemeneti adatfolyamok Beptett tpusok bemenete Formzatlan bemenet Adatfolyamok llapota Felhasznli tpusok bemenete I/O kivtelek Adatfolyamok sszektse rszemek Egsz s lebegpontos kimenet formzsa Mezk s igazts Mdostk Szabvnyos mdostk Felhasznli mdostk Fjlfolyamok Adatfolyamok lezrsa Karakterlnc-folyamok Adatfolyamok s tmeneti trolk Helyi sajtossgok Adatfolyam-visszahvsok printf() Tancsok Gyakorlatok

21.1. Bevezet
Egy programozsi nyelvben ltalnos be- s kimeneti (input/output, I/O) rendszert megvalstani rendkvl nehz. Rgebben az I/O lehetsgek csak arra korltozdtak, hogy nhny beptett adattpust kezeljenek. A legegyszerbbek kivtelvel azonban a C++ programok szmos felhasznli tpust is hasznlnak, gy e tpusok ki- s bemenett is meg kell oldani. Egy I/O rendszernek a rugalmassg s hatkonysg mellett egyszernek, knyel-

Forrs: http://www.doksi.hu

812

A standard knyvtr

mesnek, biztonsgosnak s mindenekfelett tfognak kell lennie. Eddig mg senki nem llt el olyan megoldssal, amellyel mindenki elgedett lenne, ezrt lehetv kell tennnk, hogy a felhasznl sajt ki- s bemeneti eszkzket kszthessen, illetve a szabvnyos lehetsgeket egyedi felhasznlsi terletek kezelsvel bvthesse ki. A C++ olyan nyelv, melyben a felhasznl j tpusokat adhat meg s e tpusok hasznlata ugyanolyan hatkony s knyelmes lehet, mint a beptett adattpusok. Ezrt logikus elvrs a C++ nyelv I/O szolgltatsaitl, hogy C++ nyelven kszljenek s csak olyan eszkzket hasznljanak, melyek minden programoz szmra elrhetk. Az itt bemutatott, adatfolyam-bemenetet s -kimenetet kezel lehetsgek az e kvetelmnyek kielgtsre tett erfesztsek eredmnyei: 21.2 Kimenet: A kimenet alatt az alkalmazsprogramoz azt rti, hogy tetszleges tpus (int, char* vagy Employee_record) objektumokbl karaktersorozatot llthat el. Ebben a pontban azokat a lehetsgeket ismertetjk, melyekkel a beptett s a felhasznli tpusok kimenete megvalsthat. Bemenet: Azok az eszkzk, melyekkel karaktereket, karakterlncokat vagy ms (akr beptett, akr felhasznli) tpusok rtkeit beolvashatjuk. Formzs: A kimenet elrendezsvel kapcsolatban gyakran klnleges elvrsaink vannak. Az int rtkeket pldul ltalban tzes szmrendszerben szeretjk kirni, a mutatk hagyomnyos formja a hexadecimlis (tizenhatos szmrendszer) alak, a lebegpontos szmokat pedig megadott pontossggal kell megjelenteni. Ez a rsz a formzssal s az azt segt programozsi eszkzkkel foglalkozik. Fjlok s adatfolyamok: Alaprtelmezs szerint minden C++ program hasznlhatja a szabvnyos adatfolyamokat: a szabvnyos kimenetet (cout), a szabvnyos bemenetet (cin) s a hibakimenetet (cerr). Ha ms eszkzket vagy fjlokat akarunk hasznlni, akkor adatfolyamokat kell ltrehoznunk s ezeket hozz kell ktnnk a megfelel eszkzhz, illetve fjlhoz. Ez a rsz a fjlok megnyitsnak s bezrsnak, illetve az adatfolyamok fjlokhoz s karakterlncokhoz val hozzktsnek mdszereivel foglalkozik. tmeneti trols: Hatkony ki- s bemenetet gy lehet megvalstani, ha tmeneti trakat (puffereket) hasznlunk. A mdszer alkalmazhat mindkt oldalon, gy az tmeneti trbl kiolvashatjuk s oda rhatjuk is az adatokat. Ebben a pontban az tmeneti trols (pufferels) alapmdszereit mutatjuk be. Helyi sajtossgok: A locale objektum azt hatrozza meg, hogy a szmokat ltalban milyen formban jelentjk meg, milyen karaktereket tekintnk betnek s gy tovbb. Teht az adott kultrra jellemz specialit-

21.3 21.4

21.5

21.6

21.7

Forrs: http://www.doksi.hu

21. Adatfolyamok

813

21.8

sokat foglalja ssze. Az I/O rendszer automatikusan hasznlja ezeket az informcikat, gy ez a rsz csak nagy vonalakban foglalkozik a tmval. C stlus I/O: Itt a C <stdio.h> fejllomnynak printf() fggvnyt s a C knyvtrnak a C++ <iostream> knyvtrhoz fzd viszonyt mutatjuk be.

Ahhoz, hogy az adatfolyam-knyvtrat hasznljuk, nem kell rtennk a megvalstshoz hasznlt sszes mdszert, mr csak azrt sem, mert a klnbz C++-vltozatokban klnbz mdszereket alkalmazhatnak. Egy szp I/O rendszer megvalstsa azonban komoly kihvst jelent. Megrsa kzben szmos olyan lehetsggel megismerkedhetnk, amelyet ms programozsi s tervezsi feladatok megoldsban is felhasznlhatunk. Ezrt nagyon fontos, hogy megismerjk azokat a mdszereket, melyekkel egy ki- s bemeneti rendszer elkszthet. Ebben a fejezetben csak addig a mlysgig mutatjuk be az adatfolyamok bemeneti/kimeneti rendszert, hogy pontosan megrtsk annak szerkezett, felhasznlhassuk a leggyakoribb I/O mveletek vgrehajtshoz, s ki tudjuk egszteni gy, hogy az ltalunk ltrehozott j tpusokat is kpes legyen kezelni. Ha szabvnyos vagy j tpus adatfolyamokat akarunk hasznlni, esetleg sajt helyi sajtossgokat akarunk meghatrozni, a knyvtr forrskdjra, j rendszerlersra, s mkd pldaprogramokra is szksgnk lesz az itt lert informcikon kvl. Az adatfolyam I/O rendszer sszetevit az albbi brval szemlltethetjk: ios_base: helyi sajtossgoktl fggetlen llapot basic_ios<>: helyi sajtossgoktl fgg llapot, adatfolyam-llapot

basic_streambuf<>: tmeneti trols

locale: formzsi informcik

karakterek tmeneti trolsa basic_iostream<>: formzs (<<, >> stb.), ltrehozs/felszmols

tnyleges cl/forrs

Forrs: http://www.doksi.hu

814

A standard knyvtr

A basic_iostream<> fell indul pontozott nyl azt jelzi, hogy a basic_ios<> egy virtulis bzisosztly, az egyszer vonalak mutatkat brzolnak. Azon osztlyok, melyek neve utn a <> jel szerepel, olyan sablonok, melyek paramtere egy karaktertpus s tartalmaznak egy locale objektumot is. Az adatfolyamok (streams) s az hozzjuk hasznlt ltalnos jelrendszer szmos kommunikcis problma megoldst lehetv teszik. Adatfolyamokat hasznlhatunk objektumok gpek kztti tvitelre (25.4.1), zenetfolyamok titkostshoz (21.10[22]), adattmrtshez, objektumok lland (perzisztens) trolshoz s gy tovbb. Ennek ellenre az albbiakban csak az egyszer, karakterkzpont ki- s bemenetet mutatjuk be. Az adatfolyam ki- s bemenethez kapcsold osztlyok s sablonok deklarcijt (melyek elg informcit adnak ahhoz, hogy hivatkozzunk rjuk, de ahhoz nem, hogy mveleteket hajtsunk vgre rajtuk), valamint a szabvnyos tpus-meghatrozsokat (typedef-eket) az <iosfwd> fejllomnyban tallhatjuk meg. Erre a fjlra nha szksgnk lesz, amikor ms I/O fejllomnyokat de nem az sszeset hasznlni akarunk.

21.2. Kimenet
A beptett s a felhasznli tpusok egysges s tpusbiztos kezelse egyszeren megvalsthat, ha tlterheljk a kimeneti fggvnyeket:
put(cerr,"x = "); // cerr a hibazenetek kimeneti adatfolyama put(cerr,x); put(cerr,'\n');

A paramter tpusa hatrozza meg, melyik put() fggvny kerl meghvsra az adott helyzetben. Ezt a megoldst sok nyelv alkalmazza, br sok ismtlst kvn. Ha az rd ki mvelet megvalstshoz a << opertort terheljk tl, szebb jellst kapunk s lehetsget adunk a programoznak, hogy objektumok sorozatt egyetlen utastssal rja ki:
cerr << "x = " << x << '\n';

Ha x egy int tpus vltoz, melynek rtke 123, akkor az elz parancs a szabvnyos hibakimeneti adatfolyamra (cerr) az albbi szveget rja ki (egy jsor karakterrel lezrva):
x = 123

Forrs: http://www.doksi.hu

21. Adatfolyamok

815

Ugyanakkor, ha x tpusa complex (22.5), rtke pedig (1,2.4), a kirt szveg az albbi lesz:
x = (1,2.4)

Ez a stlus mindaddig mkdkpes, amg x tpusra a << mvelet definilt, mrpedig a felhasznl knnyedn kszthet ilyen fggvnyt sajt tpusaihoz. Ha el akarjuk kerlni a kimeneti fggvny hasznlatbl ered knyelmetlen megfogalmazst, mindenkppen szksgnk van egy kimeneti opertorra. De mirt pont a << opertort vlasszuk? Arra nincs lehetsgnk, hogy j nyelvi elemet vezessnk be (11.2). Az rtkad opertor hasznlhat lenne a ki- s a bemenet jellsre is, de gy tnt, a legtbb programoz klnbz opertort szeretne hasznlni a kimenethez s a bemenethez. Radsul az = rossz irnyba kt: a cout=a=b jelentse cout=(a=b), mg neknk a (cout=a)=b rtelmezsre lenne szksgnk (6.2). Prblkoztam a < s a > opertor hasznlatval is, de az emberek tudatban ez a kt jel annyira a kisebb, mint s nagyobb, mint jelentshez kapcsoldik, hogy az j I/O utastsok teljesen olvashatatlanok voltak. A << s a >> opertort nem hasznljuk olyan gyakran a beptett tpusokra, hogy ez problmt okozzon programjaink olvashatsgban. Elg szimmetrikusak ahhoz, hogy jelkpezzk a kimenet s a bemenet mveletet. Amikor ezeket az opertorokat ki- s bemenetre hasznljuk, a << jelenti a kirst, a >> pedig a beolvasst. Azok, akik jobban szeretik a mszaki kifejezseket, nevezhetik ezt a kt mveletet sorrendben output-nak s inputnak, esetleg beszrsnak s kinyersnek (inserter, extractor). A << opertornak a precedencia sorrendben elfoglalt helye elg alacsony ahhoz, hogy zrjelezs nlkl lehetv tegye a paramterekben aritmetikai mveletek elvgzst:
cout << "a*b+c=" << a*b+c << '\n';

Ha azonban olyan mveletet akarunk hasznlni, amely precedencija alacsonyabban helyezkedik el, mint a << opertor, akkor nem szabad elfelejtennk a zrjeleket:
cout << "a^b|c=" << (a^b|c) << '\n';

A balra lptet opertor (6.2.4) szintn hasznlhat kimeneti utastsban, de termszetesen ezt is zrjelek kz kell rnunk:
cout << "a<<b=" << (a<<b) << '\n';

Forrs: http://www.doksi.hu

816

A standard knyvtr

21.2.1. Kimeneti adatfolyamok


Az ostream arra val, hogy tetszleges tpus rtkeket karakterek sorozatv alaktsunk. Ezutn ezeket a karaktereket ltalban alacsonyszint kimeneti mveletekkel rjuk ki. Sokfle karakter ltezik (20.2) s ezeket egy-egy char_traits objektum jellemzi (20.2.1). Ebbl kvetkezik, hogy az ostream egy bizonyos tpus karakterre specializlt vltozata a basic_ostream sablonnak:
template <class Ch, class Tr = char_traits<Ch> > class std::basic_ostream : virtual public basic_ios<Ch,Tr> { public: virtual ~basic_ostream(); // ... };

A sablon s a hozz tartoz mveletek az std nvtrben szerepelnek s az <ostream> fejllomnybl rhetk el, amely az <iostream> fejllomny kimenettel kapcsolatos rszeit tartalmazza. A basic_ostream paramterei meghatrozzk, hogy a megvalsts milyen tpus karaktereket hasznl, de nem rgztik a kirhat objektumok tpust. Az egyszer char tpust s a szles karaktereket hasznl adatfolyamokat viszont minden vltozat kzvetlenl tmogatja:
typedef basic_ostream<char> ostream; typedef basic_ostream<wchar_t> wostream;

Sok rendszerben a szles karakterek (wide character) rsa olyan szinten optimalizlhat a wostream osztly segtsgvel, amelyhez a kimeneti egysgknt bjtot hasznl adatfolyamok nehezen rhetnek fel. Lehetsg van olyan adatfolyamok meghatrozsra is, melyekben a fizikai ki- s bemenetet nem karakterszinten valstjuk meg, ezek az adatfolyamok azonban meghaladjk a standard knyvtr hatkrt, gy ebben a knyvben nem foglalkozunk velk (21.10[15]). A basic_ios bzisosztly az <ios> fejllomnyban tallhat. Ez kezeli a formzst (21.4), a helyi sajtossgokat (21.7) s az tmeneti trak elrst (21.6) is. Az egysges jellsrendszer rdekben nhny tpust is meghatroz:

Forrs: http://www.doksi.hu

21. Adatfolyamok

817

template <class Ch, class Tr = char_traits<Ch> > class std::basic_ios : public ios_base { public: typedef Ch char_type; typedef Tr traits_type; typedef typename Tr::int_type int_type; // a karakter egsz rtknek tpusa typedef typename Tr::pos_type pos_type; // pozci az tmeneti trban typedef typename Tr::off_type off_type; // eltols az tmeneti trban }; // ... lsd mg 21.3.3, 21.3.7, 21.4.4, 21.6.3 s 21.7.1. ...

A basic_ios osztly letiltja a msol konstruktort s az rtkad opertort. Ebbl kvetkezik, hogy az ostream s istream objektumok nem msolhatk. Ha egy adatfolyam clja megvltozik, knytelenek vagyunk vagy az tmeneti trat kicserlni (21.6.4) vagy mutatkat hasznlni (6.1.7). Az ios_base bzisosztly olyan informcikat s mveleteket tartalmaz, amelyek fggetlenek a hasznlt karaktertpustl. (Ilyen pldul a lebegpontos szmok kimeneti pontossga.) Ezrt ez az osztly nem kell, hogy sablon legyen. Az ios_base osztlyban szerepl tpus-meghatrozsokon kvl az adatfolyam I/O knyvtr egy eljeles egsz tpust is hasznl, melynek neve streamsize s az egy I/O mvelettel tvitt karakterek szmt, illetve az tmeneti tr mrett adhatjuk meg a segtsgvel. Emellett rendelkezsnkre ll a streamoff is, amely az adatfolyamokban s az tmeneti trakban val eltolst (offset) brzolja. Az <iostream> fejllomny tbb szabvnyos adatfolyamot is megad:
ostream cout; ostream cerr; ostream clog; wostream wcout; wostream wcerr; wostream wclog; // karakterek szabvnyos kimeneti adatfolyama // hibazenetek szabvnyos, nem pufferelt kimeneti adatfolyama // hibazenetek szabvnyos kimeneti adatfolyama // a cout-nak megfelel "szles" adatfolyam // a cerr-nek megfelel "szles" adatfolyam // a clog-nak megfelel "szles" adatfolyam

A cerr s a clog adatfolyamok ugyanarra a kimeneti eszkzre mutatnak, a klnbsg kztk csak a hasznlt tmeneti tr tpusa. A cout ugyanarra az eszkzre r, mint a C stdout (21.8), mg a cerr s a clog ugyanarra, mint a C stderr. Ha szksg van r, a programoz tovbbi adatfolyamokat is ltrehozhat (21.5).

Forrs: http://www.doksi.hu

818

A standard knyvtr

21.2.2. Beptett tpusok kimenete


Az ostream osztly megadja a << (kimenet) opertort, amellyel a beptett tpusok kirst vgezhetjk el:
template <class Ch, class Tr = char_traits<Ch> > class basic_ostream : virtual public basic_ios<Ch,Tr> { public: // ... basic_ostream& operator<<(short n); basic_ostream& operator<<(int n); basic_ostream& operator<<(long n); basic_ostream& operator<<(unsigned short n); basic_ostream& operator<<(unsigned int n); basic_ostream& operator<<(unsigned long n); basic_ostream& operator<<(float f); basic_ostream& operator<<(double f); basic_ostream& operator<<(long double f); basic_ostream& operator<<(bool n); basic_ostream& operator<<(const void* p); basic_ostream& put(Ch c); // c kirsa basic_ostream& write(const Ch* p, streamsize n); }; // ... // mutatrtk kirsa // p[0]..p[n-1]

A put() s write() fggvnyek egyszeren karaktereket rnak ki, gy az erre a mveletre szolgl << opertornak nem kell tagnak lennie. A karakter-operandust vr operator<<() fggvnyek a put() segtsgvel nem tagknt kszthetk el:
template <class Ch, class Tr> basic_ostream<Ch, Tr>& operator<<(basic_ostream<Ch, Tr>&, Ch); template <class Ch, class Tr> basic_ostream<Ch, Tr>& operator<<(basic_ostream<Ch, Tr>&, char); template <class Tr> basic_ostream<char, Tr>& operator<<(basic_ostream<char, Tr>&, char); template <class Tr> basic_ostream<char, Tr>& operator<<(basic_ostream<char, Tr>&, signed char); template <class Tr> basic_ostream<char, Tr>& operator<<(basic_ostream<char, Tr>&, unsigned char);

Forrs: http://www.doksi.hu

21. Adatfolyamok

819

A << a nullval lezrt karaktertmbk kirshoz is rendelkezsre ll:


template <class Ch, class Tr> basic_ostream<char, Tr>& operator<<(basic_ostream<Ch, Tr>&, const Ch*); template <class Ch, class Tr> basic_ostream<char, Tr>& operator<<(basic_ostream<Ch, Tr>&, const char*); template <class Tr> basic_ostream<char, Tr>& operator<<(basic_ostream<char, Tr>&, const char*); template <class Tr> basic_ostream<char, Tr>& operator<<(basic_ostream<char, Tr>&, const signed char*); template <class Tr> basic_ostream<char, Tr>& operator<<(basic_ostream<char, Tr>&, const unsigned char*);

A string-ek kimeneti opertorait a <string> tartalmazza (20.3.15). Az operator <<() mvelet egy referencit ad vissza ugyanarra az ostream objektumra, amelyre meghvtuk, gy azonnal alkalmazhatjuk r a kvetkez operator <<() mveletet:
cerr << "x = " << x;

Itt x egy int, az utasts jelentse pedig a kvetkez:


(cerr.operator<<("x = ")).operator<<(x);

Fontos kvetkezmny, hogy amikor tbb elemet ratunk ki egyetlen kimeneti utastssal, azok a megfelel sorrendben, balrl jobbra haladva jelennek meg:
void val(char c) { cout << "int('" << c << "') = " << int(c) << '\n'; } int main() { val('A'); val('Z'); }

Egy ASCII karaktereket hasznl rendszerben a fenti programrszlet eredmnye a kvetkez lesz:
int('A') = 65 int('Z') = 90

Forrs: http://www.doksi.hu

820

A standard knyvtr

Megfigyelhetjk, hogy a karakterliterlok tpusa char (4.3.1), gy a cout<<Z a Z bett fogja kirni, nem annak int rtkt, a 90-et. Ha logikai (bool) rtket ratunk ki, az alaprtelmezs szerint 0 vagy 1 formban jelenik meg. Ha ez nem megfelel szmunkra, bellthatjuk az <iomanip> fejllomnyban (21.4.6.2) meghatrozott boolalpha formzsjelzt, gy a true vagy a false szveg jelenik meg:
int main() { cout << true << ' ' << false << '\n'; cout << boolalpha; cout << true << ' ' << false << '\n'; }

// a true s false szimbolikus brzolsa

A kirt szveg:
10 true false

Pontosabban fogalmazva a boolalpha tulajdonsg azt biztostja, hogy a bool rtkeknek a helyi sajtossgoknak (locale) megfelel rtkt kapjuk. Ha n megfelelen belltom a locale-t (21.7), a kvetkez eredmnyt kapom:
10 sandt falsk

A lebegpontos szmok formzsrl, az egszek szmrendszerrl stb. a 21.4. pontban lesz sz. Az ostream::operator<<(const void*) fggvny egy mutat rtkt jelenti meg, az ppen hasznlt szmtgp felptsnek megfelel formban:
int main() { int i = 0; int* p = new int; cout << "local " << &i << ", free store " << p << '\n'; }

Forrs: http://www.doksi.hu

21. Adatfolyamok

821

Az eredmny az n gpemen a kvetkez:


local 0x7fffead0, free store 0x500c

Ms rendszerek mskppen jelenthetik meg a mutat rtkt.

21.2.3. Felhasznli tpusok kimenete


Kpzeljk el az albbi, felhasznli complex tpust (11.3):
class complex { public: double real() const { return re; } double imag() const { return im; } // ... };

A << opertort a kvetkezkppen definilhatjuk az j complex szmra:


ostream& operator<<(ostream&s, const complex& z) { return s << '(' << z.real() << ',' << z.imag() << ')'; }

Ezutn a << mveletet pontosan ugyangy hasznlhatjuk sajt tpusunkra, mint a beptett tpusokra:
int main() { complex x(1,2); cout << "x = " << x << '\n'; }

Az eredmny:
x = (1,2)

A felhasznli tpusok kimeneti mveleteinek megvalstshoz nem kell megvltoztatnunk az ostream osztly deklarcijt. Ez azrt szerencss, mert az ostream osztly az <iostream> fejllomnyban definilt, amit a felhasznlk jobb ha nem vltoztatnak meg (er-

Forrs: http://www.doksi.hu

822

A standard knyvtr

re nincs is lehetsgk). Az, hogy az ostream bvtst nem tettk lehetv, vdelmet nyjt az adatszerkezet vletlen mdostsa ellen is, s lehetv teszi, hogy anlkl vltoztassuk meg az ostream osztly megvalstst, hogy a felhasznli programokra hatssal lennnk.

21.2.3.1. Virtulis kimeneti fggvnyek Az ostream tagfggvnyei nem virtulisak. Azok a kimeneti mveletek, melyeket egy programoz valst meg, nem az osztly rszei, gy ezek sem lehetnek virtulisak. Ennek egyik oka az, hogy az egyszer mveletek esetben (pldul egyetlen karakter tmeneti trba rsa) gy kzel optimlis sebessget rhetnk el. Ez olyan terlet, ahol a futsi idej hatkonysg rendkvl fontos s szinte ktelez helyben kifejtett (inline) eljrsokat kszteni. A virtulis fggvnyek hasznlata it arra vonatkozik, hogy a lehet legnagyobb rugalmassgot biztostsuk azon mveletek szmra, melyek kifejezetten a tmeneti tr tlcsordulsval, illetve alulcsordulsval foglalkoznak (21.6.4). Ennek ellenre a programozk gyakran kvnnak megjelenteni olyan objektumokat, melyeknek csak egy bzisosztlya ismert. Mivel a pontos tpust nem ismerjk, a megfelel kimenetet nem llthatjuk el gy, hogy egyszeren minden j tpushoz megadjuk a << mveletet. Ehelyett kszthetnk egy virtulis kimeneti fggvnyt az absztrakt bzisosztlyban:
class My_base { public: // ... }; virtual ostream& put(ostream& s) const = 0; // *this kirsa s-re

ostream& operator<<(ostream& s, const My_base& r) { return r.put(s); // a megfelel put() hasznlata }

Teht a put() egy olyan virtulis fggvny, amely biztostja, hogy a megfelel kimeneti mvelet kerljn vgrehajtsra a << opertorban. Ennek felhasznlsval a kvetkez programrszletet rhatjuk.
class Sometype : public My_base { public: // ...

Forrs: http://www.doksi.hu

21. Adatfolyamok

823

ostream& put(ostream& s) const; };

// az igazi kimeneti fggvny: My_base::put() // fellrsa

void f(const My_base& r, Sometype& s) // hasznljuk a << opertort a // megfelel put() hvshoz { cout << r << s; }

Ezzel a virtulis put() fggvny bepl az ostream osztly s a << mvelet ltal biztostott keretbe. A mdszer jl alkalmazhat minden olyan esetben, amikor egy virtulis fggvnyknt mkd mveletre van szksgnk, de futsi idben a msodik paramter alapjn akarunk vlasztani.

21.3. Bemenet
A bemenetet a kimenethez nagyon hasonlan kezelhetjk. Az istream osztly biztostja a >> bemeneti opertort nhny ltalnos tpushoz, a felhasznli tpusokhoz pedig kszthetnk egy-egy operator>>() fggvnyt.

21.3.1. Bemeneti adatfolyamok


A basic_ostream (21.2.1) osztllyal prhuzamosan az <istream> fejllomnyban megtallhatjuk a basic_istream osztlyt, amely az <iostream> bemenettel kapcsolatos rszeit tartalmazza:
template <class Ch, class Tr = char_traits<Ch> > class std::basic_istream : virtual public basic_ios<Ch,Tr> { public: virtual ~basic_istream(); }; // ...

A basic_ios bzisosztlyt a 21.2.1. pontban mutattuk be.

Forrs: http://www.doksi.hu

824

A standard knyvtr

A cin s a wcin kt szabvnyos bemeneti adatfolyam, melyeket az <iostream> fejllomny r le:


typedef basic_istream<char> istream; typedef basic_istream<wchar_t> wistream; istream cin; wistream wcin; // karakterek szabvnyos bemeneti adatfolyama // szabvnyos bemeneti adatfolyam wchar_t rszre

A cin adatfolyam ugyanazt a forrst hasznlja, mint a C stdin adatfolyama (21.8).

21.3.2. Beptett tpusok bemenete


A beptett tpusokhoz az istream biztostja a >> opertort:
template <class Ch, class Tr = char_traits<Ch> > class basic_istream : virtual public basic_ios<Ch,Tr> { public: // ... // formzott bevitel: basic_istream& operator>>(short& n); basic_istream& operator>>(int& n); basic_istream& operator>>(long& n); basic_istream& operator>>(unsigned short& u); basic_istream& operator>>(unsigned int& u); basic_istream& operator>>(unsigned long& u); basic_istream& operator>>(float& f); basic_istream& operator>>(double& f); basic_istream& operator>>(long double& f); basic_istream& operator>>(bool& b); basic_istream& operator>>(void*& p); }; // ... // olvass n-be

// olvass u-ba

// olvass f-be

// olvass b-be // mutatrtk olvassa p-be

Az operator>>() fggvnyek a kvetkez stlust kvetik:


istream& istream::operator>>(T& tvar) { // reshely karaktereket tlpni, // T egy tpus, melyre az istream::operator>> // deklarlt

Forrs: http://www.doksi.hu

21. Adatfolyamok

825

// majd valahogyan beolvasni egy T tpus elemet `tvar'-ba return *this;

Mivel a >> tugorja az reshely (whitespace) karaktereket, az ilyenekkel elvlasztott egszeket az albbi egyszer ciklussal olvashatjuk be:
int read_ints(vector<int>& v) // feltlti v-t , visszatr a beolvasott egszek szmval { int i = 0; while (i<v.size() && cin>>v[i]) i++; return i; }

Ha a bemeneten egy nem egsz rtk jelenik meg, a bemeneti mvelet hibba tkzik, gy a ciklus is megszakad. Pldul ezt a bemenetet felttelezve:
1 2 3 4 5.6 7 8.

a read_ints() fggvny t egsz szmot fog beolvasni:


12345

A mvelet utn a bemeneten a kvetkez beolvashat karakter a pont lesz. A nem lthat reshely (whitespace) karakterek meghatrozsa itt is ugyanaz, mint a szabvnyos C-ben (szkz, tabultor, jsor, fggleges tabultorra illeszts, kocsivissza), s a <cctype> fejllomnyban lev isspace() fggvnnyel ellenrizhetk valamely karakterre (20.4.2). Az istream objektumok hasznlatakor a leggyakoribb hiba, hogy a bemenet nem egszen abban a formtumban rkezik, mint amire felkszltnk, ezrt a bemenet nem trtnik meg. Ezrt mieltt hasznlni kezdennk azokat az rtkeket, melyeket remnyeink szerint beolvastunk, ellenriznnk kell a bemeneti adatfolyam llapott (21.3.3), vagy kivteleket kell hasznlnunk (21.3.6). A bemenethez hasznlt formtumot a helyi sajtossgok (locale) hatrozzk meg (21.7). Alaprtelmezs szerint a logikai rtkeket a 0 (hamis) s az 1 (igaz) rtk jelzi, az egszeket tzes szmrendszerben kell megadnunk, a lebegpontos szmok formja pedig olyan, ahogy a C++ programokban rhatjuk azokat. A basefield (21.4.2) tulajdonsg belltsval lehetsg van arra is, hogy a 0123 szmot a 83 tzes szmrendszerbeli szm oktlis alakjaknt, a 0xff bemenetet pedig a 255 hexadecimlis alakjaknt rtelmezzk. A mutatk beolvasshoz hasznlt formtum teljesen az adott nyelvi vltozattl fgg (nzznk utna, sajt fejlesztrendszernk hogyan mkdik).

Forrs: http://www.doksi.hu

826

A standard knyvtr

Meglep mdon nincs olyan >> tagfggvny, mellyel egy karaktert olvashatnnk be. Ennek oka az, hogy a >> opertor a karakterekhez a get() karakter-beolvas fggvny (21.3.4) segtsgvel knnyen megvalsthat, gy nem kell tagfggvnyknt szerepelnie. Az adatfolyamokrl sajt karaktertpusaiknak megfelel karaktereket olvashatunk be. Ha ez a karaktertpus a char, akkor beolvashatunk signed char s unsigned char tpus adatot is:
template<class Ch, class Tr> basic_istream<Ch,Tr>& operator>>(basic_istream<Ch,Tr>&, Ch&); template<class Tr> basic_istream<char,Tr>& operator>>(basic_istream<char,Tr>&, unsigned char&); template<class Tr> basic_istream<char,Tr>& operator>>(basic_istream<char,Tr>&, signed char&);

A felhasznl szempontjbl teljesen mindegy, hogy a >> tagfggvny vagy nll eljrs-e. A tbbi >> opertorhoz hasonlan ezek a fggvnyek is elszr tugorjk a bevezet reshely karaktereket:
void f() { char c; cin >> c; // ... }

Ez a kdrszlet az els nem reshely karaktert a cin adatfolyambl a c vltozba helyezi. A beolvass clja lehet karaktertmb is:
template<class Ch, class Tr> basic_istream<Ch,Tr>& operator>>(basic_istream<Ch,Tr>&, Ch*); template<class Tr> basic_istream<char,Tr>& operator>>(basic_istream<char,Tr>&, unsigned char*); template<class Tr> basic_istream<char,Tr>& operator>>(basic_istream<char,Tr>&, signed char*);

Ezek a mveletek is eldobjk a bevezet reshely karaktereket, majd addig olvasnak, amg egy reshely karakter vagy fjlvge jel nem kvetkezik, vgl a karaktertmb vgre egy 0 karaktert rnak. Termszetesen ez a megolds alkalmat ad a tlcsordulsra, gy rdemesebb inkbb egy string objektumba (20.3.15) helyezni a beolvasott adatokat. Ha mgis az elbbi

Forrs: http://www.doksi.hu

21. Adatfolyamok

827

megoldst vlasztjuk, rgztsk, hogy legfeljebb hny karaktert akarunk beolvasni a >> opertor segtsgvel. Az is.width(n) fggvnyhvssal azt hatrozzuk meg, hogy a kvetkez, is bemeneti adatfolyamon vgrehajtott >> mvelet legfeljebb n-1 karaktert olvashat be:
void g() { char v[4]; cin.width(4); cin >> v; cout << "v = " << v << endl; }

Ez a programrszlet legfeljebb 3 karaktert olvas be a v vltozba s a vgn elhelyezi a 0 karaktert. Egy istream esetben a width() ltal belltott rtk csak az utna kvetkez >> mveletre van hatssal, s arra is csak akkor, ha a cltpus egy tmb.

21.3.3. Az adatfolyam llapota


Minden adatfolyam (az istream s az ostream is) rendelkezik valamilyen llapottal (state). A hibkat s a szokatlan llapotokat ezen llapotrtk megfelel belltsval s lekrdezsvel kezelhetjk. Az adatfolyam-llapot (stream state) fogalmt a basic_istream bzisosztlya, a basic_ios rja le az <ios> fejllomnyban:
template <class Ch, class Tr = char_traits<Ch> > class basic_ios : public ios_base { public: // ... bool good() const; bool eof() const; bool fail() const; bool bad() const; iostate rdstate() const; void clear(iostate f = goodbit); void setstate(iostate f) { clear(rdstate()|f); } // a kvetkez mvelet sikerlhet // fjlvge kvetkezett be // a kvetkez mvelet sikertelen lesz // az adatfolyam srlt // io llapotjelz lekrdezse // io llapotjelz belltsa // az f io llopotjelz belltsa

Forrs: http://www.doksi.hu

828

A standard knyvtr

operator void*() const; bool operator!() const { return fail(); } }; // ...

// nemnulla, ha !fail()

Ha az llapot good(), a megelz bemeneti mvelet sikeres volt. Ilyenkor a kvetkez bemeneti mvelet helyes vgrehajtsra is van esly, ellenkez esetben viszont az biztosan nem lesz hibtlan. Ha egy olyan adatfolyamon prblunk meg bemeneti mveletet vgrehajtani, amely nem good() llapotban van, akkor semmi sem trtnik. Ha megprblunk a v vltozba rtket beolvasni, de a bemeneti mvelet sikertelen, v rtknek nem szabad megvltoznia. (Ha v olyan tpus, amit az istream s az ostream tagfggvnyei kezelnek, biztosan megmarad az rtke.) A fail() s a bad() llapot kztti klnbsg igen kicsi. Ha az llapot fail(), de nem bad(), akkor az adatfolyam rvnytelenn vlt, de nem vesztek el karakterek. Ha bad() llapotba kerltnk, mr nem remnykedhetnk. Az adatfolyam llapott jelzk (jelzbitek, flag-ek) hatrozzk meg. Az adatfolyamok llapott kifejez legtbb konstanshoz hasonlan ezeket is a basic_ios bzisosztlya, az ios_base rja le:
class ios_base { public: // ... typedef implementation_defined2 iostate; static const iostate badbit, // az adatfolyam srlt eofbit, // fjlvge kvetkezett be failbit, // a kvetkez mvelet sikertelen lesz goodbit; // goodbit==0 }; // ...

Az I/O llapot jelzbitjeit kzvetlenl mdosthatjuk:


void f() { ios_base::iostate s = cin.rdstate();

// iostate bitek halmazval tr vissza

if (s & ios_base::badbit) { // cin karakterek elveszhettek } // ... cin.setstate(ios_base::failbit); // ...

Forrs: http://www.doksi.hu

21. Adatfolyamok

829

Ha az adatfolyamot felttelknt hasznljuk, annak llapott az operator void*() vagy az operator !() fggvnnyel vizsglhatjuk meg. A vizsglat csak akkor lesz sikeres, ha az llapot !fail() (a void*() esetben), illetve fail() (a !() esetben). Egy ltalnos msol eljrst pldul az albbi formban rhatunk meg.
template<class T> void iocopy(istream& is, ostream& os) { T buf; while (is>>buf) os << buf << '\n'; }

Az is>>buf mvelet egy referencit ad vissza, amely az is adatfolyamra hivatkozik, a vizsglatot pedig az is::operator void*() mvelet vgzi:
void f(istream& i1, istream& { iocopy<complex>(i1,cout); iocopy<double>(i2,cout); iocopy<char>(i3,cout); iocopy<string>(i4,cout); } i2, istream& i3, istream& i4) // komplex szmok msolsa // ktszeres pontossg lebegpontos szmok msolsa // karakterek msolsa // reshelyekkel elvlasztott szavak msolsa

21.3.4. Karakterek beolvassa


A >> opertor formzott bemenetre szolgl, teht adott tpus s adott formtum objektumok beolvassra. Ha erre nincs szksgnk s inkbb valban karakterekknt akarjuk beolvasni a karaktereket, hogy ksbb mi dolgozzuk fel azokat, hasznljuk a get() fggvnyeket:
template <class Ch, class Tr = char_traits<Ch> > class basic_istream : virtual public basic_ios<Ch,Tr> { public: // ... // formzatlan bemenet streamsize gcount() const; int_type get(); basic_istream& get(Ch& c); // a legutbbi get() ltal beolvasott karakterek szma // egy Ch (vagy Tr::eof()) beolvassa // egy Ch olvassa c-be // jsor a lezrjel

basic_istream& get(Ch* p, streamsize n); basic_istream& get(Ch* p, streamsize n, Ch term);

Forrs: http://www.doksi.hu

830

A standard knyvtr

basic_istream& getline(Ch* p, streamsize n); // jsor a lezrjel basic_istream& getline(Ch* p, streamsize n, Ch term); basic_istream& ignore(streamsize n = 1, int_type t = Tr::eof()); basic_istream& read(Ch* p, streamsize n); // legfeljebb n karakter beolvassa // ...

};

Ezek mellett a <string> fejllomny biztostja a getline() fggvnyt is, amely a szabvnyos string-ek (20.3.15) beolvassra hasznlhat. A get() s a getline() fggvnyek ugyangy kezelik az reshely karaktereket, mint brmely ms karaktert. Kifejezetten olyan adatok beolvassra szolglnak, ahol nem szmt a beolvasott karakterek jelentse. Az istream::get(char&) fggvny egyetlen karaktert olvas be paramterbe. Egy karakterenknt msol programot pldul a kvetkezkppen rhatunk meg.
int main() { char c; while(cin.get(c)) cout.put(c); }

A hromparamter s.get(p,n,term) legfeljebb n-1 karaktert olvas be a p[0],, p[n-2] tmbbe. A get() az tmeneti trban mindenkppen elhelyez egy 0 karaktert a beolvasott karakterek utn, gy a p mutatnak egy legalbb n karakter mret tmbre kell mutatnia. A harmadik paramter (term) az olvasst lezr karaktert hatrozza meg. A hromparamter get() leggyakoribb felhasznlsi mdja az, hogy beolvasunk egy sort egy rgztett mret trba s ksbb innen hasznljuk fel karaktereit:
void f() { char buf[100]; cin >> buf; cin.get(buf,100,'\n'); // ... }

// gyans: tlcsordulhat // biztonsgos

Ha a get() megtallja a lezr karaktert, az adatfolyamban hagyja, teht a kvetkez bemeneti mvelet ezt a karaktert kapja meg elsknt. Soha ne hvjuk meg jra a get() fggvnyt a lezr karakter eltvoltsa nlkl. Ha egy get() vagy getline() fggvny egyetlen karaktert sem olvas s tvolt el az adatfolyamrl meghvdik setstate(failbit), gy a kvetkez beolvassok sikertelenek lesznek (vagy kivtel vltdik ki, 21.3.6).

Forrs: http://www.doksi.hu

21. Adatfolyamok

831

void subtle_error { char buf[256]; while (cin) { cin.get(buf,256); // a sor beolvassa cout << buf; // a sor kirsa. // Hopp: elfelejtettk eltvoltani cin-rl '\n'-t, a kvetkez get() sikertelen lesz }

Ez a plda jl szemllteti, mirt rdemes a get() helyett a getline() fggvnyt hasznlnunk. A getline() ugyanis pontosan gy mkdik, mint a megfelel get(), de a lezr karaktert is eltvoltja az adatfolyambl:
void f() { char word[MAX_WORD][MAX_LINE];

int i = 0; while(cin.getline(word[i++],MAX_LINE,'\n') && i<MAX_WORD); // ...

// MAX_WORD darab tmb, // mindegyik MAX_LINE karaktert tartalmaz

Ha nem a hatkonysg a legfontosabb, rdemes string (3.6, 20.3.15) objektumba olvasnunk, mert azzal elkerlhetjk a leggyakoribb tlcsordulsi, illetve helyfoglalsi problmkat. A get(), a getline() s a read() fggvnyre viszont az ilyen magas szint szolgltatsok megvalstshoz szksg van. A nagyobb sebessg ra a viszonylag kusza fellet, de legalbb nem kell jra megvizsglnunk a bemeneti adatfolyamot, ahhoz, hogy megtudjuk, mi szaktotta meg a beolvassi mveletet; pontosan korltozhatjuk a beolvasand karakterek szmt s gy tovbb. A read(p,n) fggvny legfeljebb n karaktert olvas be a p[0], , p[n-1] tmbbe. A read() nem foglalkozik a bemeneten megjelen lezr karakterekkel s az eredmnytmb vgre sem helyez 0 karaktert. Ezrt kpes tnyleg n karaktert beolvasni (s nem csak n-1-t). Teht a read() fggvny egyszeren karaktereket olvas, s nem prblja az eredmnyt C stlus karakterlncc alaktani. Az ignore() fggvny ugyangy karaktereket olvas, mint a read(), de egyltaln nem trolja azokat. Egy msik hasonlsg a read() fggvnnyel, hogy tnyleg kpes n (s nem n-1) darab karakter beolvassra. Az ignore() alaprtelmezs szerint egy karaktert olvas be, teht ha paramterek nlkl hvjuk meg, akkor jelentse dobd el a kvetkez karaktert.

Forrs: http://www.doksi.hu

832

A standard knyvtr

A getline() fggvnyhez hasonlan itt is megadhatunk egy lezr karaktert, amit eltvolt a bemeneti adatfolyambl, ha beletkzik. Az ignore() alaprtelmezett lezr karaktere a fjlvge jel. Ezeknl a fggvnyeknl nem vilgos azonnal, hogy mi lltotta meg az olvasst, s nha mg arra is nehz emlkezni, melyik olvassi mveletnl mi volt a lellsi felttel. Azt viszont mindig meg tudjuk krdezni, hogy elrtk-e mr a fjlvge jelet (21.3.3). Hasonl segtsg, hogy a gcount() fggvnnyel megllapthatjuk, hogy a legutbbi formzatlan bemeneti eljrs hny karaktert olvasott be az adatfolyambl:
void read_a_line(int max) { // ... if (cin.fail()) { cin.clear(); cin.ignore(max,';');

// Hopp: hibs bemeneti formtum // a bemeneti jelzbitek trlse (21.3.3) // ugrs a pontosvesszre

if (!cin) { // hopp: elrtk az adatfolyam vgt } else if (cin.gcount()==max) { // hopp: max szm karaktert beolvastunk } else { // megtalltuk s eldobtuk a pontosvesszt }

Sajnos ha a megengedett legtbb karaktert olvastuk be, nincs mdunk annak megllaptsra, hogy megtalltuk-e a lezr karaktert (utols beolvasott karakterknt). A paramter nlkli get() a <cstdio> fejllomnyban szerepl getchar() fggvny (21.8) <iostream> -beli megfelelje. Egyszeren beolvas egy karaktert s visszaadja annak szmrtkt. Ezzel a megoldssal semmit nem kell feltteleznie az ppen hasznlt karaktertpusrl. Ha nincs beolvashat karakter a get() adatfolyamban, akkor a megfelel fjlvge jelet (teht a traits_type::eof() karaktert) adja vissza, majd belltja az istream objektum eof llapott (21.3.3):
void f(unsigned char* p) { int i; while((i = cin.get()) && i!=EOF) { *p++ = i; // ... } }

Forrs: http://www.doksi.hu

21. Adatfolyamok

833

Az EOF az eof() fggvny ltal visszaadott rtk a char tpus szoksos char_traits osztlybl. Az EOF rtket az <iostream> fejllomny hatrozza meg. Teht e ciklus helyett nyugodtan rhattuk volna a read(p, MAX_INT) utastst, de tegyk fel, hogy explicit ciklust akartunk rni, mert, mondjuk, minden beolvasott karaktert egyesvel akartunk ltni. A C nyelv egyik legnagyobb erssgnek tartjk azt a lehetsget, hogy karaktereket olvashatunk be, s gy dnthetnk, hogy semmit sem csinlunk velk, radsul mindezt gyorsan tehetjk. Ez tnyleg fontos s albecslt erssg, gy a C++ igyekszik ezt megtartani. A <cctype> szabvnyos fejllomny sok olyan fggvnyt biztost, amelynek hasznt vehetjk a bemenet feldolgozsban (20.4.2). Az eatwhite() fggvny pldul, amely az reshely karaktereket trli az adatfolyambl, a kvetkezkppen definilhat:
istream& eatwhite(istream& is) { char c; while (is.get(c)) { if (!isspace(c)) { is.putback(c); break; } } return is; }

// c reshely karakter? // c visszaraksa a bemenet tmeneti trba

Az is.putback(c) fggvnyhvsra azrt van szksg, hogy a c legyen a kvetkez karakter, amit az is adatfolyambl beolvasunk (21.6.4).

21.3.5. Felhasznli tpusok beolvassa


A felhasznli tpusok beolvasst pontosan ugyangy lehet megvalstani, mint kimeneti mveleteiket. Az egyetlen klnbsg, hogy a bemeneti mveletek esetben a msodik paramternek nem-konstans referencia tpusnak kell lennie:
istream& operator>>(istream& s, complex& a) /* a complex tpus lehetsges bemeneti formtumai ("f" lebegpontos szm) f (f) (f,f) */ {

Forrs: http://www.doksi.hu

834

A standard knyvtr

double re = 0, im = 0; char c = 0; s >> c; if (c == '(') { s >> re >> c; if (c == ',') s >> im >> c; if (c != ')') s.clear(ios_base::failbit); } else { s.putback(c); s >> re; } if (s) a = complex(re,im); return s;

// llapot belltsa

A nagyon kevs hibakezel utasts ellenre ez a programrszlet szinte minden hibatpust kpes kezelni. A loklis c vltoznak azrt adunk kezdrtket, hogy ha az els >> mvelet sikertelen, nehogy vletlenl pont a ( karakter legyen benne. Az adatfolyam llapotnak ellenrzsre a fggvny vgn azrt van szksg, mert az a paramter rtkt csak akkor vltoztathatjuk meg, ha a korbbi mveleteket sikeresen vgrehajtottuk. Ha formzsi hiba trtnik az adatfolyam llapota failbit lesz. Azrt nem badbit, mert maga az adatfolyam nem srlt meg. A felhasznl a clear() fggvnnyel alapllapotba helyezheti az adatfolyamot s tovbblpve rtelmes adatokat nyerhet onnan. Az adatfolyam llapotnak belltsra szolgl fggvny neve clear(), mert ltalban arra hasznljuk, hogy az adatfolyam llapott good() rtkre lltsuk. Az ios_base::clear() (21.3.3) paramternek alaprtelmezett rtke ios_base::goodbit.

21.3.6. Kivtelek
Nagyon knyelmetlen minden egyes I/O mvelet utn kln ellenrizni, hogy sikeres volte, ezrt nagyon gyakori hiba, hogy elfelejtnk egy ellenrzst ott, ahol felttlenl szksg van r. A kimeneti mveleteket ltalban nem ellenrzik, annak ellenre, hogy nha ott is elfordulhat hiba. Egy adatfolyam llapotnak kzvetlen megvltoztatsra csak a clear() fggvnyt hasznlhatjuk. Ezrt ha rteslni akarunk az adatfolyam llapotnak megvltozsrl, elg nyilvnval mdszer, hogy a clear() fggvnyt kivtelek kivltsra krjk. Az ios_base osztly exceptions() tagfggvnye pontosan ezt teszi:

Forrs: http://www.doksi.hu

21. Adatfolyamok

835

template <class Ch, class Tr = char_traits<Ch> > class basic_ios : public ios_base { public: // ... class failure; iostate exceptions() const; void exceptions(iostate except); }; // ... // kivtelosztly (lsd 14.10) // kivtel-llapot kiolvassa // kivtel-llapot belltsa

A kvetkez utastssal pldul elrhetjk, hogy a clear() egy ios_base::failure kivtelt vltson ki, ha a cout adatfolyam bad, fail vagy eof llapotba kerl, vagyis ha valamelyik mvelet nem hibtlanul fut le:
cout.exceptions(ios_base::badbit|ios_base::failbit|ios_base::eofbit);

Ha szksg van r, a cout vizsglatval pontosan megllapthatjuk, milyen problma trtnt. Ehhez hasonlan a kvetkez utastssal azokat a ritknak egyltaln nem nevezhet eseteket dolgozhatjuk fel, amikor a beolvasni kvnt adatok formtuma nem megfelel, s ennek kvetkeztben a bemeneti mvelet nem ad vissza rtket:
cin.exceptions(ios_base::badbit|ios_base::failbit);

Ha az exceptions() fggvnyt paramterek nlkl hvjuk meg, akkor azokat az I/O llapotjelzket adja meg, amelyek kivtelt vltanak ki:
void print_exceptions(ios_base& ios) { ios_base::iostate s = ios.exceptions(); if (s&ios_base::badbit) cout << "bad kivtelek"; if (s&ios_base::failbit) cout << "fail kivtelek"; if (s&ios_base::eofbit) cout << "eof kivtelek"; if (s == 0) cout << "nincs kivtel"; }

Az I/O kivtelek leggyakoribb felhasznlsi terlete az, hogy a ritkn elfordul (ezrt knnyen elfelejthet) hibkat kezeljk. Msik fontos feladatuk a ki- s bemenet vezrlse lehet:

Forrs: http://www.doksi.hu

836

A standard knyvtr

void readints(vector<int>& s) // nem a kedvenc stlusom! { ios_base::iostate old_state = cin.exceptions(); // menti a kivtel-llapotot cin.exceptions(ios_base::eofbit); // eof kivtelt vlt ki for (;;) try {

} catch(ios_base::failure) { // rendben: elrtk a fjl vgt } } cin.exceptions(old_state); // kivtel-llapot visszalltsa

int i; cin>>i; s.push_back(i);

A kivtelek ilyen formban val felhasznlsakor felmerl a krds, nevezhetjk-e az adott helyzetet hibnak vagy tnyleg kivteles helyzetnek. ltalban mindkt krdsre inkbb a nem vlaszt adjuk, ezrt gy gondolom, clszerbb az adatfolyam llapott kzvetlenl vizsglni. Amit egy fggvny belsejben, loklis vezrlsi szerkezetekkel kezelhetnk, azt a kivtelek sem kezelik jobban.

21.3.7. Adatfolyamok sszektse


A basic_ios osztly tie() fggvnye arra hasznlhat, hogy kapcsolatot ltestsnk egy istream s egy ostream kztt:
template <class Ch, class Tr = char_traits<Ch> > class std::basic_ios : public ios_base { // ... basic_ostream<Ch,Tr>* tie() const; // mutat sszekttt adatfolyamokra basic_ostream<Ch,Tr>* tie(basic_ostream<Ch,Tr>* s); // *this hozzktse s-hez }; // ...

Forrs: http://www.doksi.hu

21. Adatfolyamok

837

Vegyk pldul az albbi programrszletet:


string get_passwd() { string s; cout << "Jelsz: "; cin >> s; // ... }

Hogyan bizonyosodhatnnk meg arrl, hogy a Jelsz: szveg megjelent-e a kpernyn, mieltt a bemeneti mveletet vgrehajtjuk? A cout adatfolyamra kldtt kimenet tmeneti trba kerl, gy ha a cin s a cout fggetlen egymstl, a Jelsz esetleg mindaddig nem jelenik meg a kpernyn, amg a kimeneti tr meg nem telik. A megoldst az jelenti, hogy a cout adatfolyamot hozzktjk a cin adatfolyamhoz a cin.tie(&cout) utastssal. Ha egy ostream objektumot sszektnk egy istream objektummal, az ostream mindig kirl, amikor egy bemeneti mvelet alulcsordulst okoz az istream adatfolyamban, azaz amikor egy bemeneti feladat vgrehajtshoz j karakterekre van szksg a kijellt bemeneti eszkzrl. Teht ilyenkor a
cout << "Jelsz: "; cin >> s;

utastssorozat egyenrtk az albbival:


cout << "Jelsz: "; cout.flush(); cin >> s;

Adott idben minden adatfolyamhoz legfeljebb egy ostream objektum kthet. Az s.tie(0) utastssal levlaszthatjuk az s objektumhoz kttt adatfolyamot (ha volt ilyen). A tbbi olyan adatfolyam-fggvnyhez hasonlan, melyek rtket lltanak be, a tie(s) is a korbbi rtket adja vissza, teht a legutbb ide kttt adatfolyamot, vagy ha ilyen nincs, akkor a 0 rtket. Ha a tie() fggvnyt paramterek nlkl hvjuk meg, akkor egyszeren visszakapjuk az aktulis rtket, annak megvltoztatsa nlkl. A szabvnyos adatfolyamok esetben a cout hozz van ktve a cin bemenethez, a wcout pedig a wcin adatfolyamhoz. A cerr adatfolyamot felesleges lenne brmihez is hozzktni, mivel ehhez nincs tmeneti tr, a clog pedig nem vr felhasznli kzremkdst.

Forrs: http://www.doksi.hu

838

A standard knyvtr

21.3.8. rszemek
Amikor a << s a >> opertort a complex tpusra hasznltuk, egyltaln nem foglalkoztunk az sszekttt adatfolyamok (21.3.7) krdsvel, vagy azzal, hogy az adatfolyam llapotnak megvltozsa kivteleket okoz-e (21.3.6). Egyszeren azt feltteleztk (s nem ok nlkl), hogy a knyvtr ltal knlt fggvnyek figyelnek helyettnk ezekre a problmkra. De hogyan kpesek erre? Nhny tucat ilyen fggvnnyel kell megbirkznunk, gy ha olyan bonyolult eljrsokat kellene ksztennk, amely az sszekttt adatfolyamokkal, a helyi sajtossgokkal (locale, 21.7, D), a kivtelekkel, s egyebekkel is foglalkoznak, akkor igen kusza kdot kapnnk. A megoldst a sentry (rszem) osztly bevezetse jelenti, amely a kzs kdrszleteket tartalmazza. Azok a rszek, melyeknek elsknt kell lefutniuk (a prefix kd, pldul egy lekttt adatfolyam kirtse), a sentry konstruktorban kaptak helyet. Az utolsknt fut sorokat (a suffix kdokat, pldul az llapotvltozsok miatt elvrt kivtelek kivltst) a sentry destruktora hatrozza meg:
template <class Ch, class Tr = char_traits<Ch> > class basic_ostream : virtual public basic_ios<Ch,Tr> { // ... class sentry; // ... }; template <class Ch, class Tr = char_traits<Ch> > class basic_ostream<Ch,Tr>::sentry { public: explicit sentry(basic_ostream<Ch,Tr>& s); ~sentry(); operator bool(); }; // ...

Teht egy ltalnos kdot rtunk, melynek segtsgvel az egyes fggvnyek a kvetkez formba rhatk:
template <class Ch, class Tr = char_traits<Ch> > basic_ostream<Ch,Tr>& basic_ostream<Ch,Tr>::operator<<(int i) { sentry s(*this); if (!s) { // ellenrzs, minden rendben van-e a kirs megkezdshez setstate(failbit); return *this; }

Forrs: http://www.doksi.hu

21. Adatfolyamok

839

// az int kirsa return *this;

A konstruktorok s a destruktorok ilyen jelleg felhasznlsa ltalnos az eltag (prefix) s uttag (suffix) kdrszletek beillesztshez, s nagyon sok helyzetben alkalmazhat mdszer. Termszetesen a basic_istream hasonl sentry tagosztllyal rendelkezik.

21.4. Formzs
A 21.2. pontban bemutatott pldk mind abba a kategriba tartoztak, amit ltalnosan formzatlan kimenetnek (unformatted output) neveznk. Ez azt jelenti, hogy az objektumot gy alaktottuk karaktersorozatt, hogy csak alaprtelmezett szablyokat hasznltunk. A programozknak gyakran ennl rszletesebb vezrlsi lehetsgekre van szksgk. Nha pldul meg kell hatroznunk, hogy a kimenet mekkora terletet hasznljon, vagy hogy a szmok milyen formban jelenjenek meg. Az ehhez hasonl tnyezk vezrlsre a bemenet esetben is szksg lehet. A ki- s bemenet formzst vezrl eszkzk a basic_ios osztlyban, illetve annak ios_base bzisosztlyban kaptak helyet. A basic_ios pldul informcikat tartalmaz a szmrendszerrl (nyolcas, tzes vagy tizenhatos), amit egsz szmok kirsakor s beolvassakor hasznl a rendszer, vagy a lebegpontos szmok pontossgrl s gy tovbb. Az ezen adatfolyam-szint (minden adatfolyamra kln bellthat) vltozk lekrdezsre s belltsra szolgl fggvnyek szintn ebben az osztlyban kaptak helyet. A basic_ios bzisosztlya a basic_istream s a basic_ostream osztlynak, gy a formzs vezrlse minden adatfolyamra kln adhat meg.

Forrs: http://www.doksi.hu

840

A standard knyvtr

21.4.1. Formzsi llapot


A ki- s bemenet formzst nhny jelzbit s nhny egsz rtk vezrli, melyek az adatfolyamok ios_base bzisosztlyban szerepelnek:
class ios_base { public: // ... // formzsjelzk nevei: typedef megvalsts_fgg1 fmtflags; static const fmtflags skipws, // reshelyek tlpse a bemeneten left, right, internal, boolalpha, dec, hex, oct, scientific, fixed, showbase, showpoint, showpos, uppercase, adjustfield, basefield, floatfield; fmtflags unitbuf; fmtflags flags() const; fmtflags flags(fmtflags f); // mezigazts: feltlts az rtk utn // feltlts az rtk eltt // feltlts eljel s rtk kztt // a true s false szimbolikus megjelentse // szmrendszer alapja egszeknl: 10 (decimlis) // 16 (hexadecimlis) // 8 (oktlis) // lebegpontos jells: d.ddddddEdd // dddd.dd // kirskor oktlisak el 0, hexadecimlisok el 0x eltag // a zr nullk kirsa // '+' a pozitv egszek el // 'E', 'X' alkalmazsa ('e', 'x' helyett) // mezigaztssal kapcsolatos jelz (21.4.5) // egsz szmrendszer alapjval kapcsolatos jelz (21.4.2) // lebegpontos kimenettel kapcsolatos jelz (21.4.3) // minden kimenet utn az tmeneti tr rtse // jelzbitek kiolvassa // jelzbitek belltsa // jelzbit hozzadsa

fmtflags setf(fmtflags f) { return flags(flags()|f); }

Forrs: http://www.doksi.hu

21. Adatfolyamok

841

// a jelzbitek trlse s belltsa a maszkban fmtflags setf(fmtflags f, fmtflags mask) { return flags((flags()&~mask)|(f&mask)); } void unsetf(fmtflags mask) { flags(flags()&~mask); } // jelzbitek trlse }; // ...

Az egyes jelzbitek (flag) rtke az adott nyelvi vltozattl fgg, gy mindig a szimbolikus neveket hasznljuk a konkrt szmrtkek helyett, mg akkor is, ha az ltalunk hasznlt rtkek vletlenl ppen megfelelen mkdnek. Egy felletet jelzk sorozatval, illetve azokat bellt s lekrdez fggvnyekkel meghatrozni idtakarkos, de kicsit rgimdi mdszer. Legfbb ernye, hogy a felhasznl a lehetsgeket sszeptheti:
const ios_base::fmtflags my_opt = ios_base::left|ios_base::oct|ios_base::fixed;

Ez lehetv teszi, hogy egy fggvnynek belltsokat adjunk t, s akkor kapcsoljuk be azokat, amikor szksg van rjuk:
void your_function(ios_base::fmtflags opt) { ios_base::fmtflags old_options = cout.flags(opt); // rgi belltsok mentse, jak belltsa // ... cout.flags(old_options); // visszallts } void my_function() { your_function(my_opt); // ... }

A flags() fggvny az eddig rvnyben lev belltsokat adja vissza. Ha kpesek vagyunk az sszes tulajdonsg egyttes rsra s olvassra, egyesvel is bellthatjuk azokat:
myostream.flags(myostream.flags()|ios_base::showpos);

Ezen utasts hatsra a myostream adatfolyam minden pozitv szm eltt egy + jelet fog megjelenteni; ms belltsok nem vltoznak meg. Elszr beolvassuk a rgi belltsokat,

Forrs: http://www.doksi.hu

842

A standard knyvtr

majd belltjuk a showpos tulajdonsgot gy, hogy az eredeti szmhoz a bitenknti vagy mvelettel hozzkapcsoljuk ezt az rtket. A setf() fggvny pontosan ugyanezt teszi, teht a fenti pldval teljesen egyenrtk az albbi sor:
myostream.setf(ios_base::showpos);

Egy jelz mindaddig megtartja rtkt, amg azt meg nem vltoztatjuk. A ki- s bemeneti tulajdonsgok vezrlse a jelzbitek kzvetlen belltsval igen nyers megolds s knnyen hibkhoz vezethet. Az egyszerbb esetekben a mdostk (manipulator) (21.4.6) tisztbb felletet adnak. A jelzbitek hasznlata egy adatfolyam llapotnak vezrlsre a megvalstsi mdszerek tanulmnyozshoz megfelel, a fellettervezshez mr kevsb.

21.4.1.1. Formzsi llapot lemsolsa A copyfmt() fggvny segtsgvel egy adatfolyam teljes formzsi llapott lemsolhatjuk:
template <class Ch, class Tr = char_traits<Ch> > class basic_ios : public ios_base { public: // ... basic_ios& copyfmt(const basic_ios& f); // ... };

Az adatfolyam tmeneti trn (21.6) s annak llapotn kvl a copyfmt() minden llapottulajdonsgot tmsol, teht a kivtelkezelsi mdot (21.3.6) s a felhasznl ltal megadott tovbbi belltsokat is (21.7.1).

21.4.2. Egsz kimenet


A bitenknti vagy hasznlata egy j bellts megadshoz a flags() s setf() fggvnyek segtsgvel csak akkor hasznlhat, ha az adott tulajdonsgot egy bit hatrozza meg. Nem ez a helyzet azonban egy olyan jellemznl, mint pldul az egszek kirshoz hasznlt szmrendszer vagy a lebegpontos szmok kimeneti formtuma. Az ilyen tulajdonsgok esetben az rtk, amely egy adott stlust brzol, nem fogalmazhat meg egyetlen bittel vagy akr egymstl fggetlen bitek sorozatval.

Forrs: http://www.doksi.hu

21. Adatfolyamok

843

Az <iostream> fejllomnyban alkalmazott megolds az, hogy a setf() fggvny olyan vltozatt hatrozza meg, amelynek egy msodik, lparamtert is meg kell adnunk. Ez a paramter hatrozza meg, milyen tulajdonsgot akarunk belltani az j rtkkel:
cout.setf(ios_base::oct,ios_base::basefield); cout.setf(ios_base::dec,ios_base::basefield); cout.setf(ios_base::hex,ios_base::basefield); // oktlis // decimlis // hexadecimlis

Ezek az utastsok az egszek szmrendszer-alapjt lltjk be, anlkl, hogy az adatfolyam llapotnak brmely ms rszt megvltoztatnk. Az alapszm mindaddig vltozatlan marad, amg meg nem vltoztatjuk. Pldul az albbi utastssorozat eredmnye 1234 1234 2322 2322 4d2 4d2.
cout << 1234 << ' ' << 1234 << ' '; cout.setf(ios_base::oct,ios_base::basefield); cout << 1234 << ' ' << 1234 << ' '; cout.setf(ios_base::hex,ios_base::basefield); cout << 1234 << ' ' << 1234 << ' '; // alaprtelmezett: decimlis // oktlis // hexadecimlis

Ha az eredmnyrl meg kell tudnunk llaptani, hogy melyik szm milyen szmrendszerben rtend, a showbase belltst hasznlhatjuk. Teht ha az elbbi utastsok el beszrjuk a
cout.setf(ios_base::showbase);

utastst, akkor az 1234 1234 02322 02322 0x4d2 0x4d2 szmsorozatot kapjuk. A szabvnyos mdostk (manipulator) (21.4.6.2) elegnsabb megoldst knlnak az egsz szmok kirshoz hasznlt szmrendszer belltsra.

21.4.3. Lebegpontos szm kimenet


A lebegpontos szmok kimenett kt tnyez befolysolja: a formtum (format) s a pontossg (precision). Az ltalnos (general) formtum lehetv teszi a megvalsts szmra, hogy olyan szmformtumot hasznljon, amely a rendelkezsre ll terleten a lehet legjobban brzolja a lebegpontos rtket. A pontossg a megjelen szmjegyek maximlis szmt hatrozza meg. Ez a jellemz a printf() fggvny %g belltsnak felel meg (21.8).

Forrs: http://www.doksi.hu

844

A standard knyvtr

A tudomnyos (scientific) formtumban egy szmjegy szerepel a tizedespont eltt, s egy kitev (exponens) hatrozza meg a szm nagysgrendjt. A pontossg ez esetben a tizedespont utn ll szmjegyek maximlis szmt jelenti. Ezt az esetet a printf() fggvny %e belltsa valstja meg. A fixpontos (fixed) formtum a szmot egszrsz, tizedespont, trtrsz formban jelenti meg. A pontossg most is a tizedespont utn ll szmjegyek szmnak maximumt adja meg. A printf() fggvny %f belltsa viselkedik gy. A lebegpontos szmok kimeneti formtumt az llapotmdost fggvnyek (state manipulator function) segtsgvel szablyozhatjuk. Ezek felhasznlsval a lebegpontos rtkek megjelentst gy llthatjuk be, hogy az adatfolyam llapotnak ms rszeit nem befolysoljuk:
cout << "Alaprtelmezett:\t" << 1234.56789 << '\n'; cout.setf(ios_base::scientific,ios_base::floatfield); cout << "Tudomnyos:\t" << 1234.56789 << '\n'; cout.setf(ios_base::fixed,ios_base::floatfield); cout << "Fixpontos:\t" << 1234.56789 << '\n'; cout.setf(0,ios_base::floatfield); cout << "Alaprtelmezett:\t" << 1234.56789 << '\n'; // tudomnyos formtum hasznlata // fixpontos formtum hasznlata // alaprtelmezs visszalltsa // (ltalnos formtum)

Az eredmny a kvetkez lesz:


default: scientific: fixed: default: 1234.57 1.234568e+03 1234.567890 1234.57

A pontossg alaprtelmezett rtke minden formtum esetben 6, amit az ios_base osztly kln tagfggvnyvel mdosthatunk:
class ios_base { public: // ... streamsize precision() const; // pontossg lekrdezse streamsize precision(streamsize n); // pontossg belltsa (s a rgi pontossg lekrdezse) // ... };

Forrs: http://www.doksi.hu

21. Adatfolyamok

845

A precision() fggvny meghvsval minden lebegpontos I/O mvelet pontossgt megvltoztatjuk az adatfolyamban a fggvny kvetkez meghvsig. Ezrt a
cout.precision(8); cout << 1234.56789 << ' ' << 1234.56789 << ' ' << 123456 << '\n'; cout.precision(4); cout << 1234.56789 << ' ' << 1234.56789 << ' ' << 123456 << '\n';

utastssorozat eredmnye a kvetkez lesz:


1234.5679 1234.5679 123456 1235 1235 123456

A pldban kt dolgot figyelhetnk meg: az egyik, hogy a lebegpontos rtkek kerektve jelennek meg, nem egyszeren levgva, a msik, hogy a precision() az egsz rtkek megjelentsre nincs hatssal. Az uppercase jelzbit (21.4.1) azt hatrozza meg, hogy a tudomnyos formtumban e vagy E bet jellje a kitevt. A mdostk (manipulator) elegnsabb megoldst knlnak a lebegpontos szmok kimeneti formtumnak belltsra (21.4.6.2).

21.4.4. Kimeneti mezk


Gyakran arra van szksgnk, hogy egy kimeneti sor adott terlett tltsk fel szveggel. Ilyenkor pontosan n karaktert akarunk hasznlni, kevesebbet semmikpp (tbbet pedig csak akkor, ha a szveg nem fr el a meghatrozott terleten). Ehhez meg kell adnunk a terlet szlessgt s a kitlt karaktert:
class ios_base { public: // ... streamsize width() const; streamsize width(streamsize wide); // ... }; template <class Ch, class Tr = char_traits<Ch> > class basic_ios : public ios_base { public: // ...

// mezszlessg lekrdezse // mezszlessg belltsa

Forrs: http://www.doksi.hu

846

A standard knyvtr

};

Ch fill() const; Ch fill(Ch ch); // ...

// kitlt karakter lekrdezse // kitlt karakter belltsa

A width() fggvny a kimen karakterek legkisebb szmt hatrozza meg a standard knyvtr kvetkez olyan << mveletben, amellyel szmrtket, logikai rtket, C stlus karakterlncot, karaktert, mutatt (21.2.1), string objektumot (20.3.15) vagy bitfield vltozt (17.5.3.3) runk ki:
cout.width(4); cout << 12;

Ez az utasts a 12 szmot kt szkz karakter utn rja ki. A kitlt karaktert a fill() fggvny segtsgvel adhatjuk meg:
cout.width(4); cout.fill('#'); cout << "ab";

Az eredmny ##ab lesz. Az alaprtelmezett kitlt karakter a szkz, az alaprtelmezett pontossg pedig 0, ami annyi karaktert jelent, amennyire szksg van. A kimeneti mez mrett teht a kvetkez utastssal llthatjuk vissza alaprtelmezett rtkre:
cout.width(0); // "annyi karakter, amennyi csak kell"

A width(n) utastssal a kirhat karakterek legkisebb szmt n-re lltjuk. Ha ennl tbb karaktert adunk meg, akkor azok mind megjelennek:
cout.width(4); cout << "abcdef";

Az eredmny abcdef lesz, nem pedig csak abcd. Ez azrt van gy, mert ltalban jobb a megfelel rtket csnya formban megkapni, mint a rossz rtket szpen igaztva (lsd mg 21.10[21]).

Forrs: http://www.doksi.hu

21. Adatfolyamok

847

A width(n) fggvnyhvs csak a kzvetlenl utna kvetkez << kimeneti mveletre vonatkozik:
cout.width(4); cout.fill('#'); cout << 12 << ':' << 13;

Az eredmny csak ##12:13 lesz s nem ##12###:##13, ami akkor jelenne meg, ha a width(4) minden ksbbi mveletre vonatkozna. Ha a width() fggvnyt tbb, egyms utni kimeneti mveletre is alkalmazni szeretnnk, knytelenek lesznk minden egyes rtkhez kln-kln megadni. A szabvnyos mdostk (modifier) (21.4.6.2) elegnsabb megoldst knlnak a kimeneti mezk mretnek szablyozsra is.

21.4.5. Mezk igaztsa


A karakterek mezn belli igaztst (adjustment) a setf() fggvny meghvsval llthatjuk be:
cout.setf(ios_base::left,ios_base::adjustfield); cout.setf(ios_base::right,ios_base::adjustfield); cout.setf(ios_base::internal,ios_base::adjustfield); // bal // jobb // bels

Ezek az utastsok az ios_base::width() fggvnnyel meghatrozott kimeneti mezn bell adjk meg az igaztst, az adatfolyam egyb belltsaira nincsenek hatssal. Az igaztst a kvetkezkppen hasznlhatjuk.
cout.fill('#'); cout << '('; cout.width(4); cout << -12 << "),("; cout.width(4); cout.setf(ios_base::left,ios_base::adjustfield); cout << -12 << "),("; cout.width(4); cout.setf(ios_base::internal,ios_base::adjustfield); cout << -12 << ")";

Forrs: http://www.doksi.hu

848

A standard knyvtr

Az eredmny: (#-12), (-12#), (-#12). Az internal bellts a kitlt karaktereket az eljel s az rtk kz helyezi. A pldbl lthatjuk, hogy az alaprtelmezett bellts a jobbra igazts.

21.4.6. Mdostk
A standard knyvtr szeretn megkmlni a programozt attl, hogy az adatfolyamok llapott jelzbiteken keresztl lltsa be, ezrt kln fggvnyeket knl ezen feladat megoldshoz. Az alaptlet az, hogy a kirt vagy beolvasott objektumok kztt adjuk meg azokat a mveleteket is, melyek az adatfolyam llapott megvltoztatjk. Az albbi utastssal pldul a kimenet tmeneti trnak azonnali kirtsre szlthatjuk fel az adatfolyamot:
cout << x << flush << y << flush;

A megfelel helyeken a cout.flush() fggvny fut le. Ezt az tletet gy valsthatjuk meg, hogy egy olyan << vltozatot ksztnk, amely egy fggvnyre hivatkoz mutatt vr paramterknt s trzsben lefuttatja a hivatkozott fggvnyt:
template <class Ch, class Tr = char_traits<Ch> > class basic_ostream : virtual public basic_ios<Ch,Tr> { public: // ... basic_ostream& operator<<(basic_ostream& (*f)(basic_ostream&)) { return f(*this); } basic_ostream& operator<<(ios_base& (*f)(ios_base&)); basic_ostream& operator<<(basic_ios<Ch,Tr>& (*f)(basic_ios<Ch,Tr>&)); }; // ...

Ahhoz, hogy ez a megolds mkdjn, a (mutatknt tadott) fggvnynek nem szabad tagfggvnynek lennie (legfeljebb statikus tagfggvnynek), s a megfelel tpussal kell rendelkeznie. Ezrt a flush() fggvnyt pldul a kvetkezkppen kell meghatroznunk:
template <class Ch, class Tr = char_traits<Ch> > basic_ostream<Ch,Tr>& flush(basic_ostream<Ch,Tr>& s) { return s.flush(); // az ostream osztly flush() tagfggvnynek meghvsa }

Ezen deklarcik utn a


cout << flush;

Forrs: http://www.doksi.hu

21. Adatfolyamok

849

utasts egyenrtk lesz a


cout.operator<<(flush);

utastssal, ami pedig meghvja a


flush(cout);

fggvnyt, gy vgl a
cout.flush();

kerl vgrehajtsra. A teljes eljrs fordtsi idben trtnik, gy lehetv teszi, hogy a basic_ostream::flush() fggvnyt cout<<flush formban hvjuk meg. Nagyon sok olyan mvelet van, amelyet kzvetlenl egy ki- vagy bemeneti mvelet eltt vagy utn akarunk vgrehajtani:
cout << x; cout.flush(); cout << y; unset(ios_base::skipws); cin >> x;

// ne ugorjuk t az reshelyeket

Ha ezeket a mveleteket kln utastsokknt kell megfogalmaznunk, a mveletek kztti kapcsolatok kevsb fognak ltszdni, mrpedig az ilyen logikai kapcsolatok elvesztse ersen rontja a program olvashatsgt. A mdostk (manipulator) lehetv teszik, hogy az olyan mveleteket, mint a flush() s a noskipws(), kzvetlenl a ki- vagy bemeneti mveletek listjban helyezzk el:
cout << x << flush << y << flush; cin >> noskipws >> x;

Megjegyzend, hogy a mdostk az std nvtrhez tartoznak, ezrt minstennk kell azokat, amennyiben az std nem rsze az adott hatkrnek:
std::cout << endl; std::cout << std::endl; // hiba: endl nincs a hatkrben // rendben

Forrs: http://www.doksi.hu

850

A standard knyvtr

Termszetesen a basic_istream a mdostk szmra is ugyangy biztostja a >> opertorokat, mint a basic_ostream:
template <class Ch, class Tr = char_traits<Ch> > class basic_istream : virtual public basic_ios<Ch,Tr> { public: // ... basic_istream& operator>>(basic_istream& (*pf)(basic_istream&)); basic_istream& operator>>(basic_ios<Ch,Tr>& (*pf)(basic_ios<Ch,Tr>&)); basic_istream& operator>>(ios_base& (*pf)(ios_base&)); // ... };

21.4.6.1. Mdostk, melyek paramtereket vrnak Nagyon hasznosak lennnek az olyan mdostk is, melyeknek paramtereket is t tudunk adni. Pldul j lenne, ha lerhatnnk az albbi sort, s vele az angle lebegpontos vltoz megjelentst 4 jegy pontossgra llthatnnk:
cout << setprecision(4) << angle;

Ennek elrshez a setprecision-nek egy objektumot kell visszaadnia, amelynek a 4 kezdrtket adjuk, s amely meghvja a cout::setprecision(4) fggvnyt. Az ilyen mdostk fggvnyobjektumok, amelyeket a () helyett a << opertor hv meg. A fggvnyobjektum pontos tpusa az adott megvalststl fgg; egy lehetsges defincija pldul a kvetkez:
struct smanip { ios_base& (*f)(ios_base&,int); int i; }; // meghvand fggvny

smanip(ios_base& (*ff)(ios_base&,int), int ii) : f(ff), i(ii) { }

template<class Ch, class Tr> ostream<Ch,Tr>& operator<<(ostream<Ch,Tr>& os, const smanip& m) { return m.f(os,m.i); }

Az smanip konstruktor paramtereit az f-ben s az i-ben trolja, majd az operator<< egy f(i) fggvnyhvst hajt vgre. Ennek felhasznlsval a fenti setprecision() mdost a kvetkezkppen definilhat:

Forrs: http://www.doksi.hu

21. Adatfolyamok

851

ios_base& set_precision(ios_base& s, int n) { return s.precision(n); } inline smanip setprecision(int n) { return smanip(set_precision,n); }

// segd // a tagfggvny hvsa

// fggvnyobjektum ltrehozsa

gy mr lerhatjuk az utastst:
cout << setprecision(4) << angle ;

A programozk sajt mdostkat is megadhatnak az smanip stlusban, ha ms lehetsgekre is szksgk van (21.10[22]). Ehhez nem kell megvltoztatniuk a standard knyvtr osztlyait s sablonjait (pldul basic_istream, basic_ostream, basic_ios vagy ios_base).

21.4.6.2. Szabvnyos ki- s bemeneti mdostk A standard knyvtrban sok mdost (manipulator) szerepel a klnbz formzsi llapotok kezelshez. A szabvnyos mdostk az std nvtrben tallhatk. Az ios_base osztlyhoz kapcsold mdostk az <ios> fejllomnyon keresztl rhetk el, mg az istream vagy az ostream felhasznlsval mkd mdostk az <istream> s az <ostream> (illetve nha az <iostream>) fejllomnybl. A tbbi szabvnyos mdostt az <iomanip> fejllomny adja meg.
ios_base& boolalpha(ios_base&); // true s false szimbolikus jelzse (bemenet s // kimenet) ios_base& noboolalpha(ios_base& s); // s.unsetf(ios_base::boolalpha) // kimenetnl oktlishoz 0, hexadecimlishoz 0x // eltag // s.unsetf(ios_base::showbase)

ios_base& showbase(ios_base&); ios_base& noshowbase(ios_base& s);

ios_base& showpoint(ios_base&); ios_base& noshowpoint(ios_base& s); // s.unsetf(ios_base::showpoint) ios_base& showpos(ios_base&); ios_base& noshowpos(ios_base& s); ios_base& skipws(ios_base&); ios_base& noskipws(ios_base& s); // s.unsetf(ios_base::showpos) // reshelyek tugrsa // s.unsetf(ios_base::skipws)

Forrs: http://www.doksi.hu

852

A standard knyvtr

ios_base& uppercase(ios_base&); ios_base& nouppercase(ios_base&); ios_base& internal(ios_base&); ios_base& left(ios_base&); ios_base& right(ios_base&); ios_base& dec(ios_base&); ios_base& hex(ios_base&); ios_base& oct(ios_base&); ios_base& fixed(ios_base&); ios_base& scientific(ios_base&);

// X s E (x s e helyett) // x s e (X s E helyett) // igazts (21.4.5) // feltlts rtk utn // feltlts rtk eltt // egsz szmrendszer alapja: 10 (21.4.2) // egsz szmrendszer alapja: 16 // egsz szmrendszer alapja: 8 // lebegpontos, fixpontos: dddd.dd (21.4.3) // tudomnyos formtum: d.ddddEdd // '\n' kirsa s az // adatfolyam rtse // '\0' kirsa s az // adatfolyam rtse // az adatfolyam rtse // reshely "lenyelse"

template <class Ch, class Tr> basic_ostream<Ch,Tr>& endl(basic_ostream<Ch,Tr>&); template <class Ch, class Tr> basic_ostream<Ch,Tr>& ends(basic_ostream<Ch,Tr>&); template <class Ch, class Tr> basic_ostream<Ch,Tr>& flush(basic_ostream<Ch,Tr>&); template <class Ch, class Tr> basic_istream<Ch,Tr>& ws(basic_istream<Ch,Tr>&); smanip resetiosflags(ios_base::fmtflags f); smanip setiosflags(ios_base::fmtflags f); smanip setbase(int b); smanip setfill(int c); smanip setprecision(int n); smanip setw(int n);

// jelzbitek trlse (21.4) // jelzbitek belltsa (21.4) // egszek kirsa b alap szmrend // szerben (21.4.2) // legyen c a kitlt karakter (21.4.4) // n szmjegy (21.4.3, 21.4.6) // a kvetkez mezszlessg n karakter // (21.4.4)

Pldul a
cout << 1234 << ',' << hex << 1234 << ',' << oct << 1234 << endl;

utasts eredmnye 1234, 4d2, 2322, mg a


cout << '(' << setw(4) << setfill('#') << 12 << ") (" << 12 << ")\n";

eredmnye (##12) (12).

Forrs: http://www.doksi.hu

21. Adatfolyamok

853

Figyeljnk r, hogy ha olyan mdostkat hasznlunk, melyek nem vesznek t paramtereket, akkor nem szabad kitennnk a zrjeleket. A paramtereket is fogad mdostk hasznlathoz az <iomanip> fejllomnyt be kell ptennk (#include):
#include <iostream> using namespace std; int main() { cout << setprecision(4) // hiba: setprecision nem meghatrozott (<iomanip> kimaradt) << scientific() // hiba: ostream<<ostream& ("hamis" zrjelek) << 3.141421 << endl; }

21.4.6.3. Felhasznli mdostk A szabvnyos mdostk stlusban a programoz is kszthet j mdostkat. Az albbiakban egy olyan eszkzt mutatunk be, melynek a lebegpontos szmok formzsakor vehetjk hasznt. A pontossg minden tovbbi kimeneti mveletre vonatkozik, mg a szlessg-bellts csak a kvetkez numerikus kimeneti utastsra. Clunk most az lesz, hogy egy lebegpontos szmot az ltalunk megkvnt formban jelentsnk meg, anlkl, hogy a ksbbi kimeneti mveletek formzsra hatssal lennnk. Az alaptlet az, hogy egy olyan osztlyt ksztnk, amely formtumokat brzol, s egy olyat, amely a formzson kvl a formzni kvnt rtket is trolja. Ennek felhasznlsval kszthetnk egy olyan << opertort, amely a kimeneti adatfolyamon az adott formban jelenti meg az rtket:
Form gen4(4); // ltalnos formtum, pontossg 4

void f(double d) { Form sci8 = gen4; sci8.scientific().precision(8); }

// tudomnyos formtum, pontossg 8

cout << d << ' ' << gen4(d) << ' ' << sci8(d) << ' ' << d << '\n';

Az f(1234.56789) fggvnyhvs eredmnye a kvetkez lesz:


1234.57 1235 1.23456789e+03 1234.57

Figyeljk meg, hogy egy Form hasznlata nem befolysolja az adatfolyam llapott, hiszen

Forrs: http://www.doksi.hu

854

A standard knyvtr

a d utols kiratsakor ugyanazt a formt kapjuk, mint az elsben. me, egy leegyszerstett vltozat:
class Bound_form; // forma s rtk class Form { friend ostream& operator<<(ostream&, const Bound_form&); int prc; // pontossg int wdt; // szlessg, 0 jelentse: a szksges szlessg int fmt; // ltalnos, tudomnyos, vagy fix (21.4.3) // ... public: explicit Form(int p = 6) : prc(p) // alaprtelmezett pontossg 6 { fmt = 0; // ltalnos formtum (21.4.3) wdt = 0; // szksges szlessg } Bound_form operator()(double d) const; // Bound_form objektum ltrehozsa *this // s d alapjn

Form& scientific() { fmt = ios_base::scientific; return *this; } Form& fixed() { fmt = ios_base::fixed; return *this; } Form& general() { fmt = 0; return *this; } Form& uppercase(); Form& lowercase(); Form& precision(int p) { prc = p; return *this; } Form& width(int w) { wdt = w; return *this; } Form& fill(char); Form& plus(bool b = true); Form& trailing_zeros(bool b = true); // ... // minden tpusra // explicit pozitv eljel // zr nullk kirsa

};

Az tlet az, hogy a Form minden olyan informcit trol, ami egy adatelem formzshoz szksges. Az alaprtelmezett rtkeket gy vlasztottuk meg, hogy azok a legtbb esetben megfelelk legyenek; az egyes formzsi belltsokat a tagfggvnyek segtsgvel klnkln adhatjuk meg. A kirand rtkhez a meghatrozott formzst a ( ) opertor segtsgvel ktjk hozz. A Bound_form objektum ezek utn a megfelel << opertor segtsgvel tetszleges kimeneti adatfolyamon megjelenthet:

Forrs: http://www.doksi.hu

21. Adatfolyamok

855

struct Bound_form { const Form& f; double val; }; Bound_form(const Form& ff, double v) : f(ff), val(v) { }

Bound_form Form::operator()(double d) { return Bound_form(*this,d); } ostream& operator<<(ostream& os, const Bound_form& bf) { ostringstream s; // karakterlnc-folyamok lersa: 21.5.3 s.precision(bf.f.prc); s.setf(bf.f.fmt,ios_base::floatfield); s << bf.val; // s sszelltsa return os << s.str(); // s kirsa os-re }

A << opertor egy kevsb egyszer vltozatnak elksztst feladatnak hagyjuk (21.10[21]). A Form s a Bound_form osztlyt knnyen kibvthetjk, hogy egszek, karakterlncok stb. formzsra is hasznlhat legyen (lsd 21.10[20]). Megfigyelhetjk, hogy ezen deklarcik a << s a ( ) prostsval egy hrmas opertort hoznak ltre. A cout<<sci4(d) utastssal egyetlen fggvnyben kapcsolunk ssze egy ostream objektumot, egy formtumot s egy rtket, mieltt a tnyleges mveletet vgrehajtannk.

21.5. Fjl- s karakterlnc-folyamok


Amikor egy C++ programot elindtunk, a cout, a cerr, a clog, s a cin, illetve szleskarakteres megfelelik (21.2.1) azonnal elrhetk. Ezeket az adatfolyamokat a rendszer automatikusan hozza ltre s kti hozz a megfelel I/O eszkzhz vagy fjlhoz. Ezeken kvl azonban sajt adatfolyamokat is ltrehozhatunk, s ezek esetben neknk kell megmondanunk, hogy mihez akarjuk azokat ktni. Az adatfolyamoknak fjlokhoz, illetve karakterlncokhoz val ktse elg ltalnos feladat ahhoz, hogy a standard knyvtr kzvetlenl tmogassa. Az albbi bra a szabvnyos adatfolyam-osztlyok viszonyrendszert mutatja be:

Forrs: http://www.doksi.hu

856

A standard knyvtr

ios_base ios<> istream<> ostream<>

istrigstream<>

ifstream<>

iostream<>

ofstream<>

ostringstream<>

fstream<>

stringstream<>

Azok az osztlyok, melyek neve utn a <> jel szerepel, olyan sablonok, melyeknek paramtere egy karaktertpus. Ezek teljes neve a basic_ eltaggal kezddik. A pontozott vonal virtulis bzisosztlyt jell (15.2.4). A fjlok s a karakterlncok azon trolk kz tartoznak, melyeket rsra s olvassra is felhasznlhatunk. Ezrt ezekhez olyan adatfolyamokat hatrozhatunk meg, melyek a << s a >> mveleteket egyarnt tmogatjk. Az ilyen adatfolyamok bzisosztlya az iostream, amely az std nvtrhez tartozik s az <iostream> fejllomny rja le:
template <class Ch, class Tr = char_traits<Ch> > class basic_iostream : public basic_istream<Ch,Tr>, public basic_ostream<Ch,Tr> { public: explicit basic_iostream(basic_streambuf<Ch,Tr>* sb); virtual ~basic_iostream(); }; typedef basic_iostream<char> iostream; typedef basic_iostream<wchar_t> wiostream;

Egy iostream rst s olvasst a streambuf objektumn (21.6.4) vgzett ki- s bemeneti trmveletekkel vezrelhetjk.

21.5.1. Fjlfolyamok
A kvetkez pldban bemutatunk egy teljes programot, amely egy fjlt egy msikba msol. A fjlneveket parancssori paramterekknt lehet megadni:

Forrs: http://www.doksi.hu

21. Adatfolyamok

857

#include <fstream> #include <cstdlib> void error(const char* p, const char* p2 = "") { cerr << p << ' ' << p2 << '\n'; std::exit(1); } int main(int argc, char* argv[ ]) { if (argc != 3) error("Hibs paramterszm"); std::ifstream from(argv[1]); // bemeneti fjlfolyam megnyitsa if (!from) error("A bemeneti fjl nem nyithat meg",argv[1]); std::ofstream to(argv[2]); // kimeneti fjlfolyam megnyitsa if (!to) error("A kimeneti fjl nem nyithat meg",argv[2]); char ch; while (from.get(ch)) to.put(ch); } if (!from.eof() || !to) error("Vratlan esemny trtnt");

Egy fjlt olvassra az ifstream osztly egy objektumnak ltrehozsval nyithatunk meg, paramterknt a fjl nevt megadva. Ugyangy az ofstream osztly felhasznlsval a fjlt rsra kszthetjk fel. Mindkt esetben a ltrehozott objektum llapotnak vizsglatval ellenrizzk, hogy sikerlt-e a fjl megnyitsa. A basic_ofstream az <fstream> fejllomnyban a kvetkezkppen szerepel:
template <class Ch, class Tr = char_traits<Ch> > class basic_ofstream : public basic_ostream<Ch,Tr> { public: basic_ofstream(); explicit basic_ofstream(const char* p, openmode m = out); basic_filebuf<Ch,Tr>* rdbuf() const; // mutat az aktulis tmeneti trra (21.6.4)

};

bool is_open() const; void open(const char* p, openmode m = out); void close();

Forrs: http://www.doksi.hu

858

A standard knyvtr

A basic_ifstream nagyon hasonlt a basic_ofstream osztlyra, azzal a klnbsggel, hogy a basic_istream osztlybl szrmazik, s alaprtelmezs szerint olvassra nyithatjuk meg. Ezeken kvl a standard knyvtr biztostja a basic_fstream osztlyt is, amely szintn hasonlt a basic_ofstream-re, csak itt a bzisosztly a basic_iostream, s alaprtelmezs szerint rhat s olvashat is. Szoks szerint a leggyakrabban hasznlt tpusokhoz nll tpusnevek (typedef-ek) tartoznak:
typedef basic_ifstream<char> ifstream; typedef basic_ofstream<char> ofstream; typedef basic_fstream<char> fstream; typedef basic_ifstream<wchar_t> wifstream; typedef basic_ofstream<wchar_t> wofstream; typedef basic_fstream<wchar_t> wfstream;

A fjlfolyamok konstruktorainak msodik paramterben ms megnyitsi mdokat is megadhatunk:


class ios_base { public: // ... typedef megvalsts_fgg3 openmode; static openmode app, // hozzfzs ate, // megnyits s pozcionls a fjl vgre // (kiejtse: "at end") binary, // binris I/O (a szveges (text) md // ellentte) in, // megnyits olvassra out, // megnyits rsra trunc; // fjl csonkolsa 0 hosszsgra }; // ...

Az openmode konstansok konkrt rtke s jelentse a megvalststl fgg, ezrt ha rszleteket szeretnnk megtudni, akkor sajt fejlesztrendszernk s standard knyvtrunk lerst kell elolvasnunk, vagy ksrleteznnk kell. A megjegyzsekbl kvetkeztethetnk, hogy az egyes mdoktl krlbell mit vrhatunk. Pldul egy fjlt megnyithatunk gy, hogy minden, amit belerunk, a vgre kerljn:
ofstream mystream(name.c_str(),ios_base::app);

Forrs: http://www.doksi.hu

21. Adatfolyamok

859

De megnyithatunk egy fjlt egyszerre rsra s olvassra is:


fstream dictionary("concordance",ios_base::in|ios_base::out);

21.5.2. Adatfolyamok lezrsa


A fjlokat kzvetlenl az adatfolyam close() tagfggvnynek meghvsval zrhatjuk be:
void f(ostream& mystream) { // ... } mystream.close();

Ennek ellenre az adatfolyam destruktora is elvgzi ezt a feladatot, gy a close() fggvny meghvsra akkor van csak szksg, ha a fjlt mr azeltt be kell zrnunk, mieltt az adatfolyam hatkrbl kikerlnnk. Ez felveti a krdst, hogy az adott fejlesztkrnyezet hogyan biztosthatja, hogy a cout, cin, cerr s clog adatfolyamok mr els hasznlatuk eltt ltrejjjenek s csak utols hasznlatuk utn zrdjanak le. Termszetesen az <iostream> adatfolyam-knyvtr klnbz vltozatai klnbz mdszereket alkalmazhatnak e cl elrshez. Vgeredmnyben az, hogy hogyan oldjuk meg ezt a problmt, rszletkrds, amelyet nem kell s nem is szabad a felhasznl ltal lthatv tenni. Az albbiakban csak egy lehetsges megoldst mutatunk be, amely elg ltalnos arra, hogy klnbz tpus globlis objektumok konstruktorainak s destruktorainak lefutsi sorrendjt rgztsk. Egy konkrt megvalsts ennl hatkonyabb megoldst is knlhat a fordt s az sszeszerkeszt (linker) egyedi lehetsgeinek felhasznlsval. Az alaptlet az, hogy egy olyan segdosztlyt hozunk ltre, amely szmon tartja, hnyszor ptettk be az <iostream> fejllomnyt egy kln fordtott forrsfjlba:
class ios_base::Init { static int count; public: Init(); ~Init(); };

Forrs: http://www.doksi.hu

860

A standard knyvtr

namespace { // az <iostream> llomnyban, egy msolat minden fjlban, ios_base::Init __ioinit; // ahov <iostream>-et beptik } int ios_base::Init::count = 0; // valamelyik .c llomnyban

Minden fordtsi egysg (9.1) deklarl egy-egy sajt __ioinit nev objektumot. Az __ioinit objektumok konstruktora az ios_base::Init::count felhasznlsval biztostja, hogy az I/O knyvtr globlis objektumainak kezdeti rtkadsa csak egyszer trtnjen meg:
ios_base::Init::Init() { if (count++ == 0) { /* kezdrtk cout, cerr, cin stb. szmra */ } }

Ugyangy az __ioinit objektumok destruktora az ios_base::Init::count segtsgvel biztostja az adatfolyamok lezrst:


ios_base::Init::~Init() { if (--count == 0) { /* felszmolja cout-ot (flush stb.), illetve a cerr-t, cin-t stb. */ } }

Ez a mdszer ltalnosan hasznlhat olyan knyvtrak esetben, melyekben globlis objektumoknak kell kezdrtket adni, illetve meg kell azokat semmisteni. Az olyan rendszerekben, ahol a teljes program az elsdleges memriban kap helyet futs kzben, ez a mdszer nem jelent teljestmnyromlst. Ha azonban nem ez a helyzet, akkor jelents vesztesget jelenthet, hogy a kezdeti rtkadsok miatt knytelenek vagyunk a trgykdokat (object fjlokat) sorban beolvasni az elsdleges memriba. Ha lehetsges, kerljk el a globlis objektumok hasznlatt. Egy olyan osztlyban, ahol minden mvelet jelents, rdemes lehet minden fggvnyben elvgezni egy olyan ellenrzst, mint amit az ios_base::Init::count vgez, ezzel biztosthatjuk a kezdeti rtkadsokat. Ez a megolds azonban az adatfolyamok esetben rendkvl kltsges lenne. Egy olyan fggvny szmra, amely egyetlen karaktert r ki vagy olvas be, egy ilyen ellenrzs komoly tbbletterhelst jelentene.

21.5.3. Karakterlnc-folyamok
Az adatfolyamokat hozzkthetjk karakterlncokhoz is. Ez azt jelenti, hogy az adatfolyamok ltal knlt formzsi lehetsgek felhasznlsval karakterlncokat is rhatunk s olvashatunk. Az ilyen adatfolyamok neve stringstream, lersukat az <sstream> fejllomny tartalmazza:

Forrs: http://www.doksi.hu

21. Adatfolyamok

861

template <class Ch, class Tr=char_traits<Ch> > class basic_stringstream : public basic_iostream<Ch,Tr> { public: explicit basic_stringstream(ios_base::openmode m = out|in); explicit basic_stringstream(const basic_string<Ch>& s, openmode m = out|in); basic_string<Ch> str() const; void str(const basic_string<Ch>& s); }; basic_stringbuf<Ch,Tr>* rdbuf() const; // a karakterlnc msolatt veszi // az rtket s msolatra lltja // mutat az aktulis tmeneti trra

A basic_istringstream a basic_stringstream osztlyra hasonlt, azzal a klnbsggel, hogy a basic_istream osztlybl szrmazik, s alaprtelmezs szerint olvassra nyithatjuk meg. A basic_ostringstream is a basic_stringstream testvre, csak itt a bzisosztly a basic_ostream, s alaprtelmezs szerint rsra nyitjuk meg. Szoks szerint a leggyakrabban hasznlt egyedi cl vltozatokhoz nll tpusnevek (typedef-ek) tartoznak:
typedef basic_istringstream<char> istringstream; typedef basic_ostringstream<char> ostringstream; typedef basic_stringstream<char> stringstream; typedef basic_istringstream<wchar_t> wistringstream; typedef basic_ostringstream<wchar_t> wostringstream; typedef basic_stringstream<wchar_t> wstringstream;

Az ostringstream objektumokat pldul zenet-karakterlncok formzsra hasznlhatjuk:


string compose(int n, const string& cs) { extern const char* std_message[ ]; ostringstream ost; ost << "error(" << n << ") " << std_message[n] << " (user comment: " << cs << ')'; return ost.str(); }

A tlcsordulst ez esetben nem kell ellenriznnk, mert az ost mrete az ignyek szerint n. Ez a lehetsg klnsen hasznos olyan esetekben, amikor a megkvnt formzsi feladatok bonyolultabbak annl, amit az ltalnos, sorokat elllt kimeneti eszkzk kezelni tudnak. A karakterlnc-folyamok kezdrtkt ugyangy rtelmezi a rendszer, mint a fjlfolyamok esetben:

Forrs: http://www.doksi.hu

862

A standard knyvtr

string compose2(int n, const string& cs) // egyenrtk a compose()-zal { extern const char* std_message[ ]; ostringstream ost("hiba(",ios_base::ate); // a kezdeti karakterlnc vgtl kezd rni ost << n << ") " << std_message[n] << " (felhasznli megjegyzs: " << cs << ')'; return ost.str(); }

Az istringstream egy olyan bemeneti adatfolyam, amely az adatokat a konstruktorban megadott karakterlncbl olvassa (ugyangy, ahogy az ifilestream a megadott fjlbl olvas):
#include <sstream> void word_per_line(const string& s) { istringstream ist(s); string w; while (ist>>w) cout << w << '\n'; } // soronknt egy szt r ki

int main() { word_per_line("Ha azt hiszed, a C++ nehz, tanulj angolul"); }

A kezdrtket ad string bemsoldik az istringstream objektumba. A karakterlnc vge a bemenet vgt is jelenti. Lehetsg van olyan adatfolyamok meghatrozsra is, melyek kzvetlenl karaktertmbkbl olvasnak, illetve oda rnak (21.10[26]). Ez igen hasznos segtsg, fleg, ha rgebbi programokkal kell foglalkoznunk. Az ezen szolgltatst megvalst ostrstream s istrstream osztly mr rgebben bekerlt a szabvnyos adatfolyam-knyvtrba.

21.6. Ki- s bemeneti tmeneti trak


A kimeneti adatfolyamok vezrelve, hogy egy tmeneti trba (pufferbe) rjk a karaktereket, s azok egy kis id mlva innen kerlnek t oda, ahova valjban rni akartuk azokat. Az tmeneti trak osztlynak neve streambuf (21.6.4), melynek defincija a <streambuf> fejllomnyban szerepel. A klnbz tpus streambuf osztlyok klnbz trolsi md-

Forrs: http://www.doksi.hu

21. Adatfolyamok

863

szereket alkalmaznak. A legltalnosabb megolds az, hogy a streambuf egy tmbben trolja a karaktereket mindaddig, amg tlcsorduls nem kvetkezik be, s csak ilyenkor rja ki a teljes tmbt a megfelel helyre. Teht egy ostream objektumot az albbi formban brzolhatunk: valdi cl ostream: tellp() begin current end karakter tmeneti tr

streambuf:

Az ostream-nek s a hozz tartoz streambuf objektumnak ugyanazokat a sablonparamtereket kell hasznlnia, s ezek hatrozzk meg a karakterek tmeneti trban hasznlt karaktertpust. Az istream osztlyok nagyon hasonlak, csak ott a karakterek az ellenkez irnyba folynak. Az tmenetileg nem trolt (unbuffered) I/O olyan egyszer ki- s bemenet, ahol az adatfolyam tmeneti tra azonnal tovbbt minden karaktert, s nem vrakozik addig, amg megfelel szm karakter gylik ssze a hatkony tovbbtshoz.

21.6.1. Kimeneti adatfolyamok s tmeneti trolk


Az ostream osztly szmtalan klnbz tpus rtk karakterr talaktst teszi lehetv az alaprtelmezseknek (21.2.1) s a megadott formzsi utastsoknak (21.4) megfelelen. Az ostream ezenkvl nyjt nhny olyan fggvnyt is, melyek kzvetlenl a streambuf kezelsvel foglalkoznak:
template <class Ch, class Tr = char_traits<Ch> > class basic_ostream : virtual public basic_ios<Ch,Tr> { public: // ... explicit basic_ostream(basic_streambuf<Ch,Tr>* b);

Forrs: http://www.doksi.hu

864

A standard knyvtr

pos_type tellp(); basic_ostream& seekp(pos_type); basic_ostream& seekp(off_type, ios_base::seekdir); basic_ostream& flush(); };

// aktulis pozci lekrse // aktulis pozci belltsa // aktulis pozci belltsa

// tmeneti tr rtse (a tnyleges cl fel) // rs b-bl

basic_ostream& operator<<(basic_streambuf<Ch,Tr>* b);

Az ostream konstruktorban megadott strembuf paramter hatrozza meg, hogy a kirt karaktereket hogyan kezeljk s mikor legyenek tnylegesen kirva a meghatrozott eszkzre. Pldul egy ostringcstream (21.5.3) vagy egy ofstream (21.5.1) ltrejttekor az ostream objektumnak a megfelel streambuf (21.6.4) megadsval adunk kezdrtket. A seekp() fggvnyek azt lltjk be, hogy az ostream milyen pozcira rjon. A p uttag azt jelzi, hogy ez a pozci az adatfolyamban a karakterek kirsra (put) szolgl. Ezek a fggvnyek csak akkor mkdnek, ha az adatfolyam olyasvalamihez van ktve, amin a pozicionls rtelmes mvelet (teht pldul egy fjlhoz). A pos_type tpus egy karakterhelyet brzol a fjlban, mg az off_type az ios_base::seekdir vltoz ltal kijellt helytl val eltrst jelli:
class ios_base { // ... typedef implementation_defined seekdir; static const seekdir beg, // keress a fjl elejtl cur, // keress az aktulis pozcitl end; // keress a fjl vgtl // ...

};

Az adatfolyamok kezdpozcija a 0, gy a fjlokat nyugodtan kpzelhetjk n karakterbl ll tmbknek:


int f(ofstream& fout) { fout.seekp(10); fout << '#'; fout.seekp(-2,ios_base::cur); fout << '*'; } // fout valamilyen fjlra hivatkozik // karakter kirsa s pozci mozgatsa (+1)

Forrs: http://www.doksi.hu

21. Adatfolyamok

865

A fenti programrszlet egy # karaktert helyez a file[10] pozcira s egy * szimblumot a file[9] helyre. Az egyszer istream s ostream osztly esetben ilyen kzvetlen hozzfrsre nincs lehetsgnk (lsd 21.10[13]). Ha megprblunk a fjl eleje el vagy vge utn rni, az adatfolyam bad() llapotba kerl (21.3.3). A flush() mvelet lehetv teszi a programoz szmra, hogy a tlcsorduls megvrsa nlkl rtse ki az tmeneti trat. Lehetsg van arra is, hogy a << opertor segtsgvel egy streambuf objektumot kzvetlenl a kimeneti adatfolyamba rjunk. Ez elssorban az I/O rendszerek ksztinek hasznos segtsg.

21.6.2. Bemeneti adatfolyamok s tmeneti trolk


Az istream elssorban olyan mveleteket knl, melyek segtsgvel karaktereket olvashatunk be s alakthatunk t klnbz tpus rtkekre (21.3.1). Ezenkvl azonban nyjt nhny olyan szolgltatst is, melyekkel kzvetlenl a streambuf objektumot rhetjk el:
template <class Ch, class Tr = char_traits<Ch> > class basic_istream : virtual public basic_ios<Ch,Tr> { public: // ... explicit basic_istream(basic_streambuf<Ch,Tr>* b); pos_type tellg(); basic_istream& seekg(pos_type); basic_istream& seekg(off_type, ios_base::seekdir); basic_istream& putback(Ch c); basic_istream& unget(); int_type peek(); int sync(); // aktulis pozci lekrdezse // aktulis pozci belltsa // aktulis pozci belltsa

// c visszaraksa az tmeneti trba // putback alkalmazsa a legutbb beolvasott // karakterre // a kvetkez beolvasand karakter // tmeneti tr rtse (flush)

basic_istream& operator>>(basic_streambuf<Ch,Tr>* b); // olvass b-be basic_istream& get(basic_streambuf<Ch,Tr>& b, Ch t = Tr::newline()); }; streamsize readsome(Ch* p, streamsize n); // legfeljebb n karakter beolvassa

Forrs: http://www.doksi.hu

866

A standard knyvtr

A pozicionl fggvnyek ugyangy mkdnek, mint az ostream osztlybeli megfelelik (21.6.1). A g uttag azt jelzi, hogy ez a pozci a karakterek beolvassnak (get) helye. A p s a g uttagokra azrt van szksg, mert kszthetnk olyan iostream osztlyt is, melynek bzisosztlya az istream s az ostream is, s ilyenkor is meg kell tudnunk klnbztetni az rsi s az olvassi pozcit. A putback() fggvnyek azt teszik lehetv, hogy a program visszategye az adatfolyamba azokat a karaktereket, amelyekre egyelre nincs szksge, de ksbb mg fel szeretnnk dolgozni. Erre a 21.3.5 pontban mutattunk pldt. Az unget() fggvny a legutoljra beolvasott karaktert teszi vissza az adatfolyamba. Sajnos a bemeneti adatfolyamok ilyen visszagrgetse nem mindig lehetsges. Pldul ha megprbljuk visszarni az utols eltti beolvasott karaktert is, az ios_base::failbit hibajelz bekapcsoldik. Csak abban lehetnk biztosak, hogy egyetlen, sikeresen beolvasott karaktert vissza tudunk rni. A peek() beolvassa a kvetkez karaktert, de azt az tmeneti trban hagyja, gy jra beolvashatjuk. Teht a c=peek() utasts egyenrtk a (c=get(),unget(),c) s a (putback(c=get()),c) parancs-sorozatokkal. Figyeljnk r, hogy a failbit bekapcsolsa kivtelt vlthat ki (21.3.6). Az istream-ek tmeneti trnak azonnali kirtst (flushing) a sync() paranccsal knyszerthetjk ki, de ezt a mveletet sem mindig hasznlhatjuk biztonsgosan. Bizonyos tpus adatfolyamokban ehhez jra kellene olvasnunk a karaktereket az eredeti forrsbl, ami nem mindig lehetsges vagy hibs bemenetet eredmnyezhet. Ezrt a sync() 0 rtket ad vissza, ha sikeresen lefutott, s 1-t, ha nem. Ha a mvelet sikertelen volt, erre az ios_base::badbit (21.3.3) is felhvja a figyelmnket. A badbit rtknek megvltozsa is kivtelt vlthat ki (21.3.6). Ha a sync() fggvnyt olyan tmeneti trra hasznljuk, amely egy ostream objektumhoz kapcsoldik, akkor az tmeneti tr tartalma a kimenetre kerl. A >> s a get() mveletnek is van olyan vltozata, mely kzvetlenl az tmeneti trba r, de ezek is elssorban az I/O szolgltatsok ksztinek jelentenek hasznos segtsget, hiszen az adatfolyamok tmeneti trainak kzvetlen elrsre csak nekik van szksgk. A readsome() fggvny egy alacsonyszint mvelet, mellyel a programoz megllapthatja, hogy van-e az adatfolyamban beolvassra vr karakter. Ez a szolgltats akkor nagyon hasznos, ha nem akarunk a bemenet megrkezsre vrni (pldul a billentyzetrl). Lsd mg: in_avail() (21.6.4).

21.6.3. Az adatfolyamok s az tmeneti trak kapcsolata


Az adatfolyam s a hozz tartoz tmeneti tr kztti kapcsolatot az adatfolyamok basic_ios osztlya tartja fenn:

Forrs: http://www.doksi.hu

21. Adatfolyamok

867

template <class Ch, class Tr = char_traits<Ch> > class basic_ios : public ios_base { public: // ... basic_streambuf<Ch,Tr>* rdbuf() const; // mutat az tmeneti trra // az tmeneti tr belltsa, clear(), s mutat visszaadsa a rgi trra basic_streambuf<Ch,Tr>* rdbuf(basic_streambuf<Ch,Tr>* b); locale imbue(const locale& loc); char narrow(char_type c, char d) const; char_type widen(char c) const; // ... protected: basic_ios(); void init(basic_streambuf<Ch,Tr>* b); }; // helyi sajtossgok belltsa (s // a rgi rtk kiolvassa) // char rtk char_type tpus c-bl // char_type rtk a c char-bl

// a kezdeti tmeneti tr belltsa

Azon kvl, hogy lekrdezhetjk s bellthatjuk az adatfolyam streambuf objektumt (21.6.4), a basic_ios osztlyban szerepel az imbue() fggvny is, mellyel beolvashatjuk s tllthatjuk az adatfolyam helyi sajtossgokat ler locale objektumt (21.7). Az imbue() fggvnyt az ios_base (21.7.1) objektumra, a pubimbue() fggvnyt az tmeneti trra (21.6.4) kell meghvnunk. A narrow() s a widen() fggvny segtsgvel az tmeneti tr karaktertpust konvertlhatjuk char tpusra, vagy fordtva. A narrow(c,d) fggvnyhvs msodik paramtere az a char, amelyet akkor szeretnnk visszakapni, ha a c-ben megadott char_type rtknek nincs char megfelelje.

21.6.4. Adatfolyamok tmeneti trolsa


Az I/O mveletek meghatrozsa fjltpus emltse nlkl trtnt, de nem kezelhetnk minden eszkzt ugyangy az tmeneti trolsnl. Egy karakterlnchoz kttt ostream objektumnak (21.5.3) pldul msfle trolsra van szksge, mint egy fjlhoz kttt (21.5.1) kimeneti adatfolyamnak. Ezeket a problmkat gy oldhatjuk meg, hogy a klnbz adatfolyamokhoz a kezdeti rtkadskor klnbz tpus tmeneti trakat rendelnk. Mivel a klnbz tpus trak ugyanazokat a mveleteket biztostjk, az ostream osztlynak nem kell klnbznek lennie hozzjuk. Minden tmeneti tr a streambuf osztlybl szr-

Forrs: http://www.doksi.hu

868

A standard knyvtr

mazik. A streambuf virtulis fggvnyeket nyjt azokhoz a mveletekhez, melyek a klnbz trolsi mdszereknl eltrek lehetnek. Ilyenek pldul a tlcsordulst s az alulcsordulst kezel eljrsok. A basic_streambuf osztly kt felletet tesz elrhetv. A nyilvnos (public) fellet elssorban azoknak hasznos, akik olyan adatfolyam-osztlyokat akarnak elkszteni, mint az istream, az ostream, az fstream vagy a stringstream. A vdett (protected) fellet clja azon programozk ignyeinek kiszolglsa, akik j trolsi mdszert akarnak bevezetni, vagy j bemeneti forrsokat s kimeneti clokat akarnak kezelni. A streambuf osztly megrtshez rdemes elszr megismerkednnk az alapjt kpez tmeneti trterlet modellel, amelyet a vdett fellet biztost. Kpzeljk el, hogy a streambuf objektumnak van egy kimeneti terlete, amelybe a << opertor segtsgvel rhatunk, s van egy bemeneti terlete, amelybl a >> opertor olvas. Mindkt terletet hrom mutat r le: egy a kezdpontra, egy az aktulis pozcira, egy pedig az utols utni elemre mutat. Ezeket a mutatkat fggvnyeken keresztl rhetjk el:
template <class Ch, class Tr = char_traits<Ch> > class basic_streambuf { protected: Ch* eback() const; Ch* gptr() const; Ch* egptr() const; // a bemeneti tr kezdete // kvetkez karakter (a kvetkez olvass innen trtnik) // a bemeneti tr utols eleme utnra mutat

void gbump(int n); // n hozzadsa gptr()-hez void setg(Ch* begin, Ch* next, Ch* end); // eback(), gptr(), s egptr() belltsa Ch* pbase() const; // a kimeneti tr kezdete Ch* pptr() const; // a kvetkez res karakter (a kvetkez kirs ide trtnik) Ch* epptr() const; // a kimeneti tr utols eleme utnra mutat void pbump(int n); // n hozzadsa pptr()-hez void setp(Ch* begin, Ch* end); // pbase() s pptr() begin-re, epptr() end-re lltsa // ...

};

Egy karaktertmbre a setg() s a setp() fggvny segtsgvel megfelelen bellthatjuk a mutatkat. A programoz a bemeneti terletet a kvetkez formban rheti el:
template <class Ch, class Tr = char_traits<Ch> > basic_streambuf<Ch,Tr>::int_type basic_streambuf<Ch,Tr>::snextc() // az aktulis karakter tlpse, a kvetkez beolvassa {

Forrs: http://www.doksi.hu

21. Adatfolyamok

869

if (1<egptr()-gptr()) { // ha legalbb kt karakter van az tmeneti trban gbump(1); // az aktulis karakter tlpse return Tr::to_int_type(*gptr()); // az j aktulis karakter visszaadsa } if ( 1==egptr()-gptr()) { // ha pontosan egy karakter van az tmeneti trban gbump(1); // az aktulis karakter tlpse return underflow(); } // az tmeneti tr res (vagy nem hasznlt), prbljuk feltlteni if (Tr::eq_int_type(uflow(), Tr::eof()) return Tr::eof(); if (0<eptr()-gptr()) return Tr::to_int_type(*gptr()); // az j aktulis karakter visszaadsa return underflow; }

Az tmeneti trat a gptr()-en keresztl rjk el, az egptr() a bemeneti terlet hatrt jelzi. A karaktereket a valdi forrsbl az uflow() s az underflow() olvassk ki. A traits_type::to_int_type() meghvsa biztostja, hogy a kd fggetlen lesz az ppen hasznlt karaktertpustl. A kd tbbfle tpus adatfolyam-trral hasznlhat s figyelembe veszi azt is, hogy az uflow() s az underflow() virtulis fggvnyek (a setg() segtsgvel) j bemeneti terletet is meghatrozhatnak. A streambuf nyilvnos fellete a kvetkez:
template <class Ch, class Tr = char_traits<Ch> > class basic_streambuf { public: // szoksos tpus-meghatrozsok (21.2.1) virtual ~basic_streambuf(); locale pubimbue(const locale &loc); locale getloc() const; // locale belltsa (s rgi kiolvassa) // locale kiolvassa // tmeneti trterlet belltsa

basic_streambuf* pubsetbuf(Ch* p, streamsize n);

pos_type pubseekoff(off_type off, ios_base::seekdir way, // pozci (21.6.1) ios_base::openmode m = ios_base::in|ios_base::out); pos_type pubseekpos(pos_type p, ios_base::openmode m = ios_base::in|ios_base::out); int pubsync(); int_type snextc(); int_type sbumpc(); int_type sgetc(); // sync(), lsd 21.6.2 // aktulis karakter tlpse, a kvetkez kiolvassa // gptr() lptetse 1-el // az aktulis karakter beolvassa

Forrs: http://www.doksi.hu

870

A standard knyvtr

streamsize sgetn(Ch* p, streamsize n); int_type sputbackc(Ch c); int_type sungetc();

// beolvass p[0]..p[n-1]-be

// c visszahelyezse az tmeneti trba (21.6.2) // az utols karakter visszahelyezse // p[0]..p[n-1] kirsa // bemenet rendben?

int_type sputc(Ch c); // c kirsa streamsize sputn(const Ch* p, streamsize n); streamsize in_avail(); // ...

};

A nyilvnos fellet olyan fggvnyeket tartalmaz, melyekkel karaktereket helyezhetnk az tmeneti trba, illetve karaktereket vehetnk ki onnan. Ezek a fggvnyek ltalban nagyon egyszerek s helyben kifejtve (inline) is fordthatk, ami a hatkonysg szempontjbl kulcsfontossg. Azok a fggvnyek, melyek tevkenysge fgg a hasznlt trolsi mdtl, a vdett fellet megfelel eljrst hvjk meg. A pubsetbuf() pldul a setbuf() fggvnyt hvja meg, amelyet a leszrmazott osztly fellr az tmenetileg trolt karakterek szmra val memriafoglalshoz. Teht az olyan mveletek megvalstsra, mint a setbuf(), kt fggvny szolgl, ami azrt praktikus, mert gy egy iostream is vgezhet rendfenntart mveleteket, mikzben a felhasznl fggvnyt meghvja. A virtulis fggvny meghvst egy try blokkba helyezhetjk, s elkaphatjuk a felhasznli kd ltal kivltott kivteleket is. Alaprtelmezs szerint a setbuf(0,0) az tmeneti trols hinyt jelenti, mg a setbuf(p,n) a p[0],, p[n-1] tartomny hasznlatt rja el a karakterek tmeneti trolsra. Az in_avail() fggvny meghvsval azt llapthatjuk meg, hny karakter ll rendelkezsnkre az tmeneti trban. Ennek vizsglatval elkerlhetjk a bemenetre val vrakozst. Amikor olyan adatfolyambl olvasunk, amely a billentyzethez kapcsoldik, a cin.get(c) akr addig vrakozik, amg a felhasznl vissza nem tr az ebdjrl. Egyes rendszerekben s alkalmazsokban rdemes felkszlnnk erre a lehetsgre:
if (cin.rdbuf()->in_avail()) { // get() nem fog lefagyni cin.get(c); // csinlunk valamit } else { // get() lefagyhat // valami mst csinlunk }

Forrs: http://www.doksi.hu

21. Adatfolyamok

871

Vigyzzunk e lehetsg hasznlatakor, mert nha nem knny annak megllaptsa, hogy van-e beolvashat bemenet. Az in_avail() adott vltozata esetleg 0 rtket ad vissza, ha egy bemeneti mveletet sikeresen vgrehajthatunk. A nyilvnos fellet mellett amelyet a basic_istream s a basic_ostream hasznl a basic_streambuf egy vdett felletet is knl, mellyel az adatfolyamok tmeneti trainak elksztst segti, s azokat a virtulis fggvnyeket vezeti be, amelyek meghatrozzk az tmeneti trols irnyelveit:
template <class Ch, class Tr = char_traits<Ch> > class basic_streambuf { protected: // ... basic_streambuf(); virtual void imbue(const locale &loc); virtual basic_streambuf* setbuf(Ch* p, streamsize n); virtual pos_type seekoff(off_type off, ios_base::seekdir way, ios_base::openmode m = ios_base::in|ios_base::out); virtual pos_type seekpos(pos_type p, ios_base::openmode m = ios_base::in|ios_base::out); virtual int sync(); virtual int showmanyc(); virtual streamsize xsgetn(Ch* p, streamsize n); virtual int_type underflow(); virtual int_type uflow(); // sync(), lsd 21.6.2 // n karakter beolvassa // az olvassi terlet res, eof vagy // karakter visszaadsa //az olvassi terlet res, eof vagy // karakter visszaadsa, gptr() nvelse // a putback nem jrt sikerrel // n karakter kirsa // kir terlet megtelt // locale belltsa

virtual int_type pbackfail(int_type c = Tr::eof()); virtual streamsize xsputn(const Ch* p, streamsize n); virtual int_type overflow(int_type c = Tr::eof());

};

Az underflow() s az uflow() fggvny szolgl arra, hogy a kvetkez karaktereket beolvassuk a tnyleges bemeneti forrsrl, ha az tmeneti tr res. Ha az adott forrson nincs beolvashat bemenet, az adatfolyam eof llapotba (21.3.3) kerl. Ha ez nem vlt ki kivtelt, a traits_type::eof() rtket kapjuk vissza. A gptr()-t a visszaadott karakter utn az uflow() n-

Forrs: http://www.doksi.hu

872

A standard knyvtr

veli, az underflow() viszont nem. A rendszerben ltalban nem csak azok az tmeneti trak vannak, amelyeket az iostream knyvtr hoz ltre, gy akkor is elfordulhatnak tmeneti trols miatti kslekedsek, amikor tmenetileg nem trolt adatfolyamot hasznlunk. Az overflow() fggvny akkor fut le, amikor az tmeneti tr megtelik, s ez tovbbtja a karaktereket a kimenet valdi clllomsra. Az overflow(c) utasts az tmeneti tr tartalmt s a c karaktert is kirja. Ha a clllomsra nem lehet tbb karaktert rni, az adatfolyam eof llapotba kerl (21.3.3). Ha ez nem okoz kivtelt, a traits_type::eof() rtket kapjuk. A showmanyc() show how many characters, mondd meg, hny karakter egy igen rdekes fggvny. Clja az, hogy a felhasznl lekrdezhesse, hny karaktert tudunk gyorsan beolvasni, azaz az opercis rendszer tmeneti trainak kirtsvel, de a lemezrl val olvass megvrsa nlkl. A showmanyc() fggvny 1 rtket ad vissza, ha nem tudja garantlni, hogy akr egy karaktert is be tudnnk olvasni a fjlvge jel megrkezse eltt. Ez az eljrs (szksgszeren) elg alacsony szint, s nagymrtkben fgg az adott megvalststl. Soha ne hasznljuk a showmanyc() fggvnyt fejlesztrendszernk dokumentcijnak alapos tanulmnyozsa s egy kis ksrletezs nlkl. Alaprtelmezs szerint minden adatfolyam a globlis helyi sajtossgoknak (global local) megfelelen (21.7) mkdik. A pubimbue(loc) vagy az imbue(loc) fggvny meghvsval az adatfolyamot a loc objektumban megfogalmazott helyi sajtossgok hasznlatra utasthatjuk. Az adott adatfolyamhoz hasznlt streambuf osztlyt a basic_streambuf osztlybl kell szrmaztatnunk. Ebben kell szerepelnie azoknak a konstruktoroknak s kezdrtk-ad fggvnyeknek, melyek a streambuf objektumot a karakterek valdi forrshoz vagy cljhoz ktik, s ez rja fell a virtulis fggvnyeket az tmeneti trols mdjnak megvalstshoz:
template <class Ch, class Tr = char_traits<Ch> > class basic_filebuf : public basic_streambuf<Ch,Tr> { public: basic_filebuf(); virtual ~basic_filebuf(); bool is_open() const; basic_filebuf* open(const char* p, ios_base::openmode mode); basic_filebuf* close(); protected: virtual int showmanyc(); virtual int_type underflow(); virtual int_type uflow(); virtual int_type pbackfail(int_type c = Tr::eof());

Forrs: http://www.doksi.hu

21. Adatfolyamok

873

virtual int_type overflow(int_type c = Tr::eof()); virtual basic_streambuf<Ch,Tr>* setbuf(Ch* p, streamsize n); virtual pos_type seekoff(off_type off, ios_base::seekdir way, ios_base::openmode m = ios_base::in|ios_base::out); virtual pos_type seekpos(pos_type p, ios_base::openmode m = ios_base::in|ios_base::out); virtual int sync(); virtual void imbue(const locale& loc);

};

Az tmeneti trak kezelsre hasznlt fggvnyek vltozatlan formban a basic_streambuf osztlybl szrmaznak. Csak a kezdeti rtkadsra s a trolsi mdszerre hat fggvnyeket kell kln megadnunk. Szoks szerint a leggyakoribb osztlyokat s szles megfeleliket kln typedef-ek teszik knnyen elrhetv:
typedef basic_streambuf<char> streambuf; typedef basic_stringbuf<char> stringbuf; typedef basic_filebuf<char> filebuf; typedef basic_streambuf<wchar_t> wstreambuf; typedef basic_stringbuf<wchar_t> wstringbuf; typedef basic_filebuf<wchar_t> wfilebuf;

21.7. Helyi sajtossgok


A locale egy olyan objektum, amely a karakterek betk, szmok stb. szerinti osztlyozsnak mdjt adja meg, illetve a karakterlncok rendezsi sorrendjt s a szmok megjelensi formjt kirskor s beolvasskor. Az iostream knyvtr ltalban automatikusan hasznl egy ltalnos locale objektumot, amely biztostja nhny nyelv s kultra szoksainak betartst. Ha ez megfelel cljainknak, nem is kell foglalkoznunk locale objektumokkal. Ha azonban ms szoksokat akarunk kvetni, akkor az adatfolyam viselkedst gy vltoztathatjuk meg, hogy lecserljk a hozz kttt locale objektumot.

Forrs: http://www.doksi.hu

874

A standard knyvtr

Az std nvtrhez tartoz locale osztlyt a <locale> fejllomny rja le:


class locale { public: // ... locale() throw(); explicit locale(const char* name); basic_string<char> name() const; //a teljes deklarcit lsd D.2

// az aktulis globlis locale msolata // locale ltrehozsa C stlus locale nv alapjn // a hasznlt locale neve

locale(const locale&) throw(); // locale msolsa const locale& operator=(const locale& ) throw(); // locale msolsa static locale global(const locale&); }; static const locale& classic(); // a globlis locale belltsa // (az elz rtk kiolvassa) // a C ltal meghatrozott locale

A helyi sajtossgok legkznsgesebb hasznlata, amikor egy locale objektumot egy msikra kell cserlnnk:
void f() { std::locale loc("POSIX"); cin.imbue(loc); // ... cin.imbue(std::locale()); }

// szabvnyos POSIX locale // cin hasznlja loc-ot // cin visszalltsa az alaprtelmezett (globlis) // locale hasznlatra

Az imbue() fggvny a basic_ios (21.7.1) osztly tagja. Lthatjuk, hogy nhny, viszonylag szabvnyos locale objektum sajt karakterlnc-neveket hasznl. Ezeket az elnevezseket a C nyelv is hasznlja. Elrhetjk azt is, hogy a C++ minden jonnan ksztett adatfolyamhoz automatikusan az ltalunk megadott locale objektumot hasznlja:
void g(const locale& loc = locale()) { } locale old_global = locale::global(loc); // ... // alaprtelmezs szerint az aktulis // globlis locale hasznlata // legyen loc az alaprtelmezs

Forrs: http://www.doksi.hu

21. Adatfolyamok

875

A globlis locale objektum tlltsa nincs hatssal a mr ltez adatfolyamokra, mert azok tovbbra is a globlis locale rgi rtkt hasznljk. Ez vonatkozik pldul a cin, cout stb. adatfolyamra is. Ha ezeket is meg akarjuk vltoztatni, kzvetlenl az imbue() fggvnyt kell hasznlnunk. Azzal, hogy egy adatfolyamhoz j locale objektumot rendelnk, tbb helyen is megvltoztatjuk arculatt, viselkedst. A locale osztly tagjait kzvetlenl is hasznlhatjuk, j helyi sajtossgok meghatrozsra vagy a rgiek bvtsre. A locale arra is hasznlhat, hogy belltsuk a pnzegysgek, dtumok stb. megjelensi formjt ki- s bemenetkor (21.10[25]), vagy a klnbz kdkszletek kztti talaktst. A helyi sajtossgok hasznlatnak elvt, illetve a locale s facet (arculat, viselkeds) osztlyokat a D fggelk rja le. A C stlus locale meghatrozsa a <clocale> s a <locale.h> fejllomnyokban tallhat.

21.7.1. Adatfolyam-visszahvsok
Nha a programozk az adatfolyamok llapotlerst bvteni akarjk. Pldul elvrhatjuk egy adatfolyamtl, hogy tudja, milyen formban kell egy komplex szmot megjelenteni: polr- vagy Descartes-koordintarendszerben. Az ios_base osztlyban szerepel az xalloc() fggvny, mellyel az ilyen egyszer llapotinformcik szmra foglalhatunk memrit. Az xalloc() ltal visszaadott rtk azt a kt memriaterletet adja meg, amelyet az iword() s a pword() fggvnnyel elrhetnk:
class ios_base { public: // ... ~ios_base(); locale imbue(const locale& loc); locale getloc() const; static int xalloc(); long& iword(int i); void*& pword(int i); // visszahvsok enum event { erase_event, imbue_event, copyfmt_event }; // esemnytpusok // locale kiolvassa s belltsa, lsd D.2.3 // locale kiolvassa // egy egsz s egy mutat lefoglalsa (mindkett 0 // kezdrtkkel) // az iword(i) egsz elrse // a pword(i) mutat elrse

};

typedef void (*event_callback)(event, ios_base&, int i); void register_callback(event_callback f, int i); // f hozzrendelse word(i)-hez

Forrs: http://www.doksi.hu

876

A standard knyvtr

A felhasznlknak s a knyvtrak ksztinek nha szksgk van arra, hogy rtestst kapjanak az adatfolyam llapotnak megvltozsrl. A register_callback() eljrs segtsgvel a fggvnyeket bejegyezhetjk, s azok akkor futnak le, amikor a nekik kijellt esemny bekvetkezik. Teht pldul az imbue(), a copyfmt() vagy a ~ios_base() fggvny meghvsa maga utn vonhatja egy bejegyzett fggvny lefutst, amely sorrendben az imbue_event, a copyfmt_event, illetve az erase_event esemnyt figyeli. Amikor az llapot megvltozik, a bejegyzett fggvny a register_callback() fggvnyben megadott i paramtert kapja meg. Ez a trolsi s visszahvsi eljrs meglehetsen bonyolult, teht csak akkor hasznljuk, ha felttlenl szksgnk van az alacsonyszint formzsi szolgltatsok kibvtsre.

21.8. C stlus ki- s bemenet


Mivel a C s a C++ kdokat gyakran keverik, a C++ adatfolyamon alapul ki- s bemeneti eljrsait sokszor egytt hasznljuk a C nyelv printf() fggvnycsaldjt hasznl I/O rendszerrel. A C stlus I/O fggvnyek a <cstdio> s az <stdio.h> fejllomnyban tallhatk. Mivel a C fggvnyek szabadon meghvhatk a C++ programokbl, sok programoz szvesebben hasznlja a megszokott C ki- s bemeneti szolgltatsokat, de mg ha az adatfolyamokon alapul I/O eljrsokat rszestjk is elnyben, nha akkor is tallkozni fogunk C stlus megoldsokkal. A C s a C++ stlus be- s kimenet karakterszinten keverhet. Ha a sync_with_stdio() fggvnyt meghvjuk a legels adatfolyam alap I/O mvelet eltt, akkor a C s a C++ stlus eljrsok ugyanazokat az tmeneti trakat fogjk hasznlni. Ha az els adatfolyam mvelet eltt a sync_with_stdio(false) parancsot adjuk ki, az tmeneti trakat a rendszer biztosan nem fogja megosztani, gy egyes esetekben nvelhetjk a teljestmnyt:
class ios_base { // ... static bool sync_with_stdio(bool sync = true); };

// kiolvass s bellts

Az adatfolyam elv kimeneti fggvnyek legfbb elnye a C standard knyvtrnak printf() fggvnyvel szemben, hogy az adatfolyam-fggvnyek tpusbiztosak s egysges stlust biztostanak a klnbz objektumok megadsra, legyen azok tpusa akr beptett, akr felhasznli.

Forrs: http://www.doksi.hu

21. Adatfolyamok

877

A C ltalnos kimeneti fggvnyei formzott kimenetet lltanak el, melyben a megadott paramterek sorozatnak megjelenst a format formzsi karakterlnc hatrozza meg:
int printf(const char* format ...); int fprintf(FILE*, const char* format ...); int sprintf(char* p, const char* format ...); // rs az stdout-ra // rs "file"-ba (stdout, stderr) // rs p[0] ... stb.-re

A formzsi karakterlnc ktfajta elemet tartalmazhat: sima karaktereket, amelyeket a rendszer egyszeren a kimeneti adatfolyamba msol, illetve talakts-meghatrozsokat (conversion-specification), melyek mindegyike egy paramter talaktst s kirst vezrli. Az talakts-meghatrozsokat a % karakterrel kell kezdeni:
printf("Jelen volt %d szemly.",no_of_members);

Itt a %d azt hatrozza meg, hogy a no_of_members vltozt int rtkknt kell kezelni s a megfelel decimlis szmjegyek kirsval kell megjelenteni. Ha a no_of_members==127, akkor a megjelen eredmny a kvetkez lesz:
Jelen volt 127 szemly.

Az talakts-meghatrozsokat igen sokflekppen megadhatjuk s nagyfok rugalmassgot biztostanak. A % karaktert kveten az albbi jelek llhatnak: + 0 A nem ktelez mnuszjel azt rja el, hogy a rendszer a megadott mezben az talaktott rtket balra igaztsa. A nem ktelez pluszjel hasznlata azt eredmnyezi, hogy az eljeles tpus rtkek mindig + vagy - eljellel fognak megjelenni. A nem ktelez nulla jelentse: a mezszlessg feltltsre numerikus rtkek esetn 0 karakterekkel trtnik. Ha a - vagy a pontossg megadott, akkor a 0 elrst figyelmen kvl hagyjuk. A szintn nem ktelez # azt jelenti, hogy a lebegpontos rtkek mindenkppen tizedesponttal egytt jelennek meg, fggetlenl attl, hogy utna esetleg csak 0 szmjegyek llnak; hogy a lezr nullk is megjelennek; illetve hogy az oktlis szmok eltt egy 0 karakter, hexadecimlis szmok eltt pedig a 0x vagy a 0X karakterpr jelenik meg. A nem ktelez szmjegysorozat a mez szlessgt hatrozza meg. Ha az talaktott rtk kevesebb karaktert tartalmaz, mint a mez szlessge, akkor annak bal oldaln (illetve balra igazts esetn a jobb oldaln) res karakterek jelennek meg a megfelel szlessg elrse rdekben. Ha a mezszlessg megadst 0 karakterrel kezdjk, a szlessgnvel karakterek szkz helyett nullk lesznek.

Forrs: http://www.doksi.hu

878

A standard knyvtr

. d

h l % c

A nem ktelez pont kirsval vlaszthatjuk el a mezszlessget megad rtket a kvetkez szmjegy-sorozattl. jabb, nem ktelez szmjegy-sorozat. A pontossgot hatrozza meg, azaz e s ftalakts esetn a tizedes pont utn megjelen szmjegyek szmt, karakterlncok esetben pedig a megjelentend karakterek legnagyobb szmt. A mezszlessg vagy a pontossg konkrt megadsa helyett hasznlhatjuk a * karaktert, melynek hatsra a kvetkez paramterben szerepl egsz rtk adja meg a kvnt rtket. A nem ktelez h karakter azt jelzi, hogy a kvetkez d, o, x vagy u egy short int paramterre vonatkozik. A nem ktelez l karakter azt jelzi, hogy a kvetkez d, o, x vagy u egy long int paramterre vonatkozik. Azt jelzi, hogy a % karakter kirand. Paramtert nem hasznl fel. Az talakts tpust jelz karakter. Az talakt karakterek s jelentseik a kvetkezk: d Egsz rtk, amit tzes szmrendszerben kell megjelenteni. i Egsz rtk, amit tzes szmrendszerben kell megjelenteni. o Egsz rtk, amit nyolcas szmrendszerben kell megjelenteni. x Egsz rtk, amit tizenhatos szmrendszerben kell megjelenteni, 0x kezdettel X Egsz rtk, amit tizenhatos szmrendszerben kell megjelenteni, 0X kezdettel. f Egy float vagy egy double paramtert kell tzes szmrendszerbeli rtkk alaktani a [-]ddd.ddd formtummal. A tizedespont utn ll szmjegyek szmt a megadott paramter pontossga hatrozza meg. Ha szksg van r, az rtket a rendszer kerekti. Ha pontossg nincs megadva, 6 szmjegy jelenik meg; ha pontossgknt 0 rtket adunk meg s a # jelet nem hasznljuk, a tizedesjegyek nem rdnak ki. e Egy float vagy double paramtert alakt tzes szmrendszerbeli alakjra a tudomnyos [-]d.ddde+dd vagy a [-]d.ddde-dd alakra, ahol a tizedespont eltt pontosan egy jegy szerepel, mg a tizedespont utn ll szmjegyek szmt az adott paramter pontossg-meghatrozsa adja meg. Ha szksges, az rtket a rendszer kerekti. Ha pontossg nincs megadva, az alaprtelmezett rtk 6 lesz; ha a pontossg nulla s a # jelet nem hasznltuk, sem a tizedespont, sem az utna ll szmjegyek nem jelennek meg. E Nagyon hasonlt az e-re, de a kitev jellsre ez a forma nagy E bett hasznl. g A float vagy double tpus paramtert d, f, vagy e stlusban rja ki, aszerint, hogy melyik biztostja a legnagyobb pontossgot a lehet legkisebb terleten. G Ugyanaz, mint a g, de a kitev jellsre a nagy E bett hasznlja. c Karakter paramtert jelent meg. A nullkaraktereket figyelmen kvl hagyja. s Az gy tvett paramter egy karakterlnc (karakterre hivatkoz mutat).

Forrs: http://www.doksi.hu

21. Adatfolyamok

879

A karaktereket addig msolja a kimenetre, amg a nullkaraktert, vagy a pontossg-meghatrozsban meghatrozott karakterszmot el nem ri. Ha a pontossg 0 vagy nincs megadva, akkor csak a nullkarakter jelenti a karakterlnc kirsnak vgt. p A paramter egy mutat. A megjelentshez hasznlt formtum a megvalststl fgg. u Eljel nlkli egsz paramtert alakt tzes szmrendszerbeli alakra. n A printf(), az fprintf() vagy az sprintf() meghvsval eddig kirt karakterek szmt a mutatott int-be rja. Az nem fordulhat el, hogy nulla vagy kicsi mezszlessg miatt csonkols trtnjen, mert a rendszer csak akkor foglalkozik a szlessg kezelsvel, ha a mez szlessge meghaladja a benne szerepl rtk szlessgt. me egy kicsivel bonyolultabb plda:
char* line_format = "#sor szma %d \"%s\"\n"; int line = 13; char* file_name = "C++/main.c"; printf("int a;\n"); printf(line_format,line,file_name); printf("int b;\n");

Az eredmny a kvetkez lesz:


int a; #sor szma 13 "C++/main.c" int b;

A printf() fggvny hasznlata nem biztonsgos, mert a paramterek beolvassakor nem trtnik tpusellenrzs. Az albbi hiba pldul ltalban megjsolhatatlan kimenetet eredmnyez vagy mg nagyobb hibt:
char x; // ... printf("rossz bemeneti karakter: %s",x);

// %s helyett %c kell

A printf() ennek ellenre nagyfok rugalmassgot biztost, olyan formban, amelyet a C programozk mr jl ismernek.

Forrs: http://www.doksi.hu

880

A standard knyvtr

A getchar() fggvny hasonlan jl ismert mdszert ad karakterek beolvassra:


int i; while ((i=getchar())!=EOF) { /* i hasznlata */ }

Figyeljnk r, hogy a fjlvge jelet csak akkor tudjuk felismerni az int tpus EOF konstans segtsgvel, ha a getchar() ltal visszaadott rtket is int tpus vltozban troljuk s nem char tpus adatknt. A C bemeneti/kimeneti rendszernek alaposabb megismershez olvassunk el egy C kziknyvet vagy Kernighan s Ritchie A C programozsi nyelv cm knyvt (Mszaki Knyvkiad, 1994) [Kernighan, 1988].

21.9. Tancsok
[1] Az olyan felhasznli tpusokhoz, melyekhez rtelmes szveges forma rendelhet, rdemes megadnunk a >> s a << opertort. 21.2.3, 21.3.5. [2] Ha alacsony precedencij opertorokat tartalmaz kifejezsek eredmnyt akarjuk megjelenteni, zrjeleket kell hasznlnunk. 21.2. [3] j >> s << opertorok ltrehozshoz nem kell mdostanunk az istream s az ostream osztlyt. 21.2.3. [4] Olyan fggvnyeket is kszthetnk, amelyek a msodik (vagy az utni) paramter alapjn virtulisknt mkdnek. 21.2.3.1. [5] Ne feledjk, hogy a >> alaprtelmezs szerint tugorja az reshely karaktereket. 21.3.2. [6] Alacsonyszint bemeneti fggvnyeket (pldul get() s read()) ltalban csak magasabb szint eljrsok megvalstshoz kell hasznlnunk. 21.3.4. [7] Mindig krltekinten fogalmazzuk meg a befejezsi felttelt, ha a get(), a getline() vagy a read() fggvnyt hasznljuk. 21.3.4. [8] Az llapotjelz bitek kzvetlen tlltsa helyett hasznljunk mdostkat (manipulator) az I/O vezrlshez. 21.3.3, 21.4, 21.4.6. [9] Kivteleket csak a ritkn elfordul I/O hibk kezelsre hasznljunk. 21.3.6. [10] A lekttt adatfolyamok az interaktv (felhasznli kzremkdst vr) ki- s bemenethez nyjtanak segtsget. 21.3.7. [11] Ha tbb fggvny be- s kilpsi mveleteit egy helyen akarjuk megfogalmazni, hasznljunk rszemeket. 21.3.8.

Forrs: http://www.doksi.hu

21. Adatfolyamok

881

[12] Paramter nlkli mdost utn ne tegynk zrjeleket. 21.4.6.2. [13] Ha szabvnyos mdostkat hasznlunk, ne felejtsk ki programunkbl az #include <iomanip> sort. 21.4.6.2. [14] Egy egyszer fggvnyobjektum ltrehozsval akr egy hromparamter opertor hatst (s hatkonysgt) is megvalsthatjuk. 21.4.6.3. [15] A width meghatrozsok csak a kvetkez I/O mveletre vonatkoznak. 21.4.4. [16] A precision belltsai minden ksbbi lebegpontos kimeneti mveletre hatssal vannak. 21.4.3. [17] A memriban val formzshoz hasznljunk karakterlnc-folyamokat. 21.5.3. [18] A fjlfolyamok kezelsi mdjt meghatrozhatjuk. 21.5.1. [19] Az I/O rendszer bvtsekor klntsk el a formzst (iostream) s az tmeneti trolst (streambuf). 21.1, 21.6. [20] Az rtkek nem szabvnyos tovbbtst tmeneti trral oldjuk meg. 21.6.4. [21] Az rtkek nem szabvnyos formzst adatfolyam-mveletekkel oldjuk meg. 21.2.3, 21.3.5. [22] A felhasznli kdrszleteket elklnthetjk s nll egysgknt kezelhetjk, ha fggvnyprokat hasznlunk. 21.6.4. [23] Az in_avail() fggvny segtsgvel megllapthatjuk, hogy a kvetkez bemeneti mveletnek vrakoznia kell-e majd a bemenetre. 21.6.4. [24] Vlasszuk szt a biztonsgi krdsekkel foglalkoz eljrsokat azon egyszer mveletektl, melyek legfontosabb tulajdonsga a hatkonysg. (Az elbbieket a virtual, az utbbiakat az inline kulcsszval adjuk meg.) 21.6.4. [25] Hasznljuk a locale osztlyt a kulturlis klnbsgek megfogalmazsra. 21.7. [26] A sync_with_stdio(x) fggvnnyel a C stlus ki- s bemenetet sszeegyeztethetjk a C++ I/O rendszervel, de teljesen szt is vlaszthatjuk azokat. 21.8. [27] A C stlus I/O hasznlatakor mindig nagyon figyeljnk a tpushibk elkerlsre. 21.8.

21.10. Feladatok
1. (*1.5) Olvassunk be egy lebegpontos szmokat tartalmaz fjlt, a beolvasott szmprokbl ksztsnk komplex rtkeket, majd ezeket rjuk ki. 2. (*1.5) Hatrozzuk meg a Name_and_address (Nv s cm) tpust. Adjuk meg hozz a << s a >> opertort. Msoljunk le egy Name_and_address objektumokat tartalmaz adatfolyamot. 3. (*2.5) Prbljunk meg lemsolni olyan Name_and_address objektumokbl ll adatfolyamot, melyben a lehet legtbb hiba szerepel (pldul formzsi hibk,

Forrs: http://www.doksi.hu

882

A standard knyvtr

karakterlncok tl korai vgzdse stb.). Prbljuk meg gy kezelni ezeket a hibkat, hogy a msol fggvny a helyesen formzott Name_and_address objektumok tbbsgt be tudja olvasni mg akkor is, ha a bemenet korbban teljesen sszekeveredett. 4. (*2.5) rjuk jra a Name_and_address tpus kimeneti formjt gy, hogy az kevsb legyen rzkeny a formzsi hibkra. 5. (*2.5) Ksztsnk nhny fggvnyt klnbz tpus informcik bekrshez s beolvasshoz (pldul egszekhez, lebegpontos szmokhoz, fjlnevekhez, e-mail cmekhez, dtumokhoz, szemlyi adatokhoz stb.). Prbljunk minl zembiztosabb fggvnyeket kszteni. 6. (*1.5) rjunk programot, amely kirja (a) az sszes kisbett, (b) az sszes bett, (c) az sszes bett s szmjegyet, (d) minden karaktert, amely rendszernkben C++ azonostban szerepelhet, (e) az sszes rsjelet, (f) a vezrlkarakterek kdjait, (g) az sszes reshely karaktert, (h) az reshely karakterek kdjait, s vgl (i) minden lthat karaktert. 7. (*2) Olvassunk be szvegsorokat egy rgztett mret karakteres tmeneti trba. Trljnk minden reshely karaktert s az alfanumerikus karaktereket helyettestsk az bc kvetkez karaktervel (a z bett a-ra, a 9-et 0-ra cserljk). rjuk ki az gy keletkez sorokat. 8. (*3) Ksztsnk egy miniatr adatfolyam I/O rendszert, melyben definiljuk az istream, az ostream, az ifstream, s az ofstream osztlyt, melyek biztostjk az operator<<( ) s az operator>>( ) fggvnyt egsz rtkekhez, illetve az olyan eljrsokat, mint az open() s a close() a fjlok kezelshez. 9. (*4) rjuk meg a C szabvnyos ki- s bemeneti knyvtrt (<stdio.h>) a C++ szabvnyos I/O knyvtrnak (<iostream>) felhasznlsval. 10. (*4) rjuk meg a C++ szabvnyos I/O knyvtrt (<iostream>) a C szabvnyos I/O knyvtrnak (<stdio.h>) felhasznlsval. 11. (*4) rjuk meg a C s a C++ knyvtrat gy, hogy azokat egyszerre hasznlhassuk. 12. (*2) Ksztsnk egy osztlyt, amelyben a [ ] opertor segtsgvel kzvetlenl olvashatjuk be egy fjl karaktereit. 13. (*3) Ismteljk meg a 21.10[12] feladatot gy, hogy a [ ] opertort rsra s olvassra is hasznlhassuk. tlet: a [ ] adjon vissza egy olyan objektumot, amely egy fjlpozcit azonost. Az ezen objektumra vonatkoz rtkads rja a fjlba a megfelel adatokat, mg az objektumnak char tpusra val automatikus konverzija jelentse a megfelel karakter beolvasst az llomnybl. 14. (*2) Ismteljk meg a 21.10[13] feladatot gy, hogy a [ ] opertor tetszleges tpus objektum beolvasshoz hasznlhat legyen, ne csak karakterekhez. 15. (*3.5) rjuk meg az istream s az ostream olyan vltozatt, amely a szmokat binris formban rja ki, s olvassa be ahelyett, hogy szveges alakra alaktan azokat. Vizsgljuk meg ezen megkzelts elnyeit s htrnyait a karakteralap megkzeltssel szemben.

Forrs: http://www.doksi.hu

21. Adatfolyamok

883

16. (*3.5) Tervezznk s rjunk meg egy mintailleszt bemeneti mveletet. Hasznljunk printf stlus formzott karakterlncokat a minta meghatrozshoz. Azt is tegyk lehetv, hogy tbbfle mintt rprblhassunk a bemenetre a formtum megtallshoz. A mintailleszt bemeneti osztlyt szrmaztassuk az istream osztlybl. 17. (*4) Talljunk ki (s rjunk meg) egy sokkal jobb mintaillesztsi eljrst. Hatrozzuk meg pontosan, miben jobb a mi megoldsunk. 18. (*2) Hatrozzunk meg egy kimeneti mdostt (neve legyen based), amely kt paramtert kap: egy alapszmot s egy int rtket, s az egsznek az alapszm szerinti szmrendszerbeli alakjt rja a kimenetre. A based(2,9) hatsra pldul az 1001 jelenjen meg a kimeneten. 19. (*2) Ksztsnk mdostkat, melyek ki- s bekapcsoljk a karakterek visszarst (echoing). 20. (*2) rjuk meg a 21.4.6.3 rsz Bound_form osztlyt a szoksos beptett tpusokra. 21. (*2) rjuk meg a 21.4.6.3 rsz Bound_form osztlyt gy, hogy egy kimeneti mvelet soha ne csordulhasson tl a szmra megadott width() rtken. Azt is biztostanunk kell a programoz szmra, hogy a kimenet soha ne csonkuljon a megadott pontossgi rtk miatt. 22. (*3) Ksztsnk egy encrypt(k) mdostt, melynek hatsra az ostream objektumon minden kimenet a k rtkkel titkostva jelenik meg. rjuk meg a mdost decrypt(k) prjt is az istream osztlyra. Adjunk lehetsget a titkosts kikapcsolsra is, teht tudjunk jra olvashat szveget rni a kimenetre. 23. (*2) Kvessk vgig egy karakter tjt rendszernkben a billentyzettl a kpernyig az albbi egyszer utastssorozat esetben:
char c; cin >> c; cout << c << endl;

24. (*2) Mdostsuk a 21.3.6 pont readints() fggvnyt gy, hogy minden kivtelt kezelni tudjon. tlet: kezdeti rtkads az erforrs megszerzsvel. 25. (*2.5) Minden rendszerben lehetsg van arra, hogy dtumokat rjunk, olvassunk s brzoljunk egy locale objektum lersa szerint. Talljuk meg sajt rendszernk dokumentcijban, hogyan valsthatjuk ezt meg, s ksztsnk egy rvid programot, amely e mdszer felhasznlsval r s olvas karaktereket. tlet: struct tm. 26. (*2.5) Hozzuk ltre az ostrstream osztlyt az ostream osztlybl val szrmaztatssal gy, hogy egy karaktertmbhz (C stlus karakterlnchoz) kthessk hozz, ugyangy, ahogy az ostringstream kthet egy string objektumhoz. Ne msoljuk be a tmbt az ostrstream objektumba, sem ki onnan. Az ostrstream osztlynak

Forrs: http://www.doksi.hu

884

A standard knyvtr

egyszer lehetsget kell adnia a tmbbe val rsra. Az osztlyt olyan memriban vgzett formzsokra szeretnnk felhasznlni, mint az albbi:
char buf[message_size]; ostrstream ost(buf,message_size); do_something(arguments,ost); cout << buf;

// kirs buf-ra ost-on keresztl // ost hozzadja a lezr 0-t

A do_something()-hoz hasonl mveletek az ost adatfolyamra rnak, esetleg tovbbadjk azt sajt rsz-mveleteiknek, s a szabvnyos kimeneti fggvnyeket hasznljk. A tlcsorduls ellenrzsre nincs szksg, mert az ost ismeri a sajt mrett s fail() llapotba kerl, amikor megtelik. Vgl a display() mvelet meghvsval az zenetet egy valdi kimeneti adatfolyamba rhatjuk. Ez a mdszer nagyon hasznos lehet akkor, ha a vgs kimeneti eszkz bonyolultabban tudja megvalstani a kimenetet, mint a szoksos sorkirson alapul kimeneti eszkzk. Az ost objektumban trolt szveget pldul megjelenthetjk a kperny egy rgztett mret terletn. Ugyangy hozzuk ltre az istrstream osztlyt, amely egy bemeneti karakterlnc-folyam, ami nullkarakterrel lezrt karakterlncbl olvas. A lezr nullkaraktert hasznljuk fjlvge jelknt. Ezek az strstream osztlyok az eredeti adatfolyam-knyvtr rszt kpeztk s gyakran szerepelnek a <strstream.h> fejllomnyban. 27. (*2.5) Ksztsk el a general() mdostt, amely visszalltja az adatfolyamot az eredeti formtumra, hasonlan ahhoz, ahogy a scientific() (21.4.6.2) az adatfolyamot tudomnyos formtumra lltja.

Forrs: http://www.doksi.hu

22
Szmok
A szmts clja nem a szm, hanem a tisztnlts. (R.W. Hamming) de a tanulnak gyakran szmokon keresztl vezet az t a tisztnltshoz. (A. Ralston)

Bevezets A szmtpusok korltai Matematikai fggvnyek valarray Vektormveletek Szeletek slice_array Az ideiglenes vltozk kikszblse gslice_array mask_array indirect_array complex ltalnostott algoritmusok Vletlen szmok Tancsok Gyakorlatok

Forrs: http://www.doksi.hu

886

A standard knyvtr

22.1. Bevezets
A gyakorlatban ritkn akadunk olyan programozsi feladatra, ahol nincs szksg valamilyen szmokkal vgzett mveletre. Programjainkban ltalban az alapvet aritmetikai mveleteken kvl szksgnk van egy kis igazi matematikra is. Ez a fejezet a standard knyvtr ehhez kapcsold szolgltatsait mutatja be. Sem a C, sem a C++ nyelvet nem elsdlegesen szmmveletek elvgzsre terveztk. Azok viszont jellemzen ms krnyezetbe begyazva fordulnak el megemlthet itt az adatbzis- illetve hlzatkezels, a mszerek vezrlse, a grafika, a pnzgyi szmtsok, valamint a szimulci klnbz terletei , gy a C++ remek lehetsgeket nyjthat a nmileg bonyolultabb matematikai feladatok elvgzsre is, amelyekkel az elbb emltett terletek megfelelen kiszolglhatk. A szmmveletek sklja igen szles, az egyszer ciklusoktl a lebegpontos szmokbl alkotott vektorokig terjed. A C++ ereje az ilyen sszetettebb adatszerkezeteken vgzett mveleteknl mutatkozik meg igazn, ezrt egyre gyakrabban alkalmazzk mrnki s tudomnyos szmtsok elvgzsre. A fejezetben a standard knyvtr azon szolgltatsaival s eljrsaival ismerkedhetnk meg, melyek kifejezetten a szmmveletek elvgzst tmogatjk a C++ krnyezetben. A matematikai alapok tisztzsra ksrletet sem tesznk, ezeket ismertnek ttelezzk fel. Az esetleges hinyossgok matematikai (s nem szmtstechnikval foglalkoz) szakknyvekbl ptolhatk.

22.2. A szmtpusok korltozsai


Ha egy programban nem csupn alapszinten vgznk szmmveleteket, mindenkppen szksg van arra, hogy ismerjk a beptett tpusok alapvet tulajdonsgait. Ezek sokkal inkbb az adott fejlesztkrnyezettl fggnek, mintsem a nyelv szablyaitl (4.6). Pldul melyik a legnagyobb brzolhat int? Mekkora a legkisebb float? Mi trtnik egy double tpus szmmal, ha float tpusv alaktjuk? Kerekti vagy csonktja a rendszer? Hny bitet tartalmaz a char tpus? Az ilyen, s ehhez hasonl krdsekre a <limits> fejllomnyban lert numeric_limits sablon (template) specializcii szolglhatnak vlasszal. Lssunk egy pldt:

Forrs: http://www.doksi.hu

22. Szmok

887

void f(double d, int i) { if (numeric_limits<unsigned char>::digits != 8) { // szokatlan bjt (a bitek szma nem 8) } if (i<numeric_limits<short>::min() || numeric_limits<short>::max()<i) { // i nem trolhat short tpusban rtkveszts nlkl } if (0<d && d<numeric_limits<double>::epsilon()) d = 0; if (numeric_limits<Quad>::is_specialized) { // a Quad tpushoz rendelkezsre llnak korlt-adatok }

Minden specializci biztostja a sajt paramtertpusnak legfontosabb informcikat. Kvetkezskppen az ltalnos numeric_limits sablon egyszeren arra szolgl, hogy szabvnyos nevet adjon nhny konstansnak s helyben kifejtett (inline) fggvnynek:
template<class T> class numeric_limits { public: static const bool is_specialized = false; // rdektelen alaprtelmezsek

// ll rendelkezsre informci // a numeric_limits<T>-rl?

};

A tnyleges informci az egyedi cl vltozatokban (specializcikban) szerepel. A standard knyvtr minden vltozata az sszes alaptpushoz (karakterekhez, egszekhez, vals szmokhoz, valamint a logikai tpushoz) szolgltat egy-egy specializcit a numeric_limitsbl. Nem szerepel viszont ilyen a tbbi tpushoz: a void mutathoz, a felsorol tpusokhoz, vagy a knyvtri tpusokhoz (pldul a complex<double> szerkezethez). A beptett tpusok (pldul a char) esetben csupn nhny adat emltsre mlt. Lssunk egy numeric_limits<char>-t olyan megvalstsban, ahol a char 8 bites s eljeles:
class numeric_limits<char> { public: static const bool is_specialized = true; static const int digits = 7;

// igen, van adat

// bitek szma ("binris szmjegyek") eljel nlkl

Forrs: http://www.doksi.hu

888

A standard knyvtr

static const bool is_signed = true; static const bool is_integer = true; static char min() throw() { return -128; } static char max() throw() { return 127; } };

// ebben a megvalstsban a char tpus // eljeles (signed) // a char egsz jelleg tpus // legkisebb rtk // legnagyobb rtk

// deklarcik, amelyek kzmbsek a char tpus szmra

Jegyezzk meg, hogy egy eljeles egsz tpus tnylegesen szmbrzolsra hasznlt bitjeinek szma (digits) eggyel kevesebb a tpushoz rendelt bitszmnl, hiszen egy bitet az eljel foglal le. A numeric_limits tagjainak tbbsge a lebegpontos szmok lersra szolgl. A kvetkez plda egy lehetsges float-vltozatot mutat:
class numeric_limits<float> { public: static const bool is_specialized = true; static const int radix = 2; static const int digits = 24; static const int digits10 = 6; static const bool is_signed = true; static const bool is_integer = false; static const bool is_exact = false; static float min() throw() { return 1.17549435E-38F; } static float max() throw() { return 3.40282347E+38F; } static float epsilon() throw() { return 1.19209290E-07F; } static float round_error() throw() { return 0.5F; } static float infinity() throw() { return /* valamilyen rtk */; } static float quiet_NaN() throw() { return /* valamilyen rtk */; } static float signaling_NaN() throw() { return /* valamilyen rtk */; } static float denorm_min() throw() { return min(); } static const int min_exponent = -125; static const int min_exponent10 = -37; static const int max_exponent = +128; static const int max_exponent10 = +38; // a kitev tpusa (ebben az esetben 2-es alap) // radix szmjegyek a mantisszban // 10-es alap szmjegyek a mantisszban

Forrs: http://www.doksi.hu

22. Szmok

889

static const bool has_infinity = true; static const bool has_quiet_NaN = true; static const bool has_signaling_NaN = true; static const float_denorm_style has_denorm = denorm_absent; static const bool has_denorm_loss = false; static const bool is_iec559 = true; // megfelel IEC-559-nek static const bool is_bounded = true; static const bool is_modulo = false; static const bool traps = true; static const bool tinyness_before = true; }; static const float_round_style round_style = round_to_nearest;

// enum a <limits>-bl

// enum a <limits>-bl

Ne feledjk, hogy a min() a legkisebb pozitv normlt szm, az epsilon pedig a legkisebb pozitv lebegpontos szm, amelyre az 1+epsilon-1 brzolhat. Amikor egy skalr tpust egy beptett tpus segtsgvel definilunk, rdemes egyttal a numeric_limits megfelel specializcijt is megadnunk. Ha pldul egy ngyszeres pontossg Quad, vagy egy klnlegesen pontos egsz, long long tpust ksztnk, a felhasznlk jogos elvrsa, hogy ltezzenek a numeric_limits<Quad> s a numeric_limits<long long> specializcik. A numeric_limits-nek elkpzelhet olyan vltozata, mely egy olyan felhasznli tpus tulajdonsgait rja le, melynek nem sok kze van lebegpontos szmokhoz. Az ilyen esetekben rendszerint ajnlatosabb a tpustulajdonsgok lersra hasznlt ltalnos eljrs alkalmazsa, mint egy j numeric_limits meghatrozsa olyan tulajdonsgokkal, melyek a szabvnyban nem szerepelnek. A lebegpontos szmokat helyben kifejtett (inline) fggvnyek brzoljk. A numeric_limits osztlyban szerepl egsz rtkeket viszont olyan formban kell brzolnunk, amely megengedi, hogy konstans kifejezsekben felhasznljuk azokat, ezrt ezek az elemek osztlyon belli kezdeti rtkadssal rendelkeznek (10.4.6.2). Ha ilyen clokra static const tagokat hasznlunk felsorol tpusok helyett, ne felejtsk el definilni a static elemeket.

Forrs: http://www.doksi.hu

890

A standard knyvtr

22.2.1. Korltozsi makrk


A C++ rklte a C-tl azokat a makrkat, melyek lerjk az egsz tpusok tulajdonsgait. Ezek a <climits>, illetve a <limits.h> fejllomnyban szerepelnek. Ilyen makr pldul a CHAR_BIT vagy az INT_MAX. Ugyangy a <cfloat> s a <float.h> fejllomny azokat a makrkat tartalmazza, amelyek a lebegpontos szmok tulajdonsgait rjk le. Ezekre plda a DBL_MIN_EXP, a FLT_RADIX vagy a LDBL_MAX. Mint mindig, a makrkat most is rdemes elkerlnnk.

22.3. Szabvnyos matematikai fggvnyek


A <cmath> s a <math.h> fejllomny szolgltatja azokat a fggvnyeket, amelyeket ltalban szoksos matematikai fggvnyeknek neveznk:
double abs(double); double fabs(double); double ceil(double d); double floor(double d); double sqrt(double d); double pow(double d, double e); double pow(double d, int i); double cos(double); double sin(double); double tan(double); double acos(double); double asin(double); double atan(double); double atan2(double x, double y); double sinh(double); double cosh(double); double tanh(double); // abszoltrtk, ugyanaz mint fabs(); ilyen nincs a C-ben // abszoltrtk // a d-nl nem kisebb legkisebb egsz // a d-nl nem nagyobb legnagyobb egsz // d ngyzetgyke, d nem negatv kell legyen // d e-edik hatvnya, // hiba, ha d==0 s e<=0, vagy ha d<0 s e nem egsz // d i-edik hatvnya; ilyen nincs a C-ben // koszinusz // szinusz // tangens // arkusz koszinusz // arkusz szinusz // arkusz tangens // atan(x/y) // szinusz hiperbolikusz // koszinusz hiperbolikusz // tangens hiperbolikusz

Forrs: http://www.doksi.hu

22. Szmok

891

double exp(double); double log(double d); double log10(double d);

// e alap exponencilis // termszetes (e alap) logaritmus, // d nagyobb kell legyen 0-nl // 10-es alap logaritmus, d nagyobb kell legyen 0-nl // d trt rszvel tr vissza, // az egsz rszt *p-be helyezi // x eleme [.5,1) s y gy, hogy d = x*pow(2,y) legyen; // visszatr rtk x, y *p-be helyezse // lebegpontos maradk, eljele megfelel d eljelnek // d*pow(2,i)

double modf(double d, double* p); double frexp(double d, int* p); double fmod(double d, double m); double ldexp(double d, int i);

A <cmath> s a <math.h> fejllomny ugyanezeket a fggvnyeket float s long double paramterekkel is elrhetv teszi. Ha egy mveletnek tbb lehetsges eredmnye is van (mint pldul az asin() esetben), a fggvnyek a nullhoz legkzelebbi rtket adjk vissza. Az acos() eredmnye mindig nemnegatv. Ezek a fggvnyek a hibkat az <errno> fejllomnyban szerepl errno vltoz belltsval jelzik. Ennek rtke EDOM, ha egy fggvny nem rtelmezhet a megadott paramterre, s ERANGE ha az eredmny nem brzolhat az adott tpussal:
void f() { errno = 0; // trli az elz hibakdot sqrt(-1); if (errno==EDOM) cerr << "A sqrt() nem definilt negatv paramter esetn"; pow(numeric_limits<double>::max(),2); if (errno == ERANGE) cerr << "A pow() eredmnye tl nagy ahhoz," << "hogy double-knt brzoljuk"; }

A C++ elzmnyeire visszavezethet okokbl nhny matematikai fggvny a <cstdlib> fejllomnyban szerepel a <cmath> helyett:
int abs(int); long abs(long); long labs(long); // abszoltrtk // abszoltrtk ; ilyen nincs a C-ben // abszoltrtk

struct div_t { megvalsts_fgg quot, rem; }; struct ldiv_t { megvalsts_fgg quot, rem; };

Forrs: http://www.doksi.hu

892

A standard knyvtr

div_t div(int n, int d); ldiv_t div(long int n, long int d); ldiv_t ldiv(long int n, long int d);

// n osztsa d-vel, visszatrs (kvciens,maradk) // n osztsa d-vel, visszatrs (kvciens,maradk); // ilyen nincs a C-ben // n osztsa d-vel, visszatrs (kvciens,maradk)

22.4. Vektormveletek
A legtbb szmtsi feladat viszonylag egyszer, egydimenzis vektorokra vonatkozik, amelyek lebegpontos szmokat trolnak. Ezrt a nagyteljestmny szmtgpek kln tmogatjk az ilyen vektorok kezelst, a velk foglalkoz knyvtrak szles krben hasznlatosak, s nagyon sok terleten ltfontossg az ilyen vektorokat kezel fggvnyek lehet legoptimlisabb kialaktsa. A standard knyvtr tartalmaz egy olyan vektort (valarray), amelyet kifejezetten a szoksos matematikai vektormveletek gyors vgrehajtsra dolgoztak ki. Mikzben ttekintjk a valarray lehetsgeit, mindig gondoljunk arra, hogy ez egy viszonylag alacsony szint programelem, amely nagy hatkonysg szmtsok elvgzshez kszlt. Teht az osztly tervezsekor a legfontosabb clkitzs nem a knyelmes hasznlat volt, hanem a nagyteljestmny szmtgpek lehetsgeinek minl jobb kihasznlsa, a legersebb optimalizlsi mdszerek alkalmazsa. Ha sajt programunkban a rugalmassg s az ltalnossg fontosabb, mint a hatkonysg, valsznleg rdemesebb a szabvnyos trolk kzl vlasztanunk (melyeket a 16. s a 17. fejezet mutat be), ne is prbljunk a valarray egyszer, hatkony s szndkosan hagyomnyos kereteihez igazodni. Felmerlhet bennnk a krds, hogy mirt nem a valarray neve lett vector, hiszen ez a hagyomnyos, matematikai vektor valdi megfelelje, s a 16.3 pont vector osztlyt kne inkbb array tpusnak nevezni. A terminolgia mgsem ezt az elnevezsi rendet kveti. A valarray numerikus szmtsokra optimalizlt vektor, mg a vector egy rugalmas trol, amely a legklnbzbb tpus objektumok trolsra s kezelsre szolgl. Az array (tmb) fogalma ersen ktdik a beptett tmbtpushoz. A valarray tpust ngy kisegt tpus egszti ki, melyek a valarray egy-egy rszhalmazt kpezik: A slice_array s a gslice_array a szeletek (slice) fogalmt brzoljk (22.4.6, 22.4.8). A mask_array egy rszhalmazt hatroz meg, gy, hogy minden elemrl megmondja, hogy az benne van-e a rszhalmazban vagy sem (22.4.9).

Forrs: http://www.doksi.hu

22. Szmok

893

Az indirect_array a rszhalmazba tartoz elemek indexrtkeinek (sorszmainak) listjt tartalmazza (22.4.10).

22.4.1. Valarray ltrehozsa


A valarray tpus s a hozz tartoz szolgltatsok definicija az std nvtrben szerepel s a <valarray> fejllomny segtsgvel rhet el:
template<class T> class std::valarray { // brzols public: typedef T value_type; valarray(); explicit valarray(size_t n); valarray(const T& val, size_t n); valarray(const T* p, size_t n); valarray(const valarray& v); valarray(const slice_array<T>&); valarray(const gslice_array<T>&); valarray(const mask_array<T>&); valarray(const indirect_array<T>&); ~valarray(); }; // ... // valarray size()==0 mrettel // n elem, rtkk: T() // n elem, rtkk: val // n elem, rtkk: p[0], p[1], ... // v msolata // lsd 22.4.6 // lsd 22.4.8 // lsd 22.4.9 // lsd 22.4.10

Ezek a konstruktorok lehetv teszik, hogy egy valarray objektumnak a kisegt numerikus tmbtpusok vagy nll rtkek segtsgvel adjunk kezdrtket:
valarray<double> v0; valarray<float> v1(1000); valarray<int> v2(-1,2000); valarray<double> v3(100,9.8064); valarray<double> v4 = v3; // helyfoglals, v0-nak ksbb adunk rtket // 1000 elem, mindegyik rtke float()==0.0F // 2000 elem, mindegyik rtke -1 // hiba: lebegpontos valarray mret // v4 elemszma v3.size()

A ktparamter konstruktorokban az rtket az elemszm eltt kell megadnunk. Ez eltr a szabvnyos trolkban hasznlt megoldstl (16.3.4).

Forrs: http://www.doksi.hu

894

A standard knyvtr

A msol konstruktornak tadott valarray paramter elemeinek szma hatrozza meg a ltrejv valarray mrett. A legtbb program tblkbl vagy valamilyen bemeneti mvelet segtsgvel jut hozz az adatokhoz. Ezt tmogatja az a konstruktor, amely egy beptett tmbbl msolja ki az elemeket:
const double vd[ ] = { 0, 1, 2, 3, 4 }; const int vi[ ] = { 0, 1, 2, 3, 4 }; valarray<double> v3(vd,4); // 4 elem: 0,1,2,3 valarray<double> v4(vi,4); // tpushiba: vi nem double-ra mutat valarray<double> v5(vd,8); // nem meghatrozott: tl kevs elem a kezdrtk-adban

Ez a kezdrtk-ad forma nagyon fontos, mert a legtbb numerikus program nagy tmbk formjban adja meg az adatokat. A valarray tpust s kisegt szolgltatsait nagy sebessg szmtsokhoz terveztk. Ez nhny, a felhasznlkra vonatkoz korltozsban, s nhny, a megvalstkra vonatkoz engedmnyben nyilvnul meg. A valarray ksztje szinte brmilyen optimalizlsi mdszert hasznlhat, amit csak el tud kpzelni. A mveletek pldul lehetnek helyben kifejtettek, a valarray mveleteit pedig mellkhatsok nlklinek tekinthetjk (persze sajt paramtereikre ez nem vonatkozik). Egy valarray objektumrl felttelezhetjk, hogy nem rendelkezik lnvvel (alias), kisegt tpusokat brmikor bevezethetnk, s az ideiglenes vltozkat is szabadon kikszblhetjk, ha az alapvet jelentst gy is meg tudjuk tartani. Ezrt a <valarray> fejllomnyban szerepl deklarcik jelentsen el is trhetnek az itt bemutatott (s a szabvnyban szerepl) formtl, de azon programok szmra, melyek nem srtik meg a szablyokat, mindenkppen ugyanazokat a mveleteket kell biztostaniuk, ugyanazzal a jelentssel. A valarray elemeinek msolsa pldul a szoksos mdon kell, hogy mkdjn (17.1.4).

22.4.2. A valarray indexelse s az rtkads


A valarray osztly esetben az indexels egyarnt hasznlhat nll elemek elrshez s rsztmbk kijellshez:
template<class T> class valarray { public: // ... valarray& operator=(const valarray& v); valarray& operator=(const T& val);

// v msolsa // minden elem val-t kapja rtkl

Forrs: http://www.doksi.hu

22. Szmok

895

T operator[ ](size_t) const; T& operator[ ](size_t); valarray operator[ ](slice) const; slice_array<T> operator[ ](slice); valarray operator[ ](const gslice&) const; gslice_array<T> operator[ ](const gslice&); valarray operator[ ](const valarray<bool>&) const; mask_array<T> operator[ ](const valarray<bool>&); // lsd 22.4.6 // lsd 22.4.8 // lsd 22.4.9

valarray operator[ ](const valarray<size_t>&) const; // lsd 22.4.10 indirect_array<T> operator[ ](const valarray<size_t>&); valarray& valarray& valarray& valarray& }; // ... operator=(const slice_array<T>&); operator=(const gslice_array<T>&); operator=(const mask_array<T>&); operator=(const indirect_array<T>&); // lsd 22.4.6 // lsd 22.4.8 // lsd 22.4.9 // lsd 22.4.10

Egy valarray objektumot rtkl adhatunk egy msik, ugyanolyan mret valarray-nek. Elvrsainknak megfelelen a v1=v2 utasts a v2 minden elemt a v1 megfelel elembe msolja. Ha a tmbk klnbz mretek, az eredmny nem meghatrozott lesz, mert a sebessgre optimalizlt valarray osztlytl nem kvetelhetjk meg, hogy nem megfelel mret objektum rtkl adsakor knnyen rthet hibajelzst (pldul kivtelt) kapjunk, vagy ms, logikus viselkedst tapasztaljunk. Ezen hagyomnyos rtkads mellett lehetsg van arra is, hogy egy valarray objektumhoz egy skalr rtket rendeljnk. A v=7 utasts pldul a v valarray minden egyes elembe a 7 rtket rja. Ez els rnzsre elg meglep, s szerept gy rthetjk meg leginkbb, ha gy gondolunk r, mint az rtkad mveleteknek egy, nhny esetben hasznos, elfajzott vltozatra (22.4.3). Az egszekkel val sorszmozs a szoksos mdon mkdik, tartomnyellenrzs nlkl. Az nll elemek kivlasztsa mellett a valarray indexelse lehetsget ad rsztmbk ngyfle kijellsre is (22.4.6). Megfordtva, az rtkad utastsok (s a konstruktorok, 22.4.1) ilyen rsztmbket is elfogadnak paramterknt. A valarray tpusra megvalstott rtkad utastsok biztostjk, hogy a kisegt tmb tpusokat (mint a slice_array) ne kelljen talaktanunk valarray tpusra, mieltt rtkl adjuk azokat. A hatkonysg biztostsa

Forrs: http://www.doksi.hu

896

A standard knyvtr

rdekben egy alapos fejlesztkrnyezetnek illik a vektor ms mveleteihez (pldul a + s a *) is biztostani az ilyen vltozatokat. Ezenkvl a vektormveletek szmtalan mdon optimalizlhatk, szeletek (slice) vagy ms kisegt vektortpusok hasznlatval.

22.4.3. Mveletek tagfggvnyknt


A nyilvnval s a kevsb nyilvnval tagfggvnyek listja kvetkez:
template<class T> class valarray { public: // ... valarray& operator*=(const T& arg); // hasonlan: /=, %=, +=, -=, ^=, &=, |=, <<=, s >>= T sum() const; T min() const; T max() const; valarray shift(int i) const; valarray cshift(int i) const; // v[i]*=arg minden elemre

// elemek sszege, += hasznlatval // legkisebb rtk, < hasznlatval // legnagyobb rtk, < hasznlatval // logikai lptets (balra, ha 0<i; jobbra, ha i<0) // ciklikus lptets (balra, ha 0<i; jobbra, ha i<0)

valarray apply(T f(T)) const; // eredmny[i] = f(v[i]) minden elemre valarray apply(T f(const T&)) const; valarray operator-() const; // hasonlan: +, ~, ! // eredmny[i] = -v[i] minden elemre // elemek szma // n elem, rtkk val

};

size_t size() const; void resize(size_t n, const T& val = T());

Ha size()=0, akkor sum(), min() s max() rtke nem meghatrozott. Pldul, ha v egy valarray, akkor a v*=.2 vagy a v/=1.3 utastsok alkalmazhatak r. Ha skalrmveleteket hajtunk vgre egy vektoron, akkor az azt jelenti, hogy a vektor minden elemre elvgezzk azt. Szoks szerint, egyszerbb a *= mveletet optimalizlni, mint a * s az = prostst (11.3.1).

Forrs: http://www.doksi.hu

22. Szmok

897

Figyeljk meg, hogy a nem rtkad mveletek mindig j valarray objektumot hoznak ltre:
double incr(double d) { return d+1; } void f(valarray<double>& v) { valarray<double> v2 = v.apply(incr); // j, megnvelt valarray-t hoz ltre, megnvelt rtkkel }

Ezen programrszlet hatsra a v rtke nem vltozik. Sajnos az apply() nem kpes fggvnyobjektumokat (18.4) hasznlni paramterknt (22.9.[1]). A logikai s a ciklikus lptet fggvnyek (a shift() s a cshift()) olyan j valarray objektumokat adnak vissza, amelyben az elemek megfelelen el vannak lptetve. Az eredeti vektor itt is vltozatlan marad. A v2=v.cshift(n) ciklikus lptets pldul egy olyan valarray objektumot eredmnyez, melynek elemeire v2[i]==v[(i+n)%v.size()].. A v3=v.shift(n) logikai lptet mvelet hatsra a v3[i] a v[i+n] rtket veszi fel, ha az i+n ltez indexe a v vektornak. Ellenkez esetben az adott elembe a vektor alaprtelmezett rtke kerl. Ebbl kvetkezik, hogy a shift() s a cshift() is balra tolja az elemeket, ha pozitv paramtert adunk meg, s jobbra, ha negatv rtket:
void f() { int alpha[ ] = { 1, 2, 3, 4, 5 ,6, 7, 8 }; valarray<int> v(alpha,8); valarray<int> v2 = v.shift(2); valarray<int> v3 = v<<2; valarray<int> v4 = v.shift(-2); valarray<int> v5 = v>>2; valarray<int> v6 = v.cshift(2); valarray<int> v7 = v.cshift(-2); }

// 1, 2, 3, 4, 5, 6, 7, 8 // 3, 4, 5, 6, 7, 8, 0, 0 // 4, 8, 12, 16, 20, 24, 28, 32 // 0, 0, 1, 2, 3, 4, 5, 6 // 0, 0, 0, 1, 1, 1, 1, 2 // 3, 4, 5, 6, 7, 8, 1, 2 // 7, 8, 1, 2, 3, 4, 5, 6

A valarray tpus esetben a << s a >> opertor bitenknti lptetst vgez, teht nem elemeket tolnak el s nem is I/O mveletek (22.4). Ennek megfelelen a <<= s a >>= mveletekkel elemeken belli eltolst vgezhetnk egsz tpus elemek esetben:
void f(valarray<int> vi, valarray<double> vd) { vi <<= 2; // vi[i]<<=2, vi minden elemre vd <<= 2; // hiba: a lptets nem meghatrozott lebegpontos rtkekre }

Forrs: http://www.doksi.hu

898

A standard knyvtr

A valarray mrett mdosthatjuk is. A resize() itt nem arra szolgl, hogy a valarray osztlyt egy dinamikusan nvekedni kpes adatszerkezett tegye mint ahogy a vector s a string esetben trtnik , hanem j kezdrtket ad mvelet, amely a ltez elemeket is lecserli a valarray alaprtelmezett rtkre, gy a rgi elemeket vglegesen elvesztjk. Az tmretezsre sznt valarray objektumokat gyakran res vektorknt hozzuk ltre. Gondoljuk vgig pldul, hogyan adhatunk kezdrtket egy valarray objektumnak bemenet alapjn:
void f() { int n = 0; cin >> n; if (n<=0) error("hibs tmbmret"); valarray<double> v(n); int i = 0; while (i<n && cin>>v[i++]) ; if (i!=n) error("tl kevs bemeneti elem"); } // ...

// a tmb mretnek beolvassa // tmb ltrehozsa a szksges mrettel // a tmb feltltse

Ha a bemenetet kln fggvnyben szeretnnk kezelni, a kvetkezt tehetjk:


void initialize_from_input(valarray<double>& v) { int n = 0; cin >> n; if (n<=0) error("hibs tmbmret"); v.resize(n); int i = 0; while (i<n && cin>>v[i++]) ; if (i!=n) error("tl kevs bemeneti elem"); // a tmb mretnek beolvassa // v tmretezse a szksges mretre // a tmb feltltse

void g() { valarray<double> v; initialize_from_input(v); // ... }

// alaprtelmezett tmb ltrehozsa // v mretezse s feltltse

Ezzel a megoldssal elkerlhetjk nagy mret adatterletek msolst.

Forrs: http://www.doksi.hu

22. Szmok

899

Ha azt szeretnnk, hogy egy valarray megrizze az rtkes adatokat, mikzben dinamikusan nvekszik, ideiglenes vltozt kell hasznlnunk:
void grow(valarray<int>& v, size_t n) { if (n<=v.size()) return; valarray<int> tmp(n); // n alaprtelmezett elem

copy(&v[0],&v[v.size()],&tmp[0]); // msol algoritmus 18.6.1-bl v.resize(n); copy(&tmp[0],&tmp[v.size()],&v[0]);

A valarray tpust nem az ilyen felhasznlsi terletekre terveztk. Egy valarray objektumnak nem illik megvltoztatnia mrett, miutn a kezdeti helyfoglals megtrtnt. A valarray elemei egyetlen sorozatot alkotnak, teht a v[0],,v[n-1] elemek egyms utn tallhatk a memriban. Ebbl kvetkezik, hogy a T* egy kzvetlen elrs (randomaccess) bejr (itertor, 19.2.1) a valarray<T> vektorhoz, gy a szabvnyos algoritmusok, pldul a copy(), alkalmazhatk r. Ennek ellenre jobban illik a valarray szellemisghez, ha a msolst rtkadsok s rsztmbk formjban fejezzk ki:
void grow2(valarray<int>& v, size_t n) { if (n<=v.size()) return; valarray<int> tmp = v; slice s(0,v.size(),1); v.resize(n); v[s] = tmp; // v.size() elemszm rsztmb (lsd 22.4.5) // az tmretezs nem rzi meg az elemek rtkt // elemek visszamsolsa v els rszbe

Ha valamilyen okbl a bemeneti adatok olyan elrendezsek, hogy be kell azokat olvasnunk, mieltt megtudnnk a trolsukhoz szksges vektor mrett, ltalban rdemes elszr egy vector (16.3.5) objektumba olvasni az elemeket s onnan msolni azokat egy valarray vltozba.

Forrs: http://www.doksi.hu

900

A standard knyvtr

22.4.4. Nem tagfggvnyknt megvalstott mveletek


A szoksos binris (ktoperandus) opertorok s matematikai fggvnyek gy szerepelnek a knyvtrban:
template<class T> valarray<T> operator*(const valarray<T>&, const valarray<T>&); template<class T> valarray<T> operator*(const valarray<T>&, const T&); template<class T> valarray<T> operator*(const T&, const valarray<T>&); // hasonlan: /, %, +, -, ^, &, |, <<, >>, &&, ||, ==, !=, <, >, <=, >=, atan2, s pow template<class T> valarray<T> abs(const valarray<T>&); // hasonlan: acos, asin, atan, cos, cosh, exp, log, log10, sin, sinh, sqrt, tan, s tanh

A binris mveleteket kt valarray objektumra, vagy egy valarray objektum s a megfelel tpus skalr rtk egyttesre alkalmazhatjuk:
void f(valarray<double>& v, valarray<double>& v2, double d) { valarray<double> v3 = v*v2; // v3[i] = v[i]*v2[i] minden i-re valarray<double> v4 = v*d; // v4[i] = v[i]*d minden i-re valarray<double> v5 = d*v2; // v5[i] = d*v2[i] minden i-re } valarray<double> v6 = cos(v); // v6[i] = cos(v[i]) minden i-re

Ezek a vektormveletek valarray operandusuk (operandusaik) minden elemre vgrehajtjk a megfelel mveletet, gy, ahogy a * s a cos() pldkon keresztl bemutattuk. Termszetesen minden mvelet csak akkor alkalmazhat, ha a megfelel fggvny definilt a sablonparamterknt megadott tpusra. Ellenkez esetben a fordt hibazenetet ad, amikor megprblja pldnyostani a sablont (13.5). Ha az eredmny egy valarray, akkor annak hossza megegyezik a paramter(ek)ben hasznlt valarray mretvel. Ha kt valarray paramter mrete nem egyezik meg (binris mveletnl), az eredmny nem meghatrozott lesz. rdekes mdon I/O mveletek nem llnak rendelkezsnkre a valarray tpushoz (22.4.3); a << s a >> opertor csak lptetsre szolgl. Ha mgis szksgnk van a kt opertor kis bemenetet kezel vltozatra, minden gond nlkl definilhatjuk azokat (22.9[5]).

Forrs: http://www.doksi.hu

22. Szmok

901

Jegyezzk meg, hogy ezek a valarray mveletek j valarray objektumokat adnak vissza, s nem operandusaikat mdostjk. Ez egy kicsit kltsgesebb teheti a fggvnyeket, de ha kellen hatkony optimalizcis mdszereket alkalmazunk, ez a vesztesg nem jelentkezik (lsd pldul 22.4.7). A valarray objektumokra alkalmazhat opertorok s matematikai fggvnyek ugyangy hasznlhatk slice_array (22.4.6), gslice_array (22.4.8), mask_array (22.4.9) s indirect_array (22.4.10) objektumokra is, egyes nyelvi vltozatok azonban lehet, hogy a nem valarray tpus operandusokat elszr valarray tpusra alaktjk, majd ezen vgzik el a kijellt mveletet.

22.4.5. Szeletek
A slice (szelet) olyan elvont tpus, amely lehetv teszi, hogy vektorokat akrhny dimenzis mtrixknt hatkonyan kezeljnk. Ez a tpus a Fortran vektorok alapeleme s kulcsszerepet jtszik a BLAS (Basic Linear Algebra Subprograms) knyvtrban, amely viszont a legtbb szmmvelet kiindulpontja. A szelet alapjban vve nem ms, mint egy valarray egy rszletnek minden n-ik eleme:
class std::slice { // kezdindex, hossz, s lpskz public: slice(); slice(size_t start, size_t size, size_t stride); size_t start() const; size_t size() const; size_t stride() const; // az els elem indexrtke // elemek szma // az n-ik elem helye: start()+n*stride()

};

A stride a lpskz, vagyis a tvolsg (az elemek szmban kifejezve) a slice kt, egymst kvet eleme kztt. Teht a slice egszek egy sorozatt rja le:
size_t slice_index(const slice& s, size_t i) { return s.start()+i*s.stride(); } // i lekpezse a megfelel indexrtkre

void print_seq(const slice& s) // s elemeinek kirsa { for (size_t i = 0; i<s.size(); i++) cout << slice_index(s,i) << " "; }

Forrs: http://www.doksi.hu

902

A standard knyvtr

void f() { print_seq(slice(0,3,4)); cout << ", "; print_seq(slice(1,3,4)); cout << ", "; print_seq(slice(0,4,1)); cout << ", "; print_seq(slice(4,4,1)); }

// 0. sor // 1. sor // 0. oszlop // 1. oszlop

A megjelen szveg a kvetkez lesz: 0 4 8 , 1 5 9 , 0 1 2 3 , 4 5 6 7 Teht egy slice nem tesz mst, mint hogy nemnegatv egsz rtkeket sorszmokra kpez le. Az elemek szma (size()) nincs hatssal a lekpezsre (a cmzsre), de lehetsget ad szmunkra a sorozat vgnek megtallsra. Ez a lekpezs egy egyszer, hatkony, ltalnos s viszonylag knyelmes lehetsget ad egy ktdimenzis tmb utnzsra egy egydimenzis tmbben (pldul egy valarray objektumban). Pldul lssunk egy 3-szor 4-es mtrixot abban a formban, ahogy megszoktuk (C.7): 00 01 02 10 11 12 20 21 22 30 31 32

A Fortran szoksainak megfelelen ezt a kvetkez formban helyezhetnnk el a memriban: 0 0 1 2 3 4 8

00 10 20 30 01 11 21 31 02 12 22 32

A C++ nem ezt a megoldst hasznlja (lsd C.7), ugyanakkor biztostanunk kell egy rendszert, amely tiszta s logikus kezelfelletet ad, s ehhez olyan brzolst kell vlasztanunk, amely megfelel a problma ktttsgeinek. Itt most a Fortran elrendezst vlasztottam, hogy knnyen megvalsthat legyen az egyttmkds az olyan numerikus prog-

Forrs: http://www.doksi.hu

22. Szmok

903

ramokkal, melyek ezt az elvet kvetik. Annyira azrt nem voltam hajland vltoztatni, hogy az indexelst 0 helyett 1-rl indtsam ha ilyen cljaink vannak, oldjuk meg a 22.9[9] feladatot. A legtbb szmmveletet tovbbra is szmos nyelv s mg tbb knyvtr egyttes hasznlatval kell megoldanunk. A klnbz nyelvek s knyvtrak eltr formtum adatainak kezelse gyakran ltszksglet. Egy x sort a slice(x,3,4) kifejezssel rhatunk le. Teht az x sor els eleme a vektor x -ik eleme, a sor kvetkez eleme az (x+4)-ik s gy tovbb. A fenti pldban minden sor hrom elemet tartalmaz. Az brk szerint a slice(0,3,4) a 00, a 01 s a 02 elemet jelli. Egy y oszlopot a slice(4*y,4,1) hatroz meg. Az y oszlop els eleme a vektornak 4*y-ik eleme, a kvetkez oszlopelem a (4*y+1)-ik vektorelem stb. Minden oszlopban 4 elem szerepel. A slice(0,4,1) kifejezs teht az brk szerint a 00, a 10, a 20 s a 30 elemet jelli. Azon kvl, hogy knnyen utnozhatunk ktdimenzis tmbket, a slice segtsgvel szmtalan ms sorozatot is lerhatunk; az egyszer sorozatok megadsra gy kellen ltalnos mdszert biztost. Ezzel a 22.4.8 pont foglalkozik rszletesen. Egy szeletet elkpzelhetnk gy is, mint egy klnleges bejrt (iterator): a slice lehetv teszi, hogy lerjunk egy indexsorozatot egy valarray objektumban. Ez alapjn egy valdi bejrt is felpthetnk:
template<class T> class Slice_iter { valarray<T>* v; slice s; size_t curr;

// az aktulis elem indexe

T& ref(size_t i) const { return (*v)[s.start()+i*s.stride()]; } public: Slice_iter(valarray<T>* vv, slice ss) :v(vv), s(ss), curr(0) { } Slice_iter end() { Slice_iter t = *this; t.curr = s.size(); // az utols utni elem indexrtke return t; } Slice_iter& operator++() { curr++; return *this; } Slice_iter operator++(int) { Slice_iter t = *this; curr++; return t; } T& operator[ ](size_t i) { return ref(curr=i); } T& operator()(size_t i) { return ref(curr=i); } T& operator*() { return ref(curr); } }; // ... // C stlus index // Fortran stlus index // az aktulis elem

Forrs: http://www.doksi.hu

904

A standard knyvtr

Mivel a slice rgztett mret, tartomnyellenrzst is vgezhetnk. A fenti pldban a slice::size() kihasznlsval valstottuk meg az end() mveletet, hogy a valarray utols utni elemre llthassuk a bejrt. A slice lerhat egy sort s egy oszlopot is, gy a Slice_iter is lehetv teszi, hogy akr egy sort, akr egy oszlopot jrjunk be a valarray objektumban. Ahhoz, hogy a Slice_iter igazn jl hasznlhat legyen, meg kell hatroznunk az ==, a != s a < opertort is:
template<class T> bool operator==(const Slice_iter<T>& p, const Slice_iter<T>& q) { return p.curr==q.curr && p.s.stride()==q.s.stride() && p.s.start()==q.s.start(); } template<class T> bool operator!=(const Slice_iter<T>& p, const Slice_iter<T>& q) { return !(p==q); } template<class T> bool operator<(const Slice_iter<T>& p, const Slice_iter<T>& q) { return p.curr<q.curr && p.s.stride()==q.s.stride() && p.s.start()==q.s.start(); }

22.4.6. A slice_array
A valarray s a slice felhasznlsval olyan szerkezetet pthetnk, amely gy nz ki s gy is viselkedik, mint egy valarray, pedig valjban csak a szelet ltal kijellt terletre hivatkozik. Ezt a slice_array tpust a kvetkezkppen definilhatjuk:
template <class T> class std::slice_array { public: typedef T value_type; void operator=(const valarray<T>&); void operator=(const T& val); void operator*=(const T& val); // hasonlan: /=, %=, +=, -=, ^=, &=, |=, <<=, >>= // minden elem val-t kapja rtkl // v[i]*=val minden elemre

Forrs: http://www.doksi.hu

22. Szmok

905

~slice_array(); private: slice_array(); slice_array(const slice_array&); slice_array& operator=(const slice_array&); valarray<T>* p; slice s;

// ltrehozs megakadlyozsa // msols megakadlyozsa // msols megakadlyozsa // megvalststl fgg brzols

};

A felhasznlk nem hozhatnak ltre kzvetlenl egy slice_array objektumot, csak egy valarray indexelsvel az adott szeletre. Miutn a slice_array objektumnak kezdrtket adtunk, minden r trtn hivatkozs kzvetve arra a valarray objektumra hat majd, amelyhez ltrehoztuk. Az albbi formban pldul egy olyan szerkezetet hozhatunk ltre, amely egy tmb minden msodik elemt vlasztja ki:
void f(valarray<double>& d) { slice_array<double>& v_even = d[slice(0,d.size()/2+d.size()%2,2)]; slice_array<double>& v_odd = d[slice(1,d.size()/2,2)]; v_even*= v_odd; v_odd = 0; // az elemprok szorzatt troljuk a pros elemekben // 0 rtkl adsa a pratlan elemeknek

A slice_array objektumok msolst nem engedhetjk meg, mert csak gy tehetjk lehetv olyan optimalizcis mdszerek alkalmazst, melyek kihasznljk, hogy egy objektumra csak egyflekppen, lnevek nlkl hivatkozhatunk. Ez a korltozs nha nagyon kellemetlen:
slice_array<double> row(valarray<double>& d, int i) { slice_array<double> v = d[slice(0,2,d.size()/2)]; } return d[slice(i%2,i,d.size()/2)];

// hiba: msols ksrlete // hiba: msols ksrlete

Egy slice_array msolsa gyakran helyettesthet a megfelel slice msolsval. A szeletek alkalmasak a tmbk bizonyos tpus rszhalmazainak kifejezsre. A kvetkez pldban pldul szeleteket hasznlunk egy folytonos rsztartomny kezelshez:

Forrs: http://www.doksi.hu

906

A standard knyvtr

inline slice sub_array(size_t first, size_t count) // [first:first+count[ { return slice(first,count,1); } void f(valarray<double>& v) { size_t sz = v.size(); if (sz<2) return; size_t n = sz/2; size_t n2 = sz-n; valarray<double> half1(n); valarray<double> half2(n2); half1 = v[sub_array(0,n)]; half2 = v[sub_array(n,n2)]; } // ... // v els felnek msolsa // v msodik felnek msolsa

A standard knyvtrban nem szerepel mtrix osztly. Ehelyett a valarray s a slice egyttesen igyekszik biztostani azokat az eszkzket, melyekkel klnbz ignyekhez igaztott mtrixokat pthetnk fel. Nzzk meg, hogyan kszthetnk egy egyszer, ktdimenzis mtrixot a valarray s a slice_array felhasznlsval:
class Matrix { valarray<double>* v; size_t d1, d2; public: Matrix(size_t x, size_t y); // megjegyzs: nincs alaprtelmezett konstruktor Matrix(const Matrix&); Matrix& operator=(const Matrix&); ~Matrix(); size_t size() const { return d1*d2; } size_t dim1() const { return d1; } size_t dim2() const { return d2; } Slice_iter<double> row(size_t i); Cslice_iter<double> row(size_t i) const; Slice_iter<double> column(size_t i); Cslice_iter<double> column(size_t i) const; double& operator()(size_t x, size_t y); double operator()(size_t x, size_t y) const; // Fortran stlus index

Forrs: http://www.doksi.hu

22. Szmok

907

Slice_iter<double> operator()(size_t i) { return row(i); } Cslice_iter<double> operator()(size_t i) const { return row(i); } Slice_iter<double> operator[ ](size_t i) { return row(i); } Cslice_iter<double> operator[ ](size_t i) const { return row(i); } Matrix& operator*=(double); }; valarray<double>& array() { return *v; } // C stlus index

A Matrix-ot egy valarray brzolja. Erre a tmbre a ktdimenzis jelleget a szeletels segtsgvel hzhatjuk r. Ezt az brzolst tekinthetjk egy-, kt-, hrom- stb. dimenzis tmbnek is, ugyangy, ahogy az alaprtelmezett ktdimenzis nzetet biztostjuk a row() s a column() fggvny megvalstsval. A Slice_iter objektumok segtsgvel megkerlhetjk a slice_array objektumok msolsnak tilalmt. Egy slice_array tpus objektumot nem adhatunk vissza:
slice_array<double> row(size_t i) { return (*v)(slice(i,d1,d2)); } // hiba

Ezrt a slice_array helyett egy bejrt (iterator) adunk meg, amely egy mutatt tartalmaz a valarray objektumra, illetve magt a slice objektumot. Meg kell hatroznunk egy tovbbi osztlyt is, a bejrt a konstans szeletekhez. Ez a Cslice_iter, amely egy const Matrix s egy nem konstans Matrix szeletei kztti klnbsget fejezi ki.
inline Slice_iter<double> Matrix::row(size_t i) { return Slice_iter<double>(v,slice(i,d1,d2)); } inline Cslice_iter<double> Matrix::row(size_t i) const { return Cslice_iter<double>(v,slice(i,d1,d2)); } inline Slice_iter<double> Matrix::column(size_t i) { return Slice_iter<double>(v,slice(i*d2,d2,1)); } inline Cslice_iter<double> Matrix::column(size_t i) const { return Cslice_iter<double>(v,slice(i*d2,d2,1)); }

Forrs: http://www.doksi.hu

908

A standard knyvtr

A Cslice_iter definicija megegyezik a Slice_iter-vel, a klnbsg mindssze annyi, hogy itt a szelet elemeire const referencik fognak mutatni. A tbbi tagfggvny meglehetsen egyszer:
Matrix::Matrix(size_t x, size_t y) { // ellenrzs: x s y rtelmes-e d1 = x; d2 = y; v = new valarray<double>(x*y); } double& Matrix::operator()(size_t x, size_t y) { return row(x)[y]; } double mul(Cslice_iter<double>& v1, const valarray<double>& v2) { double res = 0; for (size_t i = 0; i<v1.size(); i++) res+= v1[i]*v2[i]; return res; } valarray<double> operator*(const Matrix& m, const valarray<double>& v) { valarray<double> res(m.dim1()); for (size_t i = 0; i<m.dim1(); i++) res[i] = mul(m.row(i),v); return res; } Matrix& Matrix::operator*=(double d) { (*v) *= d; return *this; }

A Matrix indexelshez az (i,j) jellst hasznltam, mert a ( ) egy elg egyszer opertor, s ez a jells a matematikus trsadalomban elgg elfogadott. A sor fogalma ennek ellenre lehetv teszi a C s C++ vilgban megszokottabb [i][j] jellst is:
void f(Matrix& m) { m(1,2) = 5; m.row(1)(2) = 6;

// Fortran stlus index

Forrs: http://www.doksi.hu

22. Szmok

909

m.row(1)[2] = 7; m[1](2) = 8; m[1][2] = 9;

// zavaros, kevert stlus (de mkdik) // C++ stlus index

Ha a slice_array osztlyt hasznljuk az indexelshez, ers optimalizlsra van szksgnk. Ennek a szerkezetnek az ltalnostsa n-dimenzis mtrixra s tetszleges elemtpusra a megfelel mvelethalmaz biztostsval a 22.9[7] feladat tmja. Elkpzelhet, hogy els tletnk a ktdimenzis vektor megvalstsra a kvetkez volt:
class Matrix { valarray< valarray<double> > v; public: // ... };

Ez a megolds is mkdkpes (22.9[10]), de gy igen nehz anlkl sszeegyeztetni a hatkonysgot a nagyteljestmny szmtsok ltal megkvnt kvetelmnyekkel, hogy a valarray s a slice osztly ltal brzolt alacsonyabb s hagyomnyosabb szintre trnnk t.

22.4.7. Ideiglenes vltozk, msols, ciklusok


Ha megprblunk kszteni egy vektor vagy egy mtrix osztlyt, rvid idn bell rjhetnk, hogy hrom, egymssal sszefgg problmt kell figyelembe vennnk a teljestmnyt hangslyoz felhasznlk ignyeinek kielgtshez: 1. Az ideiglenes vltozk szmt a lehet legkisebbre kell cskkentennk. 2. A mtrixok msolsnak szmt a lehet legkisebbre kell cskkentennk. 3. Az sszetettebb mveletekben az ugyanazon adatokon vgighalad ciklusok szmt a lehet legkisebbre kell cskkentennk. A standard knyvtr nem kzvetlenl ezekkel a clokkal foglalkozik. Ennek ellenre ltezik olyan eljrs, melynek segtsgvel optimlis megoldst biztosthatunk. Vegyk pldul az U=M*V+W mveletet, ahol U, V s W vektor, M pedig mtrix. A legegyszerbb megoldsban kln-kln ideiglenes vltozra van szksgnk az M*V s az M*V+W szmra, s msolnunk kell az M*V s az M*V+W eredmnyt is. Egy okosabb

Forrs: http://www.doksi.hu

910

A standard knyvtr

vltozatban elkszthetjk a mul_add_and_assign(&U, &M, &V, &W) fggvnyt, amelynek nincs szksge ideiglenes vltozkra, sem a vektorok msolsra, s radsul minden mtrix minden elemt a lehet legkevesebbszer rinti. Ilyen mrv optimalizlsra egy-kt kifejezsnl tbbszr ritkn van szksgnk. gy a hatkonysgi problmk megoldsra a mul_add_and_assign() jelleg fggvnyek rsa, az egyik legegyszerbb mdszer melyeket a felhasznl szksg esetn meghvhat. Ugyanakkor lehetsg van olyan Matrix kifejlesztsre is, amely automatikusan alkalmaz ilyen optimalizcit, ha a kifejezseket megfelel formban fogalmazzuk meg. A lnyeg az, hogy az U=M*V+W kifejezst tekinthetjk egyetlen, ngy operandussal rendelkez opertor alkalmazsnak. Az eljrst mr bemutattuk az ostream mdostknl (21.4.6.3). Az ott bemutatott mdszer ltalnosan hasznlhat arra, hogy n darab ktoperandus opertor egyttest egy (n+1) paramter opertorknt kezeljk. Az U=M*V+W kifejezs kezelshez be kell vezetnnk kt segdosztlyt. Ennek ellenre bizonyos rendszerekben ez a eljrs igen jelents (akr 30-szoros) sebessgnvekedst is elrhet azzal, hogy tovbbi optimalizlst tesz lehetv. Elszr is meg kell hatroznunk egy Matrix s egy Vector szorzsakor kpzd eredmny tpust:
struct MVmul { const Matrix& m; const Vector& v; MVmul(const Matrix& mm, const Vector &vv) :m(mm), v(vv) { } }; operator Vector(); // az eredmny kiszmtsa s visszaadsa

inline MVmul operator*(const Matrix& mm, const Vector& vv) { return MVmul(mm,vv); }

A szorzs semmi mst nem tesz, mint hogy referencikat trol az operandusairl; az M*V tnyleges kiszmtst ksbbre halasztjuk. Az objektum, amelyet a * mvelet eredmnyez, igen kzel ll ahhoz, amit gyakran lezrs (closure; inkbb befoglal objektum) nven emlegetnek. Ugyangy kezelhetjk egy Vector hozzadst az eddigi eredmnyhez:
struct MVmulVadd { const Matrix& m; const Vector& v; const Vector& v2;

Forrs: http://www.doksi.hu

22. Szmok
MVmulVadd(const MVmul& mv, const Vector& vv) :m(mv.m), v(mv.v), v2(vv) { } }; operator Vector(); // az eredmny kiszmtsa s visszaadsa

911

inline MVmulVadd operator+(const MVmul& mv, const Vector& vv) { return MVmulVadd(mv,vv); }

Ezzel az M*V+W kifejezs kiszmtst is elhalasztjuk. Most mr csak azt kell biztostanunk, hogy hatkony algoritmussal szmoljuk ki a tnyleges eredmnyt, amikor a kifejezs eredmnyt rtkl adjuk egy Matrix objektumnak:
class Vector { // ... public: Vector(const MVmulVadd& m) // kezdeti rtkads m eredmnyvel { // elemek lefoglalsa, stb. mul_add_and_assign(this,&m.m,&m.v,&m.v2); } Vector& operator=(const MVmulVadd& m) // m eredmnynek rtkl adsa *this-nek { mul_add_and_assign(this,&m.m,&m.v,&m.v2); return *this; } // ...

};

gy teht az U=M*V+W kifejezs automatikusan a kvetkez kifejezsre vltozik:


U.operator=(MVmulVadd(MVmul(M,V),W))

Ezt pedig a helyben kifejts alkalmazsa az eredeti, megkvnt fggvnyhvsra alaktja:


mul_add_and_assign(&U,&M,&V,&W)

Ezzel a megoldssal kikszbltk a a mul_add_and-assign() fggvnyen a mul_add_and_assign() fggvnyt a olyan formt alaktottunk ki, amely az kedvezbb.

msolsokat s az ideiglenes vltozkat, radsul tovbbi optimalizlst is vgrehajthatunk. St, ha lehet legegyszerbb mdon ksztjk el, akkor is automatikus optimalizl eszkzk szmra sokkal

Forrs: http://www.doksi.hu

912

A standard knyvtr

A fenti pldban egy j Vector tpust hasznltunk (nem az egyszer valarray osztlyt), mert j rtkad opertorokat kellett meghatroznunk s az rtkadsoknak mindenkppen tagfggvnyeknek kell lennik (11.2.2). Nagy valsznsggel azonban a valarray osztlyt hasznljuk a Vector megvalstshoz. Ennek a mdszernek abban ll a jelentsge, hogy a leginkbb idignyes vektor- s mtrixmveletek elvgzst nhny egyszer nyelvi elemmel fogalmazzuk meg. Annak igazn soha sincs rtelme, hogy gy optimalizljunk egy fltucat opertort hasznl kifejezst, ezekre a hagyomnyosabb eljrsok (11.6) is megfelelek. Az tlet lnyege, hogy fordtsi idej vizsglatokat vgznk s befoglal objektumokat (closure) hasznlunk, azzal a cllal, hogy a rszkifejezsek kiszmtst a teljes mveletet brzol objektumra bzzuk. Az elv szmos terleten hasznlhat; lnyege, hogy tbb nll informcielemet egyetlen fggvnybe gyjtnk ssze, mieltt a tnyleges mveleteket elkezdennk. A ksleltetett kiszmts rdekben ltrehozott objektumokat kompozcis lezrt (befoglal) objektumoknak (composition closure object) vagy egyszeren kompozitoroknak (compositor) nevezzk.

22.4.8. ltalnostott szeletek


A 22.4.6 pontban szerepl Matrix plda megmutatta, hogyan hasznlhatunk kt slice objektumot egy ktdimenzis tmb sorainak s oszlopainak lersra. ltalnosan az igaz, hogy egy slice alkalmas egy n-dimenzis tmb brmely sornak vagy oszlopnak kijellsre (22.9[7]), de nha szksgnk lehet olyan rsztmb kijellsre is, amely nem egyetlen sora, vagy egyetlen oszlopa az n-dimenzis tmbnek. Tegyk fel, hogy egy 3-szor 4-es mtrix bal fels sarkban elhelyezked 2-szer 3-as rszmtrixot akarunk kijellni: 00 01 02 10 11 12 20 21 22 30 31 32 Sajnos ezek az elemek nem gy helyezkednek el a memriban, hogy egy slice segtsgvel lerhatnnk azokat: 0 1 2 4 5 6

00 10 20 30 01 11 21 31 02 12 22 32

Forrs: http://www.doksi.hu

22. Szmok

913

A gslice egy olyan ltalnostott slice, amely (majdnem) n darab szelet informciit tartalmazza:
class std::gslice { // ahelyett, hogy a slice-hoz hasonlan 1 lpskz s 1 mret lenne, // a gslice-ban n lpskz s n mret van public: gslice(); gslice(size_t s, const valarray<size_t>& l, const valarray<size_t>& d); size_t start() const; valarray<size_t> size() const; valarray<size_t> stride() const; // az els elem indexrtke // elemek szma a dimenziban // lpskz index[0], index[1], ... kztt

};

Az j rtkek lehetv teszik, hogy a gslice lekpezst hatrozzon meg n darab egsz s egy index kztt, amit a tmb elemeinek elrshez hasznlhatunk fel. Egy 2-szer 3-as mtrix elhelyezkedst pldul kt (hosszsg, lpskz) elemprral rhatjuk le. A 22.4.5 pontban bemutattuk, hogy a 2 hosszsg s a 4 lpskz a 3-szor 4-es mtrix egy sornak kt elemt rja le, ha a Fortran elrendezst hasznljuk. Ugyangy, a 3 hosszsg s az 1 lpskz egy oszlop 3 elemt jelli ki. A kett egytt kpes lerni egy 2-szer 3-as rszmtrix sszes elemt. Az elemek felsorolshoz az albbi eljrsra lesz szksgnk:
size_t gslice_index(const gslice& s, size_t i, size_t j) { return s.start()+i*s.stride()[0]+j*s.stride()[1]; } size_t len[ ] = { 2, 3 }; size_t str[ ] = { 4, 1 }; valarray<size_t> lengths(len,2); valarray<size_t> strides(str,2); void f() { gslice s(0,lengths,strides); for (int i = 0 ; i<s.size()[0]; i++) cout << gslice_index(s,i,0) << " "; cout << ", "; for (int j = 0 ; j<s.size()[1]; j++) cout << gslice_index(s,0,j) << " "; // sor // oszlop // (len[0],str[0]) egy sort r le // (len[1],str[1]) egy oszlopot r le

Az eredmny 0 4 , 0 1 2 lesz.

Forrs: http://www.doksi.hu

914

A standard knyvtr

Ezzel a mdszerrel egy gslide kt (hosszsg, lpskz) pr segtsgvel kpes megadni egy ktdimenzis tmb tetszleges rszmtrixt. Hrom (hosszsg, lpskz) prral lerhatjuk egy hromdimenzis tmb rsztmbjeit s gy tovbb. Ha egy gslice objektumot hasznlunk egy valarray indexelshez, akkor egy gslice_array tpus objektumot kapunk, ami a gslice ltal kijellt elemeket tartalmazza. Pldul:
void f(valarray<float>& v) { gslice m(0,lengths,strides); v[m] = 0; // rtkl adja 0-t v[0],v[1],v[2],v[4],v[5],v[6] elemeknek }

A gslice_array ugyanazokat a tagfggvnyeket biztostja, mint a slice_array, gy egy gslice_array objektumot sem hozhat ltre kzvetlenl a felhasznl, s nem is msolhatja (22.4.6), viszont gslice_array objektumot kapunk eredmnyknt, ha egy valarray (22.4.2) objektumot egy gslice objektummal indexelnk.

22.4.9. Maszkok
A mask_array tpus a valarray tmb valamely rsze kijellsnek msik mdja. Radsul az eredmnyt is egy valarray-szer formban kapjuk meg. A valarray osztly szempontjbl a maszk egyszeren egy valarray<bool> objektum. Amikor maszkot hasznlunk egy valarray indexelshez, a true bitek jelzik, hogy a valarray megfelel elemt az eredmnyben is meg szeretnnk kapni. Ez a megolds lehetv teszi, hogy egy valarray objektumnak olyan rszhalmazt dolgozzuk fel, amely nem valamilyen egyszer elrendezsben (pldul egy szeletben) helyezkedik el:
void f(valarray<double>& v) { bool b[ ] = { true , false, false, true, false, true }; valarray<bool> mask(b,6); // a 0, 3, s 5 elem valarray<double> vv = cos(v[mask]); } // vv[0]==cos(v[0]), vv[1]==cos(v[3]), // vv[2]==cos(v[5])

A mask_array osztly ugyanazokat a mveleteket biztostja, mint a slice_array. Egy mask_array objektumot sem hozhatunk ltre kzvetlenl, s nem is msolhatjuk (22.4.6). Egy mask_array meghatrozshoz egy valarray (22.4.2) objektumot kell indexelnnk valarray<bool> objektummal. A maszkolshoz hasznlt valarray mrete nem lehet nagyobb, mint azon valarray elemeinek szma, amelyben ezt indexelsre akarjuk hasznlni.

Forrs: http://www.doksi.hu

22. Szmok

915

22.4.10. Indirekt tmbk


Az indirect_array (indirekt vagyis kzvetett tmb) lehetsget ad arra, hogy egy valarray objektumot tetszlegesen indexeljnk s trendezznk:
void f(valarray<double>& v) { size_t i[ ] = { 3, 2, 1, 0 }; valarray<size_t> index(i,4); valarray<double> vv = log(v[index]); }

// az els 4 elem fordtott sorrendben // a 3, 2, 1, 0 elemek (ebben a sorrendben) // vv[0]==log(v[3]), vv[1]==log(v[2]), // vv[2]==log(v[1]), vv[3]==log(v[0])

Ha egy sorszm ktszer szerepel, akkor a valarray objektum valamely elemre ktszer hivatkozunk egy mveleten bell. Ez pontosan az a tpus tbbszrs hivatkozs (lnv), amit a valarray nem enged meg. gy az indirect_array mkdse nem meghatrozott, ha tbbszr hasznljuk ugyanazt az indexrtket. Az indirect_array osztly ugyanazokat a mveleteket biztostja, mint a slice_array, teht nem hozhatunk ltre kzvetlenl indirect_array objektumokat, s nem is msolhatjuk azokat (22.4.6); erre a clra egy valarray (22.4.2) objektum valarray<size_t> objektummal val indexelse szolgl.

22.5. Komplex aritmetika


A standard knyvtr tartalmaz egy complex sablont, krlbell azokkal a tulajdonsgokkal, amelyekkel a 11.3 pontban szerepl complex osztlyt meghatroztuk. A knyvtrban szerepl complex osztlynak azrt kell sablonknt (template) szerepelnie, hogy klnbz skalr tpusokra pl komplex szmokat is kezelni tudjunk. A knyvtrban kln-kln vltozat szerepel a float, a double s a long double skalrtpushoz. A complex osztly az std nvtrhez tartozik s a <complex> fejllomny segtsgvel rhet el:
template<class T> class std::complex { T re, im; public: typedef T value_type;

Forrs: http://www.doksi.hu

916

A standard knyvtr

complex(const T& r = T(), const T& i = T()) : re(r), im(i) { } template<class X> complex(const complex<X>& a) : re(a.real()), im(a.imag()) { } T real() const { return re; } T imag() const { return im; } complex<T>& operator=(const T& z); // complex(z,0) rtkl adsa template<class X> complex<T>& operator=(const complex<X>&); // hasonlan: +=, -=, *=, /=

};

Az itt bemutatott megvalsts s a helyben kifejtett (inline) fggvnyek csak illusztrciknt szerepelnek. Ha nagyon akarunk, el tudunk kpzelni ms megvalstst is a standard knyvtr complex osztlyhoz. Figyeljk meg a sablon tagfggvnyeket, amelyek lehetv teszik, hogy brmilyen tpus complex objektumnak egy msikkal adjunk kezdrtket, vagy azt egyszer rtkadssal rendeljk hozz (13.6.2). A knyv folyamn a complex osztlyt nem sablonknt hasznltuk, csak egyszer osztlyknt. Erre azrt volt lehetsg, mert a nvterekkel gyeskedve a double rtkek complex osztlyt tettk alaprtelmezett:
typedef std::complex<double> complex;

Rendelkezsnkre llnak a szoksos egyoperandus (unris) s ktoperandus (binris) opertorok is:


template<class T> complex<T> operator+(const complex<T>&, const complex<T>&); template<class T> complex<T> operator+(const complex<T>&, const T&); template<class T> complex<T> operator+(const T&, const complex<T>&); // hasonlan: -, *, /, ==, s != template<class T> complex<T> operator+(const complex<T>&); template<class T> complex<T> operator-(const complex<T>&);

A koordinta-fggvnyek a kvetkezk:
template<class T> T real(const complex<T>&); template<class T> T imag(const complex<T>&); template<class T> complex<T> conj(const complex<T>&); // polr koordintarendszer szerinti ltrehozs (abs(),arg()):

Forrs: http://www.doksi.hu

22. Szmok

917

template<class T> complex<T> polar(const T& rho, const T& theta); template<class T> T abs(const complex<T>&); template<class T> T arg(const complex<T>&); template<class T> T norm(const complex<T>&); // nha rho a neve // nha theta a neve // az abs() ngyzetgyke

A szoksos matematikai fggvnyeket is hasznlhatjuk:


template<class T> complex<T> sin(const complex<T>&); // hasonlan: sinh, sqrt, tan, tanh, cos, cosh, exp, log, s log10 template<class T> complex<T> pow(const complex<T>&,int); template<class T> complex<T> pow(const complex<T>&, const T&); template<class T> complex<T> pow(const complex<T>&, const complex<T>&); template<class T> complex<T> pow(const T&, const complex<T>&);

A ki- s bemeneti adatfolyamok kezelsre pedig a kvetkezk szolglnak:


template<class T, class Ch, class Tr> basic_istream<Ch,Tr>& operator>>(basic_istream<Ch,Tr>&, complex<T>&); template<class T, class Ch, class Tr> basic_ostream<Ch,Tr>& operator<<(basic_ostream<Ch,Tr>&, const complex<T>&);

A komplex szmok kirsi formja (x,y), mg beolvassra hasznlhatjuk az x, az (x) s az (x,y) formtumot is (21.2.3, 21.3.5). A complex<float>, a complex<double> s a complex<long double> specializcik azrt szerepelnek, hogy korltozzuk a konverzikat (13.6.2) s lehetsget adjunk az optimalizlsra is:
template<> class complex<double> { double re, im; public: typedef double value_type; complex(double r = 0.0, double i = 0.0) : re(r), im(i) { } complex(const complex<float>& a) : re(a.real()), im(a.imag()) { } explicit complex(const complex<long double>& a) : re(a.real()), im(a.imag()) { } }; // ...

Forrs: http://www.doksi.hu

918

A standard knyvtr

Ezek utn egy complex<float> rtk gond nlkl complex<double> szmra konvertlhat, de egy complex<long double> mr nem. Ugyanilyen specializcik biztostjk, hogy egy complex<float> vagy egy complex<double> automatikusan konvertlhat complex<long double> rtkre, de egy complex<long double> nem alakthat titokban complex<float> vagy complex<double> szmm. rdekes mdon az rtkadsok nem ugyanazt a vdelmet biztostjk, mint a konstruktorok:
void f(complex<float> cf, complex<double> cd, complex<long double> cld, complex<int> ci) { complex<double> c1 = cf; // j complex<double> c2 = cd; // j complex<double> c3 = cld; // hiba: esetleg csonkol complex<double> c4(cld); // rendben: explicit konverzi complex<double> c5 = ci; // hiba: nincs konverzi c1 = cld; c1 = cf; c1 = ci; // rendben, de vigyzat: esetleg csonkol // rendben // rendben

22.6. ltalnostott numerikus algoritmusok


A <numeric> fejllomnyban a standard knyvtr biztost nhny ltalnostott numerikus algoritmust is, az <algorithm> fejllomny (18. fejezet) nem numerikus algoritmusainak stlusban:

ltalnostott numerikus algoritmusok <numeric> accumulate() inner_product() partial_sum() adjacent_difference() Egy sorozat elemein vgez el egy mveletet. Kt sorozat elemein vgez el egy mveletet. Egy sorozatot llt el egy msik sorozatra alkalmazott mvelettel. Egy sorozatot llt el egy msik sorozatra alkalmazott mvelettel.

Forrs: http://www.doksi.hu

22. Szmok

919

Ezek az algoritmusok egy-egy jellegzetes, gyakran hasznlt mveletet ltalnostanak. Az egyik pldul kiszmtja egy sorozat elemeinek sszegt, de gy, hogy brmilyen tpus sorozatra hasznlhassuk az eljrst, a mveletet pedig, amit a sorozat elemeire el kell vgezni, paramterknt adjuk meg. Mindegyik algoritmus esetben az ltalnos mveletet kiegszti egy olyan vltozat, amely kzvetlenl a leggyakoribb opertort hasznlja az adott algoritmushoz.

22.6.1. Az accumulate()
Az accumulate() algoritmust felfoghatjuk gy, mint egy vektor elemeit sszegz eljrs ltalnostst. Az accumulate() algoritmus az std nvtrhez tartozik s a <numeric> fejllomnybl rhetjk el:
template <class In, class T> T accumulate(In first, In last, T init) { while (first != last) init = init + *first++; // sszeads return init; } template <class In, class T, class BinOp> T accumulate(In first, In last, T init, BinOp op) { while (first != last) init = op(init,*first++); // ltalnos mvelet return init; }

Az accumulate() legegyszerbb vltozata egy sorozat elemeit adja ssze, az elemekre rtelmezett + opertor segtsgvel:
void f(vector<int>& price, list<float>& incr) { int i = accumulate(price.begin(),price.end(),0); double d = 0; d = accumulate(incr.begin(),incr.end(),d); // ... }

// int-be sszegzs // double-ba sszegzs

A visszatrsi rtk tpust az tadott kezdrtk tpusa hatrozza meg.

Forrs: http://www.doksi.hu

920

A standard knyvtr

Gyakran azok az rtkek, melyeket sszegezni szeretnnk, nem egy sorozat elemeiknt llnak rendelkezsnkre. Ha ilyen helyzetbe kerlnk, akkor az accumulate() szmra megadhat mveletet felhasznlhatjuk arra is, hogy az rtkeket ellltsuk. A legegyszerbb ilyen eljrs csak annyit tesz, hogy a sorozatban trolt adatszerkezetbl kivlasztja a kvnt rtket:
struct Record { // ... int unit_price; int number_of_units; }; long price(long val, const Record& r) { return val + r.unit_price * r.number_of_units; } void f(const vector<Record>& v) { cout << "sszeg: " << accumulate(v.begin(),v.end(),0,price) << '\n'; }

Az accumulate() szerepnek megfelel mveletet egyes fejlesztk reduce vagy reduction nven valstjk meg.

22.6.2. Az inner_product
Egyetlen sorozat elemeinek sszegzse, illetve az ilyen jelleg mveletek nagyon gyakoriak, ugyanakkor a sorozatprokon ilyen mveletet vgz eljrsok viszonylag ritkk. Az inner_product() algoritmus meghatrozsa az std nvtrben szerepel, deklarcijt pedig a <numeric> fejllomnyban tallhatjuk meg:
template <class In, class In2, class T> T inner_product(In first, In last, In2 first2, T init) { while (first != last) init = init + *first++ * *first2++; return init; } template <class In, class In2, class T, class BinOp, class BinOp2> T inner_product(In first, In last, In2 first2, T init, BinOp op, BinOp2 op2) { while (first != last) init = op(init,op2(*first++,*first2++)); return init; }

Forrs: http://www.doksi.hu

22. Szmok

921

Szoks szerint, a msodik sorozatnak csak az elejt kell megadnunk paramterknt. A msodik bemeneti sorozatrl azt felttelezzk, hogy legalbb olyan hossz, mint az els. Amikor egy Matrix objektumot egy valarray objektummal akarunk sszeszorozni, akkor a legfontosabb mvelet az inner_product:
valarray<double> operator*(const Matrix& m, valarray<double>& v) { valarray<double> res(m.dim2()); for (size_t i=0; i<m.dim1(); i++) { const Cslice_iter<double>& ri = m.row(i); res[i] = inner_product(ri,ri.end(),&v[0], double(0)); } return res;

valarray<double> operator*(valarray<double>& v, const Matrix& m) { valarray<double> res(m.dim1()); for (size_t i=0; i<m.dim2(); i++) { const Cslice_iter<double>& ci = m.column(i); res[i] = inner_product(ci, ci.end(), &v[0], double(0)); } return res;

Az inner_product (bels szorzat) bizonyos formit gyakran emlegetjk pont-szorzs (dot product) nven is.

22.6.3. Nvekmnyes vltozs


A partial_sum() s az adjacent_difference() algoritmus egyms fordtottjnak (inverznek) tekinthet, de mindkett az inkrementlis vagy nvekmnyes vltozs (incremental change) fogalmval foglalkozik. Lersuk az std nvtrben s a <numeric> fejllomnyban szerepel:
template <class In, class Out> Out adjacent_difference(In first, In last, Out res); template <class In, class Out, class BinOp> Out adjacent_difference(In first, In last, Out res, BinOp op);

Forrs: http://www.doksi.hu

922

A standard knyvtr

Az a, b, c, d, sorozatbl pldul az adjacent_difference() a kvetkez sorozatot lltja el: a, b-a, c-b, d-c, Kpzeljnk el egy sorozatot, amely hmrsklet-rtkeket tartalmaz. Ebbl knnyen kszthetnk egy olyan vektort, amely a hmrsklet-vltozsokat tartalmazza:
vector<double> temps; void f() { adjacent_difference(temps.begin(),temps.end(),temps.begin()); }

A 17, 19, 20, 20, 17 sorozatbl pldul a 17, 2, 1, 0, -3 eredmnyt kapjuk. Megfordtva, a partial_sum() azt teszi lehetv, hogy tbb nvekmnyes vltozs vgeredmnyt kiszmtsuk:
template <class In, class Out, class BinOp> Out partial_sum(In first, In last, Out res, BinOp op) { if (first==last) return res; *res = *first; T val = *first; while (++first != last) { val = op(val,*first); *++res = val; } return ++res; } template <class In, class Out> Out partial_sum(In first, In last, Out res) { return partial_sum(first,last,res,plus); // 18.4.3 }

Az a, b, c, d, sorozatbl a partial_sum() a kvetkez eredmnyt lltja el: a, a+b, a+b+c, a+b+c+d, Pldul:
void f() { partial_sum(temps.begin(),temps.end(),temps.begin()); }

Forrs: http://www.doksi.hu

22. Szmok

923

Figyeljk meg, hogy a partial_sum() nveli a res rtkt, mieltt j rtket adna a hivatkozott terletnek. Ez lehetv teszi, hogy a res ugyanaz a sorozat legyen, mint a bemeneti. Ugyangy mkdik az adjacent_difference() is. Teht a kvetkez utasts az a, b, c, d sorozatot az a, a+b, a+b+c, a+b+c+d sorozatra kpezi le:
partial_sum(v.begin(),v.end(),v.begin());

Az albbi utastssal pedig visszallthatjuk az eredeti sorozatot:


adjacent_difference(v.begin(),v.end(),v.begin());

Egy msik plda: a partial_sum() a 17, 2, 1, 0, -3 sorozatbl a 17, 19, 20, 20, 17 sorozatot lltja vissza. Akik szerint a hmrsklet-vltozsok figyelse csupn rendkvl unalmas rszletkrdse a meteorolginak vagy a tudomnyos, laboratriumi ksrleteknek, azok szmra megjegyezzk, hogy a kszlettartalkok vltozsainak vizsglata pontosan ugyanezen a kt mveleten alapul.

22.7. Vletlenszmok
A vletlenszmok fontos szerepet tltenek be igen sok szimulcis s jtkprogramban. A <cstdlib> s az <stdlib.h> fejllomnyban a standard knyvtr egyszer alapot biztost a vletlenszmok ellltshoz:
#define RAND_MAX megvalsts_fgg /* nagy pozitv egsz */ int rand(); int srand(int i); // l-vletlenszm 0 s RAND_MAX kztt // a vletlenszm-elllt belltsa i-vel

J vletlenszm-elllt (genertor) ksztse nagyon nehz feladat, ezrt sajnos nem minden rendszerben ll rendelkezsnkre j rand() fggvny. Klnsen igaz ez a vletlenszmok als bitjeire, aminek kvetkeztben a rand()%n kifejezs egyltaln nem tekinthet ltalnos (ms rendszerre tvihet) mdszernek arra, hogy 0 s n-1 kz es vletlenszmokat lltsunk el. A (double(rand())/RAND_MAX)*n ltalban sokkal elfogadhatbb eredmnyt ad.

Forrs: http://www.doksi.hu

924

A standard knyvtr

Az srand() fggvny meghvsval egy j vletlenszm-sorozatot kezdhetnk a paramterknt megadott alaprtktl. A programok tesztelsekor gyakran nagyon hasznos, hogy egy adott alaprtkbl szrmaz vletlenszm-sorozatot meg tudjunk ismtelni. Ennek ellenre ltalban a program minden futtatsakor ms alaprtkrl akarunk indulni. Ahhoz hogy jtkainkat tnyleg megjsolhatatlann tegyk, gyakran szksg van arra, hogy az alaprtket a program krnyezetbl szerezzk be. Az ilyen programokban a szmtgp rja ltal jelzett id nhny bitje ltalban j alaprtk. Ha sajt vletlenszm-ellltt kell ksztennk, elengedhetetlen az alapos tesztels (22.9[14]). Egy vletlenszm-elllt gyakran hasznlhatbb, ha osztly formjban ll rendelkezsnkre. gy knnyen kszthetnk vletlenszm-ellltkat a klnbz rtktartomnyokhoz:
class Randint { unsigned long randx; public: Randint(long s = 0) { randx=s; } void seed(long s) { randx=s; } // egyenletes eloszls 32 bites long-ot felttelezve

// bvs szmok: 31 bitet hasznlunk egy 32 bites long-bl: long abs(long x) { return x&0x7fffffff; } static double max() { return 2147483648.0; } // figyelem: double long draw() { return randx = randx*1103515245 + 12345; } double fdraw(){ return abs(draw())/max(); } }; long operator()() { return abs(draw()); } //a [0,1] tartomnyban //a [0,pow(2,31)] tartomnyban // egyenletes eloszls [0:n[ intervallumban

class Urand : public Randint { long n; public: Urand(long nn) { n = nn; } };

long operator()() { long r = n*fdraw(); return (r==n) ? n-1 : r; }

class Erand : public Randint { // exponencilis eloszls vletlenszm-elllt long mean; public: Erand(long m) { mean=m; } long operator()() { return -mean * log( (max()-draw())/max() + .5); } };

Forrs: http://www.doksi.hu

22. Szmok

925

me egy rvid prbaprogram:


int main() { Urand draw(10); map<int,int> bucket; for (int i = 0; i< 1000000; i++) bucket[draw()]++; for(int j = 0; j<10; j++) cout << bucket[j] << '\n'; }

Ha nem minden bucket rtke van a 100 000 kzelben, valahol valamilyen hibt vtettnk. Ezek a vletlenszm-ellltk annak a megoldsnak kiss talaktott vltozatai, amit a C++ knyvtr legels megvalstsban (pontosabban az els osztlyokkal bvtett C knyvtrban, 1.4) alkalmaztam.

22.8. Tancsok
[1] A numerikus problmk gyakran nagyon rnyaltak. Ha nem vagyunk 100 szzalkig biztosak valamely problma matematikai vonatkozsaiban, mindenkppen krjk szakember segtsgt vagy ksrletezznk sokat. 22.1. [2] A beptett adattpusok tulajdonsgainak megllaptshoz hasznljuk a numeric_limits osztlyt. 22.2. [3] A felhasznli skalrtpusokhoz adjunk meg kln numeric_limits osztlyt. 22.2. [4] Ha a futsi idej hatkonysg fontosabb, mint a rugalmassg az elemtpusokra s a mveletekre nzve, a szmmveletekhez hasznljuk a valarray osztlyt. 22.4. [5] Ha egy mveletet egy tmb rszre kell alkalmaznunk, ciklusok helyett hasznljunk szeleteket. 22.4.6. [6] Hasznljunk kompozitorokat, ha egyedi algoritmusokkal s ideiglenes vltozk kikszblsvel akarjuk nvelni rendszernk hatkonysgt. 22.4.7. [7] Ha komplex szmokkal kell szmolnunk, hasznljuk az std::complex osztlyt. 22.5. [8] Ha egy rtket egy lista elemeinek vgignzsvel kell kiszmtanunk, akkor mieltt sajt ciklus megvalstsba kezdennk, vizsgljuk meg, nem felel-e meg cljainknak az accumulate(), az inner_product() vagy az adjacent_difference() algoritmus. 22.6.

Forrs: http://www.doksi.hu

926

A standard knyvtr

[9] Az adott eloszlsokhoz ksztett vletlenszm-elllt osztlyok hasznosabbak, mint a rand() kzvetlen hasznlata. 22.7. [10] Figyeljnk r, hogy vletlenszmaink valban vletlenek legyenek. 22.7.

22.9. Gyakorlatok
1. (*1.5) Ksztsnk egy fggvnyt, amely gy viselkedik, mint az apply(), azzal a klnbsggel, hogy nem tagfggvny s fggvnyobjektumokat is elfogad. 2. (*1.5) Ksztsnk fggvnyt, amely csak abban tr el az apply() fggvnytl, hogy nem tagfggvnyknt szerepel, elfogad fggvnyobjektumokat, s a sajt valarray paramtert mdostja. 3. (*2) Fejezzk be a Slice_iter-t (22.4.5). Klnsen figyeljnk a destruktor ltrehozsakor. 4. (*1.5) rjuk meg jbl a 17.4.1.3 pontban szerepl programot az accumulate() felhasznlsval. 5. (*2) Ksztsk el a << s a >> ki-, illetve bemeneti opertort a valarray osztlyhoz. Ksztsnk egy get_array() fggvnyt, amely gy hoz ltre egy valarray objektumot, hogy annak mrett is maga a bemenet adja meg. 6. (*2.5) Hatrozzunk meg s ksztsnk el egy hromdimenzis tmbt a megfelel mveletek ltrehozsval. 7. (*2.5) Hatrozzunk meg s ksztsnk el egy n-dimenzis mtrixot a megfelel mveletek ltrehozsval. 8. (*2.5) Ksztsnk egy valarray-szer osztlyt, s adjuk meg hozz a + s a * mveletet. Hasonltsuk ssze ennek hatkonysgt a sajt C++-vltozatunk valarray osztlynak hatkonysgval. tlet: prbljuk ki tbbek kztt az x=0.5*(x+y)-z kifejezs kirtkelst klnbz mret x, y s z vektor felhasznlsval. 9. (*3) Ksztsnk egy Fortran stlus tmbt (pldul Fort_array nven), ahol az indexek nem 0-tl, hanem 1-tl indulnak. 10. (*3) Ksztsk el a Matrix osztlyt gy, hogy az egy sajt valarray objektumot hasznljon az elemek brzolshoz (nem pedig egy mutatt vagy referencit a valarray objektumra). 11. (*2.5) Kompozitorok (22.4.7) segtsgvel ksztsnk egy hatkony tbbdimenzis indexelsi rendszert a [ ] jellssel. A kvetkez kifejezsek mindegyike a megfelel elemeket, illetve rsztmbket jellje ki, mghozz egyszer indexmveletek segtsgvel: v1[x], v2[x][y], v2[x], v3[x][y][z], v3[x][y], v3[x] stb.

Forrs: http://www.doksi.hu

22. Szmok

927

12. (*2) A 22.7 pontban szerepl program tletnek ltalnostsval rjunk olyan fggvnyt, amely paramterknt egy vletlenszm-ellltt fogad s egyszer grafikus formban szemllteti annak eloszlst, gy, hogy segtsgvel szemlletesen is ellenrizhessk a vletlenszm-elllt helyessgt. 13. (*1) Ha n egy int rtk, akkor milyen a (double(rand())/RAND_MAX)*n kifejezs eloszlsa? 14. (*2.5) Rajzoljunk pontokat egy ngyzet alak terleten. Az egyes pontok koordinta-prjait az Urand(N) osztly segtsgvel lltsuk el, ahol N a megjelentsi terlet oldalnak kppontban mrt hossza. Milyen kvetkeztetst vonhatunk le az eredmny alapjn az Urand ltal ltrehozott vletlenszmok eloszlsrl? 15. (*2) Ksztsnk egy Normal eloszls vletlenszm-ellltt Nrand nven.

Forrs: http://www.doksi.hu

Negyedik rsz Tervezs a C++ segtsgvel

Ez a rsz a programfejleszts tfogbb szemszgbl mutatja be a C++-t s az ltala tmogatott eljrsokat. A hangslyt a tervezsre s a nyelv lehetsgein alapul hatkony megvalstsra fektetjk.

Fejezetek 23. Fejleszts s tervezs 24. Tervezs s programozs 25. Az osztlyok szerepe

Forrs: http://www.doksi.hu

Csak most kezdem felfedezni, milyen nehz a gondolatainkat paprra vetni. Amg csupn lersbl ll, elg knnyen megy, de amint rvelni kell, a gondolatok kztt megfelel kapcsolatokat kell teremteni, vilgosan s grdlkenyen kell fogalmazni, s ez, mint mondottam, szmomra oly nehzsget jelent, amire nem is gondoltam (Charles Darwin)

Forrs: http://www.doksi.hu

23
Fejleszts s tervezs
Nincs ezstgoly. (F. Brooks) Programpts Clok s eszkzk A fejlesztsi folyamat fejleszts ciklus Tervezsi clok Tervezsi lpsek Osztlyok keresse Mveletek meghatrozsa Fggsek meghatrozsa Felletek meghatrozsa Osztlyhierarchik jraszervezse Modellek Ksrletezs s elemzs Tesztels A programok karbantartsa Hatkonysg Vezets jrahasznosts Mret s egyensly Az egynek fontossga Hibrid tervezs Bibliogrfia Tancsok

23.1. ttekints
Ez az els a hrom fejezetbl, melyek rszletesebben bemutatjk a szoftvertermk ksztst, a viszonylag magas szint tervezsi szemlletmdtl azokig a programozsi fogalmakig s eljrsokig, amelyekkel a C++ a tervezst kzvetlenl tmogatja. Ez a fejezet a bevezetn s a szoftverfejleszts cljait s eszkzeit rviden trgyal 23.3 ponton kvl kt f rszbl ll:

Forrs: http://www.doksi.hu

932

Tervezs a C++ segtsgvel

23.4 23.5

A szoftverfejleszts folyamatnak ttekintse. Gyakorlati tancsok a szoftverfejleszti munka megszervezshez.

A 24. fejezet a tervezs s a programozsi nyelv kzti kapcsolatot trgyalja, a 25. pedig az osztlyoknak a tervezsben jtszott szerepvel foglalkozik. Egszben vve a 4. rsz clja, hogy thidalja a szakadkot a nyelvfggetlensgre trekv tervezs s a rvidltan a rszletekre sszpontost programozs kztt. Egy nagyobb program elksztsi folyamatban mindkettnek megvan a helye, de a tervezsi szempontok s a hasznlt eljrsok kztt megfelel sszhangot kell teremteni, hogy elkerljk a katasztrfkat s a felesleges kiadsokat.

23.2. Bevezets
A legegyszerbbek kivtelvel minden program elksztse sszetett s gyakran csggeszt feladat. A programutastsok megrsa mg az nllan dolgoz programoz szmra is csupn egy rsze a folyamatnak. A problma elemzse, az tfog programtervezs, a dokumentls, a tesztels s a program fenntartsnak, mdostsnak krdsei, valamint mindennek az sszefogsa mellett eltrpl az egyes kdrszek megrsnak s kijavtsnak feladata. Termszetesen nevezhetnnk e tevkenysgek sszessgt programozsnak, majd logikus mdon kijelenthetnnk: n nem tervezek, csak programozok. Akrminek nevezzk is azonban a tevkenysget, nha az a fontos, hogy a rszletekre sszpontostsunk, nha pedig az, hogy az egsz folyamatot tekintsk. Sem a rszleteket, sem a vgclt nem szabad szem ell tvesztennk br nha pontosan ez trtnik , csak gy kszthetnk teljes rtk programokat. Ez a fejezet a programfejleszts azon rszeivel foglalkozik, melyek nem vonjk magukkal egyes kdrszek rst s javtst. A trgyals kevsb pontos s rszletes, mint a korbban bemutatott nyelvi tulajdonsgok s programozsi eljrsok trgyalsa, de nem is lehet olyan tkletes szakcsknyvet rni, amely lern, hogyan kell j programot kszteni. Ltezhetnek rszletes hogyan kell lersok egyes programfajtkhoz, de az ltalnosabb alkalmazsi terletekhez nem. Semmi sem ptolja az intelligencit, a tapasztalatot, s a programozsi rzket. Kvetkezskppen ez a fejezet csak ltalnos tancsokat ad, illetve megkzeltsi mdokat mutat be s tanulsgos megfigyelseket knl. A problmk megkzeltst bonyoltja a programok eleve elvont termszete s az a tny, hogy azok a mdszerek, melyek kisebb projekteknl (pldul amikor egy vagy kt ember r 10 000 sornyi kdot) mkdnek, nem szksgszeren alkalmazhatk kzepes vagy nagy

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

933

projektekben. Ezrt szmos krdst inkbb a kevsb elvont mrnki tudomnyokbl tvett hasonlatokon keresztl kzeltnk meg, kdpldk hasznlata helyett. A programtervezs trgyalsa a C++ fogalmaival, illetve a kapcsold pldk a 24. s 25. fejezetben tallhatk, de az itt bemutatott elvek tkrzdnek mind magban a C++ nyelvben, mind a knyv egyes pldiban. Arra is emlkeztetnm az olvast, hogy az alkalmazsi terletek, emberek s programfejleszt krnyezetek rendkvl sokflk, ezrt nem vrhat el, hogy minden itteni javaslatnak kzvetlenl hasznt vehessk egy adott problma megoldsban. A megfigyelsek vals helyzetekben szlettek s szmos terleten felhasznlhatk, de nem tekinthetk ltalnos rvnynek, ezrt nem rt nmi egszsges szkepticizmus. A C++ gy is tekinthet, mint egy jobb C. De ha gy tesznk, kihasznlatlanul maradnak a C++ igazi erssgei, gy a nyelv elnyeinek csak tredke rvnyesl. Ez a fejezet kimondottan azokra a tervezsi megkzeltsekre sszpontost, melyek lehetv teszik a C++ elvont adatbrzolsi s objektumkzpont programozsi lehetsgeinek hatkony hasznlatt. Az ilyen mdszereket gyakran nevezzk objektumorientlt (object-oriented) tervezsnek. A fejezet f tmi a kvetkezk: A szoftverfejleszts legfontosabb szempontja, hogy tisztban legynk vele, mit is ksztnk. A sikeres szoftverfejleszts sokig tart. Az ltalunk ptett rendszerek sszetettsgnek hatrait sajt tudsunk s eszkzeink kpessgei szabjk meg. Semmifle tanknyvi mdszer nem helyettestheti az intelligencit, a tapasztalatot, a j tervezsi s programozsi rzket. A ksrletezs minden bonyolultabb program elksztsnl lnyeges. A programkszts klnbz szakaszai a tervezs, a programozs s a tesztels nem vlaszthatk el szigoran egymstl. A programozs s a tervezs nem vlaszthatk el e tevkenysgek megszervezsnek krdstl. Knnyen abba a hibba eshetnk s persze megfizetjk az rt , hogy ezeket a szempontokat albecsljk, pedig az elvont tleteket nehz a gyakorlatba ttenni. Tudomsul kell vennnk a tapasztalat szksgessgt: ppgy, mint a csnakpts, a kerkprozs, vagy ppen a programozs, a tervezs sem olyan kpessg, amely pusztn elmleti tanulmnyokkal elsajtthat.

Forrs: http://www.doksi.hu

934

Tervezs a C++ segtsgvel

Gyakran elfeledkeznk a rendszerpts emberi tnyezirl, s a programfejleszts folyamatt egyszeren meghatrozott lpsek sorozatnak tekintjk, mely a bemenetbl adott mveletek vgrehajtsval ellltja a kvnt kimenetet, a lefektetett szablyoknak megfelelen. A programozsi nyelv, amelyet hasznlunk, elfedi az emberi tnyez jelenltt, mrpedig a tervezs s a programozs emberi tevkenysgek ha errl megfeledkeznk, nem jrhatunk sikerrel. Ez a fejezet olyan rendszerek tervezsvel foglalkozik, melyek a rendszert pt emberek tapasztalathoz s erforrsaihoz kpest ignyesek. gy tnik, a fejlesztk szeretik feszegetni lehetsgeik hatrait. Az olyan munkknl, melyek nem jelentenek ilyen kihvst, nincs szksg a tervezs megvitatsra, hiszen ezeknek kidolgozott kereteik vannak, amelyeket nem kell szttrni. Csak akkor van szksg j, jobb eszkzk s eljrsok elsajttsra, amikor valami ignyes dologra vllalkozunk. Arra is hajlamosak vagyunk, hogy hozznk kpest joncokra olyan feladatokat bzzunk, melyekrl mi tudjuk, hogyan kell elvgezni, de k nem. Nem ltezik egyetlen helyes mdszer minden rendszer tervezsre s ptsre. A hitet az egyetlen helyes mdszerben gyermekbetegsgnek tekintenm, ha nem fordulna el oly gyakran, hogy tapasztalt programozk s tervezk engednek neki. Emlkeztetek arra, hogy azrt, mert egy eljrs a mlt vben egy adott munknl jl mkdtt, mg nem biztos, hogy ugyanaz mdosts nlkl mkdni fog valaki msnl vagy egy msik munka sorn is. A legfontosabb, hogy mentesek legynk az ilyesfajta felttelezsektl. Az itt lertak termszetesen a nagyobb mret programok fejlesztsre vonatkoznak. Azok, akik nem mkdnek kzre ilyen fejlesztsben, visszalhetnek s rlhetnek, ltvn, milyen rmsgektl menekltek meg, illetve nzegethetik az egyni munkval foglalkoz rszeket. A programok mretnek nincs als hatra, ahol a kdols eltti tervezs mg sszer, de ltezik ilyen hatr, amennyiben a tervezs s dokumentls megkzeltsi mdjrl van sz. A clok s a mret kztti egyensly fenntartsnak krdseivel a 23.5.2 pont foglalkozik. A programfejleszts kzponti problmja a bonyolultsg. A bonyolultsggal pedig egyetlen mdon lehet elbnni, az oszd meg s uralkodj! elvet kvetve. Ha egy problmt kt nmagban kezelhet rszproblmra vlaszthatunk szt, flig mris megoldottuk azt. Ez az egyszer elv bmulatosan vltozatos mdokon alkalmazhat. Nevezetesen, egy modul vagy egy osztly hasznlata a rendszer megtervezsnl kt rszre osztja a programot a tnyleges megvalstsra s a felhasznli kdra amelyeket idelis esetben csak egy jl definilt fellet (interface) kapcsol ssze: ez a programmal jr bonyolultsg kezelsnek alapvet megkzeltse. Ugyangy a programtervezsi folyamat is kln tevkeny-

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

935

sgekre bonthat, s gy a kzremkd s egymssal kapcsolatban ll fejlesztk kztt feladatokra bontva sztoszthat: ez pedig a fejlesztsi folyamat s a rsztvev programozk kztti bonyolult kapcsolatok kezelsnek alapvet megkzeltse. Mindkt esetben a feloszts s a kapcsolatot megteremt felletek meghatrozsa az, ahol a legnagyobb tapasztalatra s rzkre van szksg. Ez nem egyszer, mechanikusan megoldhat feladat; jellemzen lesltst ignyel, melyre csak egy rendszer alapos tanulmnyozsa s az elvonatkoztatsi szintek megfelel megrtse ltal tehetnk szert (lsd: 23.4.2, 24.3.1 s 25.3). Ha egy programot vagy fejlesztsi folyamatot csak bizonyos szemszgbl vizsglunk, slyos hibkat vthetnk. Azt is vegyk szre, hogy klnvlasztani mind az emberek feladatait, mind a programokat knny. A feladat nehz rsze a hatkony kapcsolattarts biztostsa a vlaszfal kt oldaln lev felek kztt anlkl, hogy lerombolnnk a vlaszfalat vagy elnyomnnk az egyttmkdshez szksges kommunikcit. Ez a fejezet egy tervezsi megkzeltst mutat be, nem egy teljes tervezsi mdszert; annak bemutatsa tlmutatna e knyv keretein. Az itt bemutatott megkzelts az ltalnosts vagyis a formlis megfogalmazs klnbz fokozataival s a mgttk megbv alapvet elvekkel ismertet meg. Nem szakirodalmi ttekints s nem szndkszik rinteni minden, a szoftverfejlesztsre vonatkoz tmt, vagy bemutatni minden szempontot. Ez ismt csak meghaladn e knyv lehetsgeit. Ilyen ttekintst tallhatunk [Booch,1994]-ben. Feltnhet, hogy a kifejezseket itt elg ltalnos s hagyomnyos mdon hasznlom. A legrdekesebbeknek tervezs, prototpus, programoz a szakirodalomban szmos klnbz s gyakran egymssal ellenttes defincija tallhat. Legynk vatosak, nehogy olyan nem szndkolt jelentst olvassunk ki az itt elmondottakbl, melyek az egyes kifejezsek nmagban vett vagy csupn helyileg pontos meghatrozsain alapulnak.

23.3. Clok s eszkzk


A professzionlis programozs clja olyan termket ltrehozni, mellyel felhasznli elgedettek lesznek. Ennek elsdleges mdja olyan programot alkotni, melynek bels felptse tiszta, s csapatot kovcsolni olyan tervezkbl s programozkbl, akik elg gyesek s lelkesek ahhoz, hogy gyorsan s hatkonyan reagljanak a vltozsokra s lni tudjanak lehetsgeikkel.

Forrs: http://www.doksi.hu

936

Tervezs a C++ segtsgvel

Mirt? A program bels szerkezete s keletkezsnek folyamata idelis esetben nem rinti a vgfelhasznlt. Nyltabban fogalmazva: ha a vgfelhasznlnak agglya van, hogyan rtk meg a programot, akkor azzal a programmal valami baj van. Ezt figyelembe vve feltehetjk a krdst: miben ll a program szerkezetnek s a programot megalkot embereknek a fontossga? Elszr is, egy programnak tiszta bels felptssel kell rendelkeznie, hogy megknnytse a tesztelst, a ms rendszerekre val tltetst, a program karbantartst s mdostst, a bvtst, az jraszervezst s a kd megrtst.

A lnyeg, hogy egyetlen sikeres nagy program trtnete sem r vget a piacra kerlssel; jabb s jabb programozk s tervezk dolgoznak rajta, j hardverre viszik t, elre nem ltott clokra hasznljk fel s tbbszr is talaktjk szerkezett. A program lete folyamn j vltozatokat kell kszteni, elfogadhat mennyisg hibval, elfogadhat id alatt. Ha ezzel nem szmolunk, kudarcra vagyunk tlve. Vegyk szre, hogy br a vgfelhasznlk idelis esetben nem kell, hogy ismerjk egy rendszer bels felptst, elfordulhat, hogy kvncsiak r, pldul azrt, hogy fel tudjk becslni megbzhatsgt, lehetsges fellvizsglatt s bvtst. Ha a krdses program nem egy teljes rendszer, csak ms programok ptst segt knyvtrak kszlete, a felhasznlk mg tbb rszletet akarnak tudni, hogy kpesek legyenek jobban kihasznlni a knyvtrakat s tleteket merthessenek bellk. Egyenslyt kell teremteni a program tfog tervezsnek mellzse s a szerkezetre fektetett tlzott hangsly kztt. Az elbbi vg nlkli javtgatsokhoz vezet (ezt most leszlltjuk, a problmt meg majd kijavtjuk a kvetkez kiadsban), az utbbi tlbonyoltja a tervezst s a lnyeg elvsz a formai tkletessgre val trekvs kzben, ami azt eredmnyezi, hogy a tnyleges megvalsts ksedelmet szenved a program szerkezetnek folytonos alaktgatsa miatt (de ez az j felpts sokkal jobb, mint a rgi; az emberek hajlandak vrni r). A forma tartalom fl rendelse gyakran eredmnyez olyan erforrs-igny rendszereket is, melyeket a leend felhasznlk tbbsge nem engedhet meg magnak. Az egyensly megtartsa a tervezs legnehezebb rsze s ez az a terlet, ahol a tehetsg s a tapasztalat megmutatkozik. A vlaszts az nll programoz szmra is nehz, de mg nehezebb a nagyobb programoknl, melyek tbb, klnbz kpessg ember munkjt ignylik.

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

937

A programot egy olyan szervezett kzssgnek kell megalkotnia s fenntartania, mely a szemlyi s vezetsi vltozsok ellenre kpes a feladatot megoldani. Npszer megkzelts a programfejlesztst nhny viszonylag alacsony szint feladatra szkteni, melyek egy merev vzba illeszkednek. Az elgondols egy knnyen betanthat (olcs) s cserlhet, alacsony szint programozkbl (kdolkbl) ll osztly s egy valamivel kevsb olcs, de ugyangy cserlhet (s ugyangy mellzhet) tervezkbl ll osztly ltrehozsa. A kdolkrl nem ttelezzk fel, hogy tervezsi dntseket hoznak, mg a tervezkrl nem ttelezzk fel, hogy a kdols piszkos rszleteivel trdnek. Ez a megkzelts gyakran csdt mond; ahol pedig mkdik, nagy mret, de gyenge teljestmny programokat eredmnyez. E megolds buktati a kvetkezk: Elgtelen kapcsolattarts a megvalstst vgzk s a tervezk kztt, ami alkalmak elmulasztst, ksst, rossz hatkonysgot, s a tapasztalatbl val tanuls kptelensge miatt ismtld problmkat eredmnyez. A programozk kezdemnyezsi hatskrnek elgtelensge, ami a szakmai fejlds hinyhoz, felletessghez s koszhoz vezet. E rendszer alapvet gondja a visszajelzs hinya, mely lehetv tenn, hogy a kzremkdk egyms tapasztalataibl tanuljanak. Ez a szks emberi tehetsg elvesztegetse. Az olyan keretek biztostsa, melyen bell mindenki megcsillanthatja tehetsgt, j kpessgeket fejleszthet ki, tletekkel jrulhat hozz a sikerhez s jl rezheti magt, nemcsak az egyedli tisztessges megolds, hanem gyakorlati s gazdasgi rtelme is van. Msrszt egy rendszert nem lehet felpteni, dokumentlni s a vgtelensgig fenntartani valamilyen ttekinthet szerkezet nlkl. Egy jtsokat is ignyl munka elejn gyakran az a legjobb, ha egyszeren megkeressk a legjobb szakembereket s hagyjuk ket, hogy gy kzeltsk meg a problmt, ahogy a legjobbnak tartjk. A munka elrehaladtval azonban egyre inkbb gyelni kell a helyes temezsre, a rszfeladatok kiosztsra, a bevont szemlyek kztti kapcsolattartsra s bizonyos a jellsre, elnevezsekre, dokumentcira, tesztelsre stb. vonatkoz irnyelvek betartsra. Ismt csak egyenslyra s a helynval irnti rzkre van szksg. Egy tl merev rendszer akadlyozhatja a fejldst s elfojthatja az innovcit. Ebben az esetben a vezet tehetsge s tapasztaltsga mrettetik meg, de az nll programoz szmra is ugyanilyen dilemma kivlasztani, hol prbljon gyeskedni s hol csinlja a knyv szerint. Az adott munknl ajnlatos nem csak a kvetkez kiadsra, hanem hossz tvra tervezni. Az elrelts hinya mindig megbosszulja magt. A szervezeti felptst s a programfejlesztsi stratgit gy kell megvlasztani, hogy tbb program tbb kiadsnak megalkotst clozzuk meg vagyis sikerek sorozatt kell megterveznnk.

Forrs: http://www.doksi.hu

938

Tervezs a C++ segtsgvel

A tervezs clja a program szmra tiszta s viszonylag egyszer bels szerkezet architektra ltrehozsa. Ms szval, olyan vzat akarunk alkotni, melybe beilleszthetk az egyes kdrszek s ezltal irnyelvknt szolgl e kdrszek megrshoz. A terv a tervezsi folyamat vgtermke (amennyiben egy krkrs folyamatnak vgtermke lehet). Ez ll a tervez s a programoz, illetve az egyes programozk kzti kapcsolattarts kzppontjban. Fontos, hogy itt kell arnyrzknk legyen. Ha n mint nll programoz egy kis programot tervezek, melyet holnap fogok elkszteni, megfelel pontossg s rszletessg lehet egy bortk htra firklt pr sor. A msik vglet: egy tervezk s programozk szzait foglalkoztat rendszer fejlesztse knyveket kitev, gondosan megrt szabvnyokat kvnhat, valamilyen szablyhoz rszben vagy teljes egszben igazod jellsek hasznlatval. Egy terv megfelel rszletessgi, pontossgi s formalitsi szintjnek meghatrozsa nmagban vve is kihv szakmai s vezetsi feladat. Ebben s a kvetkez fejezetekben felttelezem, hogy egy program terve osztlydeklarcik egy halmazval (a privt deklarciikat, mint zavar rszleteket, ltalban elhagyom) s a kztk lev kapcsolatokkal van kifejezve. Ez persze egyszersts: egy konkrt tervben sok ms krds is felmerl; pldul a prhuzamos folyamatok, a nvterek kezelse, a nem tag fggvnyek s adatok hasznlata, az osztlyok s fggvnyek paramterezse, az jrafordts szksgessgt a lehet legalacsonyabb szintre visszaszort kdszervezs, a perzisztencia s a tbb-szmtgpes hasznlat. A trgyalsnak ezen a szintjn mindenkppen szksg van egyszerstsre s a C++-ban a tervezshez az osztlyok megfelel kiindulpontot jelenthetnek. A fent emltett tmakrk kzl nhnyat futlag ez a fejezet is rint, de a C++-ban val tervezsre kzvetlenl hatst gyakorlkat a 24. s 25. fejezet trgyalja. Ha egy bizonyos objektumorientlt tervezsi modellre rszleteiben vagyunk kvncsiak, [Booch, 1994] lehet segtsgnkre. Az elemzs (analzis, analysis) s a tervezs (design) kztti klnbsgre nem trek ki klnsebben, egyrszt mert nem tmja e knyvnek, msrszt az egyes tervezsi mdszerek esetben e klnbsget ms-ms mdon hatrozhatnnk meg. Rviden annyit mondhatunk, hogy az elemzsi mdszert mindig az adott tervezsi megkzeltshez kell igaztani, azt pedig a hasznlt programozsi nyelv s stlus alapjn kell megvlasztani.

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

939

23.4. A fejlesztsi folyamat


A szoftverfejleszts ismtlst s gyarapodst jelent: a folyamat minden szakasza a fejleszts alatt jra s jra fellvizsglatra kerl s minden egyes fellvizsglat finomtja az adott szakasz vgtermkt. A fejlesztsi folyamatnak ltalban nincs kezdete s nincs vge. Amikor egy programrendszert megterveznk s elksztnk, ms emberek terveit, knyvtrait s programjait vesszk alapul. Amikor befejezzk, a terv s a kd trzst msokra hagyjuk, hogy finomtsk, fellvizsgljk, bvtsk s ms gpekre ttegyk azt. Termszetesen egy adott munknak lehet meghatrozott kezdete s vge, s fontos is (br gyakran meglepen nehz), hogy azt idben s trben tisztn s pontosan behatroljuk. Ha azonban gy tesznk, mintha tiszta lappal indulnnk, komoly problmkat okozhatunk. gy tenni, mintha a vilg a program vgs tadsnl vgzdne, egyarnt fejfjst okozhat utdaink s gyakran sajt magunk szmra. Ebbl kvetkezik, hogy a kvetkez fejezetek brmilyen sorrendben olvashatk, mivel egy vals munka sorn a tervezsi s megvalstsi szempontok majdnem tetszs szerint tfedhetik egymst. Ez azt jelenti, hogy a tervezs majdnem mindig jratervezs, amely egy elz tervezsen s nmi fejlesztsi tapasztalaton alapul. Tovbb, a tervezst korltozzk az temtervek, a rsztvev emberek kpessgei, a kompatibilitsi krdsek s gy tovbb. A tervez, vezet vagy programoz szmra nagy kihvst jelent rendet teremteni e folyamatban anlkl, hogy elfojtannk az jtsi trekvseket s tnkretennnk a visszajelzs rendszert, melyek a sikeres fejlesztshez szksgesek. A fejlesztsi folyamat hrom szakaszbl ll: Elemzs: a megoldand problma kiterjedsnek meghatrozsa Tervezs: a rendszer tfog felptsnek megalkotsa Megvalsts: a kd megrsa s ellenrzse Mg egyszer emlkeztetnk e folyamat ismtld termszetre nem vletlen, hogy a szakaszok kztt nem lltottam fel sorrendet. Vegyk szre azt is, hogy a programfejleszts nhny f szempontja nem kln szakaszknt jelentkezik, mivel ezeknek a folyamat sorn mindvgig rvnyeslnik kell: Ksrletezs Ellenrzs A tervek s a megvalsts elemzse Dokumentls Vezets

Forrs: http://www.doksi.hu

940

Tervezs a C++ segtsgvel

A program fenntartsa vagy karbantartsa egyszeren e fejlesztsi folyamat tbbszri ismtlst jelenti (23.4.6). A legfontosabb, hogy az elemzs, a tervezs s a megvalsts ne vljon el tlzottan egymstl, s hogy a bevont emberek kzs szellemisg, kultra s nyelv rvn hatkonyan tudjanak egymssal kommuniklni. Nagyobb programok fejlesztsnl ez sajnos ritkn van gy. Idelis esetben az egynek a munka sorn tbb szakaszban is rszt vesznek; ez az ismeretek s tapasztalatok tadsnak legjobb mdja. Sajnos a programfejleszt cgek gyakran akadlyozzk az ilyen mozgst, pldul azltal, hogy a tervezknek magasabb beosztst s/vagy magasabb fizetst adnak, mint a csak programozknak. Ha gyakorlati szempontbl nem clszer, hogy a munkatrsak vndorolva tanuljanak s tantsanak, legalbb arra biztassuk ket, hogy rendszeresen beszlgessenek a fejleszts ms szakaszaiba bevontakkal. Kisebb s kzepes projekteknl gyakran nem klnbztetik meg az elemzst s a tervezst; e kt szakasz egyesl. A kis programok ksztsnl ltalban ugyangy nem vlik szt a tervezs s a programozs, ami persze megoldja a kommunikcis problmkat. Fontos, hogy a formalitsok s a szakaszok elklntse mindig az adott munkhoz igazodjanak (23.5.2); nem ltezik egyetlen, rk rvny t. Az itt lert programfejlesztsi modell gykeresen klnbzik a hagyomnyos vzess modelltl, amelyben a fejleszts szablyosan s egyenes irnyban halad a fejlesztsi szakaszokon t, az elemzstl a tesztelsig. A vzess modell alapvet baja, hogy az informci folysa egyirny. Ha a folys irnyban haladva problmkba tkznk, a szervezeti felpts s a munkamdszerek arra knyszertenek, hogy helyben oldjuk meg azokat, az elz szakaszokra val hats nlkl. A visszacsatols ezen hinya gyenge tervekhez vezet, a helyi javtsok pedig torz megvalstst eredmnyeznek. Vannak elkerlhetetlen esetek, amikor az informci mindenkppen visszafel ramlik s megvltoztatja az eredeti terveket. A gerjesztett hullm csak lassan s nehzkesen jut el cljhoz, hiszen a rendszert gy alkottk meg, hogy ne legyen szksg vltoztatsokra s a rendszer emiatt lassan s kelletlenl reagl. A semmi vltoztats vagy a helyi javts elve teht olyan elvv vltozik, mely szerint egy rszleg nem adhat tbbletmunkt ms rszlegeknek a sajt knyelme rdekben. Lehetsges, hogy mire egy nagyobb hibt szlelnek, mr olyan sok paprmunkt vgeztek, hogy a kd javtshoz szksges erfeszts eltrpl ahhoz kpest, ami a dokumentci mdostshoz kell. Ilyenkor a paprmunka vlik a programfejleszts f kerkktjv. Termszetesen ilyen problmk elfordulhatnak s el is fordulnak, jllehet a nagy rendszerek fejlesztst alaposan megszervezik. Mindenkppen elengedhetetlen nmi paprmunka. Az egyenes vonal (vzess) fejlesztsi modell alkalmazsa azonban nagyban nveli a valsznsgt, hogy a problma kezelhetetlenn vlik.

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

941

A vzesses modellel az a problma, hogy nincs benne megfelel visszacsatols s kptelen a vltozsokra reaglni. Az itt vzolt ciklikus megkzelts veszlye viszont a ksrts, hogy a valdi gondolkods s halads helyett nem azonos cl rdekben vgrehajtott mdostsok sort vgezzk el. Mindkt problmt knnyebb felismerni, mint megoldani, s brmilyen jl is szervezzk meg a feladat vgrehajtst, knnyen sszetveszthetjk a puszta munkt a haladssal. Termszetesen a munka elrehaladtval a fejlesztsi folyamat klnbz szakaszainak jelentsge vltozik. Kezdetben a hangsly az elemzsen s a tervezsen van, s kevesebb figyelmet fordtunk a programozsi krdsekre. Ahogy mlik az id, az erforrsok a tervezs s a programozs fel toldnak, majd inkbb a programozs s a tesztels fel. Kulcsfontossg azonban, hogy soha ne sszpontostsunk csupn az elemzs/tervezs/megvalsts egyikre, kizrva ltkrnkbl a tbbit. Ne felejtsk el, hogy nem segthet rajtunk semmilyen, a rszletekre is kiterjed figyelem, vezetsi mdszer vagy fejlett technolgia, ha nincs tiszta elkpzelsnk arrl, mit is akarunk elrni. Tbb terv hisul meg amiatt, hogy nincsenek jl meghatrozott s valszer cljai, mint brmilyen ms okbl. Brmit tesznk is s brhogyan fogunk is hozz, tzznk ki kzzelfoghat clokat, jelljk ki a hozz vezet t llomsait, s hasznljunk fel minden megfelel technolgit, ami csak elrhet mg akkor is, ha az beruhzst ignyel , mert az emberek jobban dolgoznak megfelel eszkzkkel s megfelel krnyezetben. Ne higgyk persze, hogy knny ezt a tancsot megfogadni.

23.4.1. A fejleszts krforgsa


Egy program fejlesztse sorn lland fellvizsglatra van szksg. A f ciklus a kvetkez lpsek ismtelt vgrehajtsbl ll: 1. Vizsgljuk meg a problmt. 2. Alkossunk tfog tervet. 3. Keressnk szabvnyos sszetevket. Igaztsuk ezeket a tervhez. 4. Ksztsnk j szabvnyos sszetevket. Igaztsuk ezeket is a tervhez. 5. lltsuk ssze a konkrt tervet. Hasonlatknt vegynk egy autgyrat. A projekt beindtshoz kell, hogy legyen egy tfog terv az j auttpusrl. Az elzetes tervnek valamilyen elemzsen kell alapulnia s ltalnossgban kell lernia az autt, inkbb annak szndkolt hasznlatt, mintsem a kvnt tulajdonsgok kialaktsnak rszleteit szem eltt tartva. Eldnteni, mik is a kvnt tulajdonsgok vagy mg inkbb, a dntshez viszonylag egyszer irnyelveket adni gyakran

Forrs: http://www.doksi.hu

942

Tervezs a C++ segtsgvel

a munka legnehezebb rsze. Ezt sikeresen ltalban egyetlen, nagy ttekintssel rendelkez egyn tudja elvgezni, s ez az, amit gyakran ltomsnak (vision) neveznk. Igen gyakori, hogy hinyoznak az ilyen tiszta clok mrpedig a terv ezek nlkl meginoghat vagy meg is hisulhat. Tegyk fel, hogy egy kzepes nagysg autt akarunk pteni, ngy ajtval s ers motorral. A tervezs els szakasza a leghatrozottabban nem az aut (s az alkatrszek) megtervezse a semmibl. Egy meggondolatlan programtervez vagy programoz hasonl krlmnyek kztt (ostobn) pontosan ezt prbln tenni. Az els lps annak szmbavtele, milyen alkatrszek szerezhetk be a gyr sajt kszleteibl s megbzhat szlltktl. Az gy tallt alkatrszek nem kell, hogy pontosan illeszkedjenek az j autba. Lesz md az alkatrszek hozzigaztsra. Azt is megtehetjk, hogy az ilyen alkatrszek kvetkez kiadshoz irnyelveket adunk, hogy sajt elkpzelseinkhez jobban illeszkedjenek. Pldul ltezhet egy motor, melynek megfelelek a tulajdonsgai, azzal a kivtellel, hogy kicsit gyengbb a leadott teljestmnye. A cg vagy a motor szlltja rszerelhet egy turbfeltltt, hogy ezt helyrehozza, anlkl, hogy befolysoln az alapvet terveket. Vegyk szre, hogy az ilyen, az alaptervet nem befolysol vltoztats valsznsge kicsi, hacsak az eredeti terv nem ellegezi meg valamilyen formban az igazthatsgot. Az ilyen igazthatsg ltalban egyttmkdst kvn kztnk s a motor szlltja kztt. A programoz hasonl lehetsgekkel rendelkezik: a tbbalak (polimorf) osztlyok s sablonok (template) pldul hatkonyan felhasznlhatk egyedi vltozatok ksztsre. Nem szabad azonban elvrnunk, hogy az osztly ksztje a mi szjunk ze szerint bvtse alkotst; ehhez a kszt elreltsra vagy a velnk val egyttmkdsre van szksg. Ha kimerlt a megfelel szabvnyos alkatrszek vlasztka, az auttervez nem rohan, hogy megtervezze az j authoz az optimlis j alkatrszeket. Ez egyszeren tl kltsges lenne. Tegyk fel, hogy nem kaphat megfelel lgkondicionl egysg s hogy egy megfelel L alak tr ll rendelkezsre a motortren bell. Az egyik megolds egy L alak lgkondicionl egysg megtervezse lenne, de annak valsznsge, hogy ez a klnleges forma ms auttpusoknl is hasznlhat, mg alapos igazts esetn is csekly. Ebbl kvetkezik, hogy autterveznk nem lesz kpes az ilyen egysgek termelsi kltsgeit ms auttpusok tervezivel megosztani, gy az egysg kihasznlhat lettartama rvid lesz. rdemes teht olyan egysget tervezni, mely szlesebb krben szmthat rdekldsre; vagyis egy tisztbb vonal egysget kell tervezni, mely jobban az ignyekhez igazthat, mint a mi elmletben elksztett L alak furcsasgunk. Ez valsznleg tbb munkval jr, mint az L alak egysg s magval vonhatja az aut tfog terveinek mdostst is, hogy illeszkedjen az ltalnosabb cl egysghez. Mivel az j egysg szlesebb krben hasznlhat, mint a mi L alak csodnk, felttelezhet, hogy szksg lesz egy kis igaztsra, hogy tkletesen

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

943

illeszkedjen a mi fellvizsglt ignyeinkhez. A programoz ismt hasonl lehetsgek kzl vlaszthat: ahelyett, hogy az adott programra nzve egyedi kdot rna, j, ltalnosabb sszetevt tervezhet, amely remlhetleg valamilyen szabvnny vlik. Amikor vgkpp kifogytunk a lehetsges szabvnyos sszetevkbl, sszelltjuk a vgleges tervet. A lehet legkevesebb egyedi tervezs alkatrszt hasznljuk, mert jvre kiss ms formban ismt vgig kell csinlnunk ugyanezt a munkt a kvetkez j modellhez, s az egyedi alkatrszek lesznek azok, amelyeket a legvalsznbb, hogy jra kell ptennk vagy el kell dobnunk. Sajnos a hagyomnyosan tervezett programokkal kapcsolatban az a tapasztalat, hogy kevs bennk az olyan rsz, amelyet egyltaln nll sszetevnek lehetne tekinteni s az adott programon kvl mshol is fel lehetne hasznlni. Nem lltom, hogy minden auttervez olyan sszeren gondolkodik, mint ahogy a hasonlatban felvzoltam vagy hogy minden programtervez elkveti az emltett hibkat. ppen ellenkezleg, a bemutatott modell a programtervezsben is hasznlhat. Ez a fejezet s a kvetkezk olyan eljrsokat ismertetnek, melyek a C++-ban mkdnek. A programok nem kzzelfoghat termszete miatt viszont a hibkat ktsgtelenl nehezebb elkerlni (24.3.1, 24.3.4), s rmutatok majd arra is (23.5.3.1), hogy a cgek mkdsi elve gyakran elveszi az emberek btorsgt, hogy az itt vzolt modellt hasznljk. Ez a fejlesztsi modell valjban csak akkor mkdik jl, ha hosszabb tvra terveznk. Ha ltkrnk csak a kvetkez kiadsig terjed, a szabvnyos sszetevk megalkotsa s fenntartsa rtelmetlen, egyszeren felesleges tlmunknak fogjuk ltni. E modell kvetse olyan fejlesztkzssgek szmra javasolt, melyek lete szmos projektet fog t s melyek mrete miatt rdemes pnzt fektetni a szksges eszkzkbe (a tervezshez, programozshoz s projektvezetshez) s oktatsba (a tervezk, programozk s vezetk rszre). Modellnk valjban egyfajta szoftvergyr vzlata, amely furcsa mdon csak mreteiben klnbzik a legjobb nll programozk gyakorlattl, akik az vek sorn mdszerek, tervek, eszkzk s knyvtrak kszlett ptettk fel, hogy fokozzk szemlyes hatkonysgukat. Sajnos gy tnik, a legtbb cg elmulasztotta kihasznlni a legjobbak gyakorlati tudst, mert hinyzott belle az elrelts s a kpessg, hogy az ltaluk hasznlt megkzeltst nagyobb programokra is alkalmazza. Vegyk szre, hogy nincs rtelme minden szabvnyos sszetevtl elvrni, hogy ltalnos rvny szabvny legyen. Nemzetkzileg szabvnyos knyvtrbl csak nhny maradhat fenn. A legtbb sszetev vagy alkatrsz (csak) egy orszgon, egy ipargon, egy gyrtsoron, egy osztlyon, egy alkalmazsi terleten stb. bell lesz szabvny. A vilg egyszeren tl nagy ahhoz, hogy valsgos vagy igazn kvnatos cl legyen minden eszkzre egyetemes szabvnyt alkotni.

Forrs: http://www.doksi.hu

944

Tervezs a C++ segtsgvel

Ha mr az elejn az egyetemessget tzzk ki clul, a tervet arra tljk, hogy soha ne legyen befejezve. Ennek egyik oka, hogy a fejleszts krkrs folyamat, s elengedhetetlen, hogy legyen egy mkd rendszere, amelybl tapasztalatokat merthetnk (23.4.3.6).

23.4.2. Tervezsi clok


Melyek a tervezs tfog cljai? Az egyik termszetesen az egyszersg, de milyen szempontok szerint? Felttelezzk, hogy a tervnek fejldnie kell, vagyis a programot majd bvteni, ms rendszerre tvinni, finomhangolni s tbbfle, elre nem lthat mdon mdostani lesz szksges. Kvetkezskppen olyan tervet s rendszert kell megcloznunk, amely egyszer, de knnyen s sokfle mdon mdosthat. A valsgban a rendszerrel szemben tmasztott kvetelmnyek mr a kezdeti terv s a program els kibocstsa kztt tbbszr megvltoznak. Ez magval vonja, hogy a programot gy kell megtervezni, hogy a mdostsok sorozata alatt a lehet legegyszerbb maradjon. A mdosthatsgot figyelembe vve a kvetkezket kell clul kitznnk: Rugalmassg Bvthetsg Hordozhatsg (ms rendszerekre val tltethetsg) A legjobb, ha megprbljuk elszigetelni a program azon terleteit, melyek valsznleg vltozni fognak, a felhasznlk szmra pedig lehetv tesszk, hogy mdostsaikat e rszek rintse nlkl vgezhessk el. Ezt a program kulcsfogalmainak azonostsval s azzal rhetjk el, hogy minden egyes osztlyra kizrlagos felelssggel rbzzuk egy fogalommal kapcsolatos minden informci kezelst. Ez esetben mindennem vltoztats csupn az adott osztly mdostsval rhet el. Idelis esetben egy fogalom mdostshoz elg egy j osztly szrmaztatsa (23.4.3.5) vagy egy sablon eltr paramterezse. Termszetesen az elvet a gyakorlatban sokkal nehezebb kvetni. Vegynk egy pldt: egy meteorolgiai jelensgeket utnz programban egy esfelht akarunk megjelenteni. Hogyan csinljuk? Nem lehet ltalnos eljrsunk a felh megjelentsre, mivel a felh kinzete fgg a felh bels llapottl s ez az llapot a felh kizrlagos felelssge al kell, hogy tartozzon.

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

945

A problma els megoldsa, hogy hagyjuk a felhre a sajt megjelentst. Ez bizonyos krlmnyek kztt elfogadhat, de nem ltalnos megolds, mert egy felht sokfle mdon lehet megjelenteni: rszletes kppel, elnagyolt vzlatknt, vagy mint egy jelet egy trkpen. Ms szval az, hogy egy felh mire hasonlt, fgg mind a felhtl, mind a krnyezettl. A msik megolds, hogy a felht tjkoztatjuk krnyezetrl, majd hagyjuk, hogy megjelentse magt. Ez mg tbb esetben elfogadhat, de mg mindig nem ltalnos mdszer. Ha tudatjuk a felhvel krnyezetnek rszleteit, megsrtjk azt a szablyt, hogy egy osztly csak egy dologrt lehet felels s hogy minden dolog valamelyik osztly felelssge. Nem biztos, hogy eljuthatunk a felh krnyezetnek egy kvetkezetes jellshez, mert ltalban az, hogy egy felh mihez hasonlt, fgg mind a felhtl, mind a nztl. Az, hogy szmomra mihez hasonlt egy felh, mg a vals letben is meglehetsen fgg attl, hogyan nzem: puszta szemmel, polrszrn keresztl vagy meteorolgiai radarral. A nzn s a felhn kvl lehet, hogy valamilyen ltalnos htteret is figyelembe kell venni, pldul a nap viszonylagos helyzett. Tovbb bonyoltja a dolgot, ha egyb objektumokat is ms felhket, replgpeket szmtsba vesznk. Hogy a tervez lett igazn megneheztsk, adjuk hozz azt a lehetsget is, hogy egyszerre tbb nznk van. A harmadik megolds, hogy hagyjuk a felht s a tbbi objektumot (a replgpeket s a napot), hogy lerjk magukat a nzknek. E megolds elg ltalnos ahhoz, hogy a legtbb clnak megfeleljen, de jelents rat fizethetnk rte, mert a program tlsgosan bonyolult s lass lehet. Hogyan rtetjk meg pldul a nzvel a felhk s ms objektumok ltal ksztett lersokat? A programokban nem srn fordulnak el esfelhk (br a plda kedvrt lsd 15.2), de a klnbz I/O mveletekbe szksgszeren bevont objektumok is hasonlan viselkednek. Ez teszi a felh pldt tallv a programok s klnsen a knyvtrak tervezsre nzve. Logikailag hasonl plda C++ kdjt mutattam be az adatfolyamok be- s kimeneti rendszernek trgyalsakor, a formzott kimenetre hasznlt mdostknl (manipulator, 21.4.6, 21.4.6.3). Le kell azonban szgeznnk, hogy a harmadik megolds nem a helyes, csupn a legltalnosabb megolds. A terveznek mrlegelnie kell a rendszer klnbz szksgleteit, hogy megvlassza az adott problmhoz az adott rendszerben megfelel mrtk ltalnossgot s elvont brzolsmdot. Alapszably, hogy egy hossz letnek tervezett programban az elvonatkoztatsi szintnek a mg megrthet s megengedhet legltalnosabbnak, nem az abszolt legltalnosabbnak kell lennie. Ha az ltalnosts tlhaladja az adott program kereteit s a ksztk tapasztalatt, kros lehet, mert ksedelmet, elfogadhatatlanul rossz hatkonysgot, kezelhetetlen terveket s nyilvnval kudarcot von maga utn.

Forrs: http://www.doksi.hu

946

Tervezs a C++ segtsgvel

Az ilyen mdszerek kezelhetv s gazdasgoss ttelhez az jrahasznosts mikntjt is meg kell terveznnk (23.5.1) s nem szabad teljesen elfeledkezni a hatkonysgrl (23.4.7).

23.4.3. Tervezsi lpsek


Vegyk azt az esetet, amikor egyetlen osztlyt terveznk. Ez ltalban nem j tlet. Fogalmak nem lteznek elszigetelten; ms fogalmakkal val sszefggseik hatrozzk meg azokat. Ugyanez rvnyes az osztlyokra is; meghatrozsuk logikailag kapcsold osztlyok meghatrozsval egytt trtnik. Jellemzen egymssal kapcsolatban lv osztlyok egy halmazn dolgozunk. Az ilyen halmazt gyakran osztlyknyvtrnak (class library) vagy sszetevnek (komponens, component) nevezzk. Nha az adott sszetevben lv sszes osztly egyetlen osztlyhierarchit alkot, nha egyetlen nvtr tagjai, nha viszont csupn deklarcik ad hoc gyjtemnyt kpezik (24.4). Az egy sszetevben lv osztlyok halmazt logikai ktelkek egyestik; kzs stlusuk van vagy azonos szolgltatsokra tmaszkodnak. Az sszetev teht a tervezs, a dokumentci, a tulajdonls s az jrafelhasznls egysge. Ez nem jelenti azt, hogy ha valakinek egy sszetevbl csak egy osztlyra van szksge, rtenie s hasznlnia kell az sszetev minden osztlyt vagy be kell tltenie a programjba az sszes osztly kdjt. ppen ellenkezleg, ltalban arra treksznk, hogy az osztlyoknak a lehet legkevesebb gpi s emberi erforrsra legyen szksgk. Ahhoz azonban, hogy egy sszetev brmely rszt hasznlhassuk, rtennk kell az sszetevt sszetart s meghatroz logikai kapcsolatokat, (a dokumentciban ezek remlhetleg elgg vilgosak), a felptsben s dokumentcijban megtesteslt elveket s stlust, illetve a kzs szolgltatsokat (ha vannak). Lssuk teht, hogyan tervezhetnk meg egy sszetevt. Mivel ez ltalban komoly kihvst jelent, megri lpsekre bontani, hogy knnyebb legyen a klnbz rszfeladatokra sszpontostani. Szokott mdon, az sszetevk megtervezsnek sincs egyetlen helyes tja. Itt csupn egy olyan lpssorozatot mutatok be, amely egyesek szmra mr bevlt: 1. Keressk meg a fogalmakat/osztlyokat s legalapvetbb kapcsolataikat. 2. Finomtsuk az osztlyokat a rajtuk vgezhet mveletek meghatrozsval. Osztlyozzuk a mveleteket. Klnsen figyeljnk a ltrehozs, a msols s a megsemmists szksgessgre. Trekedjnk a minl kisebb mretre, a teljessgre s a knyelemre. 3. Finomtsuk az osztlyokat az sszefggsek meghatrozsval. gyeljnk a paramterezsre s az rklsre, s hasznljuk ki a fggsgeket.

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

947

4. Hatrozzuk meg a felleteket. Vlasszuk szt a fggvnyeket privt s vdett mveletekre. A mveleteket hatrozzuk meg pontosan az adott osztlyokra. szrevehetjk, hogy ezek is egy ismtld folyamat lpsei. ltalban tbbszr kell rajtuk vgigmenni, hogy knyelmesen hasznlhat tervet kapjunk, melyet felhasznlhatunk egy els vagy egy jabb megvalstsnl. Ha az itt lertak szerinti elemezzk a tennivalkat s ksztjk el az elvont adatbrzolst, az azzal az elnnyel jr, hogy viszonylag knny lesz az osztlykapcsolatok trendezse a kd megrsa utn is. (Br ez sohasem tl egyszer.) Ha vgeztnk, elksztjk az osztlyokat s visszatrnk fellvizsglni a tervet annak alapjn, amit a megvalsts sorn megtudtunk. A kvetkezkben ezeket a lpseket egyenknt vesszk sorra.

23.4.3.1. Els lps: keressnk osztlyokat Keressk meg a fogalmakat/osztlyokat s legalapvetbb kapcsolataikat. A j tervezs kulcsa a valsg valamely rsznek kzvetlen modellezse vagyis a program fogalmainak osztlyokra val lekpezse, az osztlyok kzti kapcsolatok brzolsa meghatrozott mdon (pldul rklssel), s mindezek ismtelt vgrehajtsa klnbz elvonatkoztatsi szinteken. De hogyan fogjunk hozz a fogalmak megkeresshez? Milyen megkzeltst clszer kvetni, hogy eldnthessk, mely osztlyokra van szksg? A legjobb, ha elszr magban a programban nznk szt, ahelyett, hogy a szmtgptuds fogalomzskjban keresglnnk. Hallgassunk valakire, aki a rendszer elkszlte utn annak szakrt felhasznlja lesz, s valakire, aki a kivltand rendszer kiss elgedetlen felhasznlja. Figyeljk meg, milyen szavakat hasznlnak. Gyakran mondjk, hogy a fnevek fognak megfelelni a programhoz szksges osztlyoknak s objektumoknak; s ez tbbnyire gy is van. Ezzel azonban semmikppen sincs vge a trtnetnek. Az igk jelenthetik az objektumokon vgzett mveleteket, a hagyomnyos (globlis) fggvnyeket, melyek paramtereik rtkei vagy osztlyok alapjn j rtkeket lltanak el. Az utbbira pldk a fggvnyobjektumok (function object, 18.4) s a mdostk (manipulator, 21.4.6). Az olyan igk, mint a bejr vagy a vglegest brzolhatk egy bejr (itertor) objektummal, illetve egy adatbzis commit mvelett vgrehajt objektummal. Mg a mellknevek is gyakran hasznlhat mdon brzolhatk osztlyokkal. Vegyk a trolhat, prhuzamosan fut, bejegyzett s lekttt mellkneveket. Ezek is lehetnek osztlyok, melyek lehetv teszik a terveznek vagy a programoznak, hogy virtulis bzisosztlyok meghatrozsa ltal kivlassza a ksbb megtervezend osztlyok kvnt jellemzit (15.2.4).

Forrs: http://www.doksi.hu

948

Tervezs a C++ segtsgvel

Nem minden osztly felel meg programszint fogalmaknak. Vannak pldul rendszer-erforrsokat vagy a megvalsts fogalmait brzol osztlyok (24.3.1) is. Az is fontos, hogy elkerljk a rgi rendszer tl kzeli modellezst. Pldul biztosan nem akarunk olyan rendszert kszteni, mely egy adatbzis kr plve egy kzi rendszer lehetsgeit utnozza s az egynnek csak paprok fizikai tologatsnak irnytst engedi meg. Az rkls (inheritance) fogalmak kzs tulajdonsgainak brzolsra szolgl. Legfontosabb felhasznlsa a hierarchikus felpts brzolsa az egyes fogalmakat kpvisel osztlyok viselkedse alapjn (1.7, 12.2.6, 24.3.2). Erre nha gy hivatkoznak, mint osztlyozs (classification), st rendszertan (taxonomy). A kzs tulajdonsgokat aktvan keresni kell. Az ltalnosts s az osztlyozs magas szint tevkenysgek, melyek lesltst ignyelnek, hogy hasznos s tarts eredmnyt adjanak. Egy kzs alapnak egy ltalnosabb fogalmat kell brzolnia, nem pedig egy hasonl, az brzolshoz esetleg kevesebb adatot ignylt. Vegyk szre, hogy az osztlyozst a rendszerben modellezett fogalmak alapjn kell vgezni, nem ms terleteken rvnyes szemszgbl. A matematikban pldul a kr az ellipszis egy fajtja, de a legtbb programban a krt nem az ellipszisbl s az ellipszist sem a krbl szrmaztatjk. Azok a gyakran hallhat rvek, hogy azrt, mert a matematikban ez a mdja s azrt, mert a kr az ellipszis rszhalmaza, nem meggyzek s ltalban rosszak. Ennek az az oka, hogy a legtbb programban a kr f tulajdonsga az, hogy kzppontja van s tvolsga a kerletig rgztett. A kr minden viselkedse (minden mvelete) meg kell, hogy tartsa ezt a tulajdonsgot (invarins; 24.3.7.1). Msrszt az ellipszist kt fkuszpont jellemzi, melyeket sok programban egymstl fggetlenl lehet mdostani. Ha ezek a fkuszpontok egybeesnek, az ellipszis olyan lesz, mint egy kr, de nem kr, mivel a mveletei nem tartjk krnek. A legtbb rendszerben ez a klnbsg gy tkrzdik, hogy van egy kr s egy ellipszis, amelyeknek vannak mvelethalmazaik, de azok nem egyms rszhalmazai. Nem az trtnik, hogy kiagyalunk egy osztlyhalmazt az osztlyok kztti kapcsolatokkal s felhasznljuk azokat a vgs rendszerben. Inkbb osztlyok s kapcsolatok kezdeti halmazt alkotjuk meg, majd ezeket ismtelten finomtjuk (23.4.3.5), hogy eljussunk egy olyan osztlykapcsolat-halmazig, mely elgg ltalnos, rugalmas s stabil ahhoz, hogy valdi segtsget nyjtson a rendszer ksbbi megalkotshoz. A kezdeti f fogalmak/osztlyok felvzolsra a legjobb eszkz egy tbla, finomtsukra pedig az alkalmazsi terlet szakrtivel s nhny bartunkkal folytatott vita, melynek sorn kialakthatunk egy hasznlhat kezdeti sztrat s fogalmi vzlatot. Kevs ember tudja ezt egyedl elvgezni. A kiindul osztlyhalmazbl valban hasznlhat osztlyhalmaz kialaktsnak egyik mdja a rendszer utnzsa, ahol a tervezk veszik t az osztlyok szerept.

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

949

A nyilvnos vita feltrja a kezdeti tletek hibit, ms megoldsok keressre sztnz s elsegti a kialakul terv kzs megrtst. A tevkenysg lapokra rott feljegyzsekkel tmogathat, amelyek ksbb visszakereshetk. Az ilyen krtykat rendszerint CRC krtyknak nevezik (Class, Responsibility, Collaborators, vagyis Osztlyok, Felelssg, Egyttmkdk, [Wirfs-Brock, 1990]) a rjuk feljegyzett informcik miatt. A hasznlati eset (use case) a rendszer egy konkrt felhasznlsnak lersa. me a hasznlati eset egyszer pldja egy telefonrendszerre: felvesszk a kagylt, trcszunk egy szmot, a telefon a msik oldalon csenget, a msik oldalon felveszik a kagylt. Ilyen hasznlati esetek halmaznak meghatrozsa hatalmas rtk lehet a fejleszts minden szakaszban, a tervezs elejn pedig a hasznlati esetek megkeresse segt, hogy megrtsk, mit is prblunk pteni. A tervezs alatt nyomon kvethetjk velk a rendszer mkdsi tjt (pldul CRC krtyk hasznlatval), hogy ellenrizzk, van-e rtelme a felhasznl szempontjbl a rendszer osztlyokkal s objektumokkal val viszonylag statikus lersnak; a programozs s tesztels alatt pedig lehetsges helyzeteket modellezhetnk velk. Ily mdon a hasznlati eseteket a rendszer valszersgnek ellenrzsre hasznlhatjuk. A hasznlati esetek a rendszert (dinamikusan) mkd egysgknt tekintik. Ezrt a tervezt abba a csapdba ejthetik, hogy a rendszer rendeltetst tartsa szem eltt, ami elvonja a figyelmt az osztlyokkal brzolhat, hasznlhat fogalmak keressnek elengedhetetlen feladattl. Klnsen azok, akiknek a rendszerezett elemzs erssgk, de az objektumorientlt programozsban s tervezsben kevs tapasztalattal rendelkeznek, a hasznlati esetek hangslyozsval hajlamosak az eszkzket a clnak alrendelni. A hasznlati esetek halmaza mg nem tekinthet tervnek. Nem elg a rendszer hasznlatt szem eltt tartani, gondolni kell annak felptsre is. A tervezk csapata eredenden hibaval s kltsges ksrlet csapdjba eshet, ha az sszes hasznlati esetet meg akarja keresni s le akarja rni. Amikor a rendszerhez osztlyokat keresnk, ugyangy eljn az id, amikor ki kell mondani: Ami sok, az sok. Itt az id, hogy kiprbljuk, amink van s lssuk, mi trtnik. A tovbbi fejleszts sorn csak egy elfogadhat osztlyhalmaz s egy elfogadhat hasznlatieset-halmaz hasznlatval kaphatjuk meg a j rendszer elrshez elengedhetetlen visszajelzst. Persze nehz felismerni, mikor lljunk meg egy hasznos tevkenysgben, klnsen akkor, ha tudjuk, hogy ksbb vissza kell trnnk a feladatot befejezni. Mennyi eset elegend? Erre a krdsre ltalban nem lehet vlaszt adni. Az adott munka sorn azonban eljn az id, amikor a rendszer f szolgltatsainak legtbbje mkdik s a szokatlanabb problmk, illetve a hibakezelsi krdsek j rszt is rintettk. Ekkor ideje elvgezni a tervezs s programozs kvetkez lpst.

Forrs: http://www.doksi.hu

950

Tervezs a C++ segtsgvel

Amikor a program felhasznlsi terleteit hasznlati esetek segtsgvel prbljuk felbecslni, hasznos lehet azokat elsdleges s msodlagos hasznlati esetekre sztvlasztani. Az elsdlegesek a rendszer legltalnosabb, normlis tevkenysgeit, a msodlagosak a szokatlanabb, illetve a hibakezelssel kapcsolatos tevkenysgeket rjk le. A msodlagos hasznlati esetre plda a telefonhvs egy vltozata, ahol a hvott lloms kagyljt felemeltk, mert ppen a hv szmt trcszzk. Gyakran mondjk, hogy ha az elsdleges hasznlati esetek 80%-t s nhny msodlagosat mr kiprbltunk, ideje tovbbmenni, de mivel elre nem tudhatjuk, mi alkotja az sszes esetet, ez csupn irnyelv. Itt a tapasztalat s a j rzk szmt. A fogalmak, mveletek s kapcsolatok a programok alkalmazsi terleteibl termszetszeren addnak vagy az osztlyszerkezeten vgzett tovbbi munkbl szletnek. Ezek jelzik, hogy alapveten rtjk a program mkdst s kpesek vagyunk az alapfogalmak osztlyozsra. Pldul a tzoltkocsi egy tzolt gp, ami egy teheraut, ami egy jrm. A 23.4.3.2 s 23.4.5 pontok bemutatnak nhny mdszert, amelyekkel az osztlyokra s osztlyhierarchikra a tovbbfejleszts szndkval nzhetnk. vakodjunk a rajzos tervezstl! Termszetesen a fejleszts bizonyos szakaszban megkrnek majd, hogy mutassuk be a tervet valakinek, s akkor bemutatunk egy sereg diagramot, melyek az pl rendszer felptst magyarzzk. Ez nagyon hasznos lehet, mert segt, hogy figyelmnket arra sszpontostsuk, ami a rendszerben fontos, s knytelenek vagyunk tleteinket msok ltal is rthet szavakkal kifejezni. A bemutat rtkes tervezsi eszkz. Elksztse azzal a cllal, hogy valban megrtsk azok, akiket rdekel s akik kpesek pt brlatot alkotni, j gyakorlat a gondolatok megfogalmazsra s tiszta kifejezsre. A terv hivatalos bemutatsa azonban veszlyes is lehet, mivel ers a ksrts, hogy az idelis rendszert mutassuk be olyat, amit brcsak meg tudnnk pteni, egy rendszert, melyre a vezetsg vgyik ahelyett, amivel rendelkeznk, s amit elfogadhat id alatt meg tudunk csinlni. Amikor klnbz megkzeltsek vetlkednek s a dntshozk nem igazn rtik a rszleteket (vagy nem is trdnek azokkal), a bemutatk hazugsgversenny vlnak, amelyben a leggrandizusabb rendszert bemutat csapat nyeri el a munkt. Ilyen esetekben a gondolatok vilgos kifejezst gyakran szakzsargon s rvidtsek helyettestik. Ha ilyen bemutatt hallgatunk s klnsen, ha dntshozk vagyunk s fejlesztsi erforrsokrl rendelkeznk ltfontossg, hogy kpesek legynk klnbsget tenni vgylom s relis terv kztt. A j bemutatanyag nem garantlja a lert rendszer minsgt. Tapasztalatom szerint a vals problmkra sszpontost cgek, amikor eredmnyeik bemutatsra kerl sor, rvidre fogjk a szt azokhoz kpest, akik kevsb rdekeltek vals rendszerek elksztsben.

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

951

Amikor osztlyokkal brzoland fogalmakat keresnk, vegyk szre, hogy a rendszernek vannak olyan fontos tulajdonsgai is, melyeket nem brzolhatunk osztlyokkal. A megbzhatsg, a teljestmny, a tesztelhetsg fontos mrhet rendszertulajdonsgok. Mgsem lehet mg a legtisztbban objektumokra alapozott rendszer megbzhatsgt sem egy megbzhatsgi objektummal brzolni. A rendszerben mindentt jelenlev tulajdonsgok meghatrozhatk, megtervezhetk s mrssel igazolhatk; gondot kell fordtani rjuk minden osztlynl, s tkrzdnik kell az egyes osztlyok s sszetevk tervezsi s megvalstsi szablyaiban (23.4.3).

23.4.3.2. Msodik lps: hatrozzuk meg a mveleteket Finomtsuk az osztlyokat a rajtuk vgezhet mveletek meghatrozsval. Termszetes, hogy nem lehet az osztlyok keresst elvlasztani attl, hogy kigondoljuk, milyen mveleteket kell rajtuk vgezni. Gyakorlati klnbsg van azonban abban, hogy az osztlyok keressekor a f fogalmakra sszpontostunk s tudatosan httrbe szortjuk a szmtstechnikai szempontokat, mg a mveletek meghatrozsakor azt tartjuk szem eltt, hogy teljes s hasznlhat mvelethalmazt talljunk. ltalban tl nehz egyszerre mindkettre gondolni, klnsen azrt, mert az egymssal kapcsolatban lv osztlyokat egytt kell megtervezni, de ebben is segthetnek a CRC krtyk (23.4.3.1). Amikor az a krds, milyen szolgltatsokrl gondoskodjunk, tbbfle megkzelts lehetsges. n a kvetkez stratgit javaslom: 1. Gondoljuk vgig, hogyan jn ltre, hogyan msoldik (ha egyltaln msoldik) s hogyan semmisl meg az osztly egy objektuma. 2. Hatrozzuk meg az osztly ltal ignyelt mveletek minimlis halmazt. ltalban ezek lesznek a tagfggvnyek. 3. Gondoljuk vgig, milyen mveleteket lehetne mg megadni a knyelmesebb jells rdekben. Csak nhny valban fontos mveletet adjunk meg. Ezekbl lesznek a nem tag segdfggvnyek (10.3.2). 4. Gondoljuk vgig, mely mveleteknek kell virtulisaknak lennik, vagyis olyan mveleteknek, melyekkel az osztly felletknt szolglhat egy szrmaztatott osztly ltal adott megvalsts szmra. 5. Gondoljuk vgig, milyen kzs elnevezsi mdszert s szolgltatsokat biztosthatunk egy sszetev minden osztlyra vonatkozan. Vilgos, hogy ezek a minimalizmus elvei. Sokkal knnyebb minden hasznosnak gondolt fggvnyt megadni s minden mveletet virtuliss tenni. Minl tbb fggvnynk van azonban, annl valsznbb, hogy nem fogjk hasznlni azokat, s hogy a fls fggv-

Forrs: http://www.doksi.hu

952

Tervezs a C++ segtsgvel

nyek korltozni fogjk a rendszer megvalstst, tovbbi fejldst. Nevezetesen azok a fggvnyek, melyek egy osztly egy objektuma llapotnak valamely rszt kzvetlenl olvassk vagy rjk, gyakran egyedi megvalstst kvetelnek az osztlytl s komoly mrtkben korltozzk az jratervezs lehetsgt. Az ilyen fggvnyek az elvont brzolst a fogalom szintjrl a megvalsts szintjre szlltjk le. A fggvnyek hozzadsa a programoznak is tbb munkt jelent s a terveznek is, a kvetkez jratervezskor. Sokkal knnyebb egy fggvnyt megadni, amikor vilgoss vlt, hogy szksg van r, mint eltvoltani azt, ha koloncc vlik. Annak oka, hogy egy fggvnyrl vilgosan el kell dnteni, hogy virtulis legyen vagy sem, s ez nem alaprtelmezett viselkeds vagy megvalstsi rszlet, az, hogy egy fggvny virtuliss ttele alapjaiban befolysolja osztlynak hasznlatt s az osztly kapcsolatait ms osztlyokkal. Egy olyan osztly objektumainak elrendezse, melyben akr csak egyetlen virtulis fggvny is van, jelents mrtkben klnbzik a C vagy Fortran nyelvek objektumaitl. A virtulis fggvnyt is tartalmaz osztlyok felletet nyjthatnak a ksbb definiland osztlyok szmra, de egy virtulis fggvny egyben fggst is jelent azoktl (24.3.2.1). Vegyk szre, hogy a minimalizmus inkbb tbb, mint kevesebb munkt kvetel a terveztl. Amikor kivlasztjuk a mveleteket, fontos, hogy inkbb arra sszpontostsunk, mit kell tenni, nem pedig arra, hogyan tegyk. Vagyis inkbb a kvnt viselkedsre, mint a megvalsts krdseire figyeljnk. Nha hasznos az osztlyok mveleteit aszerint osztlyozni, hogyan hasznljk az objektumok bels llapott: Alapvet mveletek: konstruktorok, destruktorok s msol opertorok. Lekrdezsek: mveletek, melyek nem mdostjk egy objektum llapott. Mdostk: mveletek, melyek mdostjk egy objektum llapott. Konverzik: mveletek, melyek ms tpus objektumot hoznak ltre annak az objektumnak az rtke (llapota) alapjn, amelyre alkalmazzuk ket. Bejrk: opertorok, melyek a tartalmazott objektumsorozatokhoz val hozzfrst, illetve azok hasznlatt teszik lehetv. A fentiek nem egymst kizr kategrik. Egy bejr pldul lehet egyben lekrdez vagy mdost is. Ezek a kategrik egyszeren egy osztlyozst jelentenek, mely segthet az osztlyfelletek megtervezsben. Termszetesen lehetsgesek ms osztlyozs is. Az ilyen osztlyozsok klnsen hasznosak az sszetevkn belli osztlyok egysgessgnek fenntartsban.

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

953

A C++ a lekrdezk s mdostk megklnbztetst a konstans s nem konstans tagfggvnyekkel segti. Ugyangy a konstruktorok, destruktorok, msol opertorok s konverzis fggvnyek is kzvetlenl tmogatottak.

23.4.3.3. Harmadik lps: hatrozzuk meg az sszefggseket Finomtsuk az osztlyokat az sszefggsek meghatrozsval. A klnfle sszefggseket a 24.3 trgyalja. A tervezssel kapcsolatosan vizsgland f sszefggsek a paramterezsi, rklsi (inheritance) s hasznlati (use) kapcsolatok. Mindegyiknl meg kell vizsglni, mit jelent, hogy az osztly a rendszer egyetlen tulajdonsgrt felels. Felelsnek lenni biztos, hogy nem azt jelenti, hogy az osztly az sszes adatot maga kell, hogy tartalmazza, vagy hogy az sszes szksges mveletet kzvetlenl tagfggvnyei kell, hogy vgrehajtsk. Ellenkezleg: az, hogy minden osztlynak egyetlen felelssgi terlete van, azt biztostja, hogy egy osztly munkja jobbra abbl ll, hogy a krseket egy msik osztlyhoz irnytja, mely az adott rszfeladatrt felels. Legynk azonban vatosak, mert e mdszer tlzott mrtk hasznlata kis hatkonysg s ttekinthetetlen tervekhez vezet, azltal, hogy olyan mrtkben elburjnzanak az osztlyok s objektumok, hogy ms munka nem trtnik, mint a krsek tovbbtsa. Amit az adott helyen s idben meg lehet tenni, azt meg kell tenni. Annak szksgessge, hogy az rklsi s hasznlati kapcsolatokat a tervezsi szakaszban vizsgljuk (s nem csak a megvalsts sorn), kzvetlenl kvetkezik abbl, hogy az osztlyokat fogalmak brzolsra hasznljuk. Ez pedig azt is magban foglalja, hogy nem az egyedi osztly, hanem a komponens (23.4.3, 24.4) a tervezs valdi egysge. A paramterezs mely gyakran sablonok (template) hasznlathoz vezet egy md arra, hogy a mgttes fggseket nyilvnvalv tegyk, gy, hogy j fogalmak hozzadsa nlkl tbb lehetsges megolds legyen. Gyakran vlaszthatunk, meghagyunk-e valamit krnyezettl fgg egy rklsi fa gaknt brzolt elemknt, vagy inkbb paramtert hasznlunk (24.4.1).

23.4.3.4. Negyedik lps: hatrozzuk meg a felleteket Hatrozzuk meg a felleteket. A privt fggvnyekkel a tervezsi szakaszban nem kell foglalkozni. Azt, hogy ekkor a megvalsts mely krdseit vesszk figyelembe, legjobb a harmadik lpsben, a fggsi viszonyok meghatrozsakor eldnteni. Vilgosabban kifejezve: vegyk alapszablynak, hogy ha egy osztlynak nem lehetsges legalbb kt jelentsen eltr megvalstsa, akkor valszn, hogy ezzel az osztllyal valami nincs rendben, s valjban lczott megvalstssal (implementation), nem pedig igazi fogalommal llunk

Forrs: http://www.doksi.hu

954

Tervezs a C++ segtsgvel

szemben. Sok esetben az Elgg fggetlen ennek az osztlynak a fellete a megvalsts mdjtl? krds j megkzeltse, ha megvizsgljuk, alkalmazhat-e az osztlyra valamilyen takarkos kirtkelsi mdszer (lusta kirtkels, lazy evaluation). Vegyk szre, hogy a nyilvnos (public) alaposztlyok s bart fggvnyek (friend) az osztly nyilvnos felletnek rszei (lsd mg 11.5 s 24.4.2). Kifizetd lehet, ha kln felletekrl gondoskodunk az rkls, illetve az ltalnos felhasznlk szmra, azltal, hogy kln protected s public felleteket adunk meg. Ez az a lps, amelyben a paramterek pontos tpust meghatrozzuk. Az az idelis, ha annyi statikus, programszint tpusokat tartalmaz felletnk van, amennyi csak lehetsges (24.2.3 s 24.4.2). Amikor a felleteket (interface) meghatrozzuk, vigyzni kell azoknl az osztlyoknl, ahol a mveletek ltszlag tbb fogalmi szintet tmogatnak. A File osztly egyes tagfggvnyei pldul File_descriptor (fjller) tpus paramtereket kaphatnak, msok pedig karakterlnc paramtereket, melyek fjlneveket jellnek. A File_descriptor mveletek ms fogalmi szinten dolgoznak, mint a fjlnv-mveletek, gy elgondolkozhatunk azon, vajon ugyanabba az osztlyba tartoznak-e. Lehet, hogy jobb lenne, ha kt fjlosztlyunk lenne, az egyik a fjller fogalmnak tmogatsra, a msik a fjlnv fogalomhoz. ltalban egy osztly minden mvelete ugyanazt a fogalmi szintet kell, hogy tmogassa. Ha nem gy van, t kell szervezni az osztlyt s a vele kapcsolatban lv osztlyokat.

23.4.3.5. Osztlyhierarchik jraszervezse Az els s a harmadik lpsben megvizsgltuk az osztlyokat s osztlyhierarchikat, hogy lssuk, megfelelen kiszolgljk-e ignyeinket. A vlasz ltalban nem s ilyenkor t kell szerveznnk, osztlyainkat, hogy tkletestsk a szerkezetet, a tervet vagy ppen a megvalsts mdjt. Az osztlyhierarchia legkznsgesebb tszervezsi mdszere kt osztly kzs rsznek kiemelse egy j osztlyba, illetve az osztly kt j osztlyra bontsa. Mindkt esetben hrom osztly lesz az eredmny: egy bzisosztly (base class) s kt szrmaztatott osztly (derived class). Mikor kell gy jraszervezni? Mi jelzi azt, hogy egy ilyen jraszervezs hasznos lehet? Sajnos ezekre a krdsekre nincsenek egyszer, ltalnos vlaszok. Ez nem igazn meglep, mert amirl beszlnk, nem apr rszletek, hanem egy rendszer alapfogalmainak mdostsa. Az alapvet br nem egyszer feladat az osztlyok kzs tulajdonsgainak megkeresse s a kzs rsz kiemelse. Azt, hogy mi szmt kzsnek, nem lehet pontosan meghatrozni, de a kzs rsznek a fogalmak kzti, s nem csak a megvalsts knyelmt szolgl kzssget kell tkrznie. Arrl, hogy kt vagy tbb osztly olyan kzs tu-

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

955

lajdonsgokkal rendelkezik, melyeket egy bzisosztlyban foglalhatunk ssze, a kzs hasznlati minta, a mvelethalmazok hasonlsga, illetve a hasonl megvalsts rulkodik, valamint az, hogy ezek az osztlyok gyakran egytt merlnek fel a tervezsi vitk sorn. Ezzel szemben egy osztly valsznleg kt msikra bonthat, ha mveleteinek rszhalmazai eltr hasznlati mintjak, ha ezen rszhalmazok az brzols kln rszhalmazaihoz frnek hozz, vagy ha az osztly egymssal nyilvnvalan nem kapcsolatos tervezsi vitk sorn merl fel. A rokon osztlyok halmaznak sablonba (template) foglalsa rendszerezett mdot ad a szksges alternetvk biztostsra (24.4.1). Az osztlyok s fogalmak kzti szoros kapcsolatok miatt az osztlyhierarchik szervezsi problmi gyakran mint az osztlyok elnevezsi problmi merlnek fel. Ha az osztlynevek nehzkesen hangzanak vagy az osztlyhierarchikbl kvetkez osztlyozsok tlsgosan bonyolultak, valsznleg itt az alkalom, hogy tkletestsk a hierarchikat. Vlemnyem szerint kt ember sokkal jobban tud egy osztlyhierarchit elemezni, mint egy. Ha trtnetesen nincs valaki, akivel egy tervet meg tudnnk vitatni, hasznos lehet, ha runk egy bevezet tervismertett, amelyben az osztlyok neveit hasznljuk. A terv egyik legfontosabb clkitzse olyan felletek (interface) biztostsa, melyek a vltozsok sorn llandak maradnak (23.4.2). Ezt gyakran gy rhetjk el legjobban, ha egy osztlyt, amelytl szmos osztly s fggvny fgg, olyan absztrakt osztlly tesznk, mely csak nagyon ltalnos mveleteket biztost. A rszletesebb mveletek jobb, ha egyedi cl szrmaztatott osztlyokhoz kapcsoldnak, melyektl kevesebb osztly s fggvny fgg kzvetlenl. Vilgosabban: minl tbb osztly fgg egy osztlytl, annl ltalnosabbnak kell lennie. Ers a ksrts, hogy egy sokak ltal hasznlt osztlyhoz mveleteket (s adatokat) tegynk hozz. Ezt gyakran gy tekintik, mint ami egy osztlyt hasznlhatbb s (tovbbi) vltoztatst kevsb ignylv tesz. Pedig az eredmny csak annyi, hogy a fellet meghzik (kvr fellet, 24.4.3) s olyan adattagjai lesznek, melyek szmos gyengn kapcsold szolgltatst tmogatnak. Ez ismt azzal jr, hogy az osztlyt mdostani kell, amikor az ltala tmogatott osztlyok kzl akr csak egy is jelentsen megvltozik. Ez viszont magval vonja olyan felhasznli osztlyok s szrmaztatott osztlyok mdostst is, melyek nyilvnvalan nincsenek vele kapcsolatban. Ahelyett, hogy bonyoltannk egy, a tervben kzponti szerepet jtsz osztlyt, rendszerint ltalnosnak s elvontnak kellene hagynunk. Ha szksges, az egyedi szolgltatsokat szrmaztatott osztlyokknt kell biztostani. (Pldkat lsd [Martin,1995]). Ez a gondolatfzr absztrakt (abstract) osztlyok hierarchijhoz vezet, ahol a gykrhez kzeli osztlyok a legltalnosabbak, s ezektl fgg a legtbb ms osztly s fggvny. A levl osztlyok a legegyedibbek s csak nagyon kevs kdrsz fgg kzvetlenl tlk. Pldaknt emlthetnnk az lval_box hierarchia vgs vltozatt (12.4.3, 12.4.4).

Forrs: http://www.doksi.hu

956

Tervezs a C++ segtsgvel

23.4.3.6. Modellek hasznlata Amikor cikket rok, egy megfelel modellt prblok kvetni. Vagyis ahelyett, hogy azonnal elkezdem a gpelst, hasonl tmj cikkeket keresek, hogy lssam, tallok-e olyat, amely a cikkem kezdeti mintjaknt szolglhat. Ha az ltalam vlasztott modell egy sajt, rokon tmrl szl cikk, mg arra is kpes vagyok, hogy helykn hagyok szvegrszeket, msokat szksg szerint mdostok s j informcit csak ott teszek hozz, ahol mondandm logikja megkvnja. Ez a knyv pldul ilyen mdon rdott, az els s msodik kiads alapjn. Ennek a mdszernek szlssges esete egy levlsablon hasznlata. Ekkor egyszeren egy nevet rok be s esetleg hozzrok mg nhny sort, hogy a levl szemlyre szl legyen. Az ilyen leveleket lnyegben gy rom meg, hogy a sablont csak az alapmodelltl eltr rszekkel egsztem ki. A ltez rendszerek ilyen, j rendszerek modelljeiknt val felhasznlsa ltalnos eljrs az alkot trekvsek minden formjnl. Amikor csak lehetsges, a tervezs s programozs elz munkra kell, hogy alapozdjon. Ez korltozza a tervez szabadsgt, de lehetv teszi, hogy figyelmt egyszerre csak nhny krdsre sszpontostsa. Egy nagy projekt tiszta lappal indtsa frisst hatssal lehet, de knnyen mrgezv is vlhat, mert a tervezsi mdszerek kztti kvlygst eredmnyezheti. Ennek ellenre, ha van modellnk, az nem jelent korltozst s nem kveteli meg, hogy szolgai mdon kvessk; egyszeren megszabadtja a tervezt attl, hogy egy tervet egyszerre csak egy szempontbl kzelthessen meg. Vegyk szre, hogy a modellek hasznlata elkerlhetetlen, mivel minden terv tervezinek tapasztalataibl tevdik ssze. Egy adott modell birtokban a modellvlaszts tudatos elhatrozss vlik, felttelezseink megalapozottak, a hasznlt kifejezsek kre pedig meghatrozott lesz, a tervnek lesz egy kezdeti vza s n a valsznsge, hogy a tervezk meg tudnak egyezni egy kzs megkzeltsben. Termszetesen a kiindul modell kivlasztsa nmagban is fontos tervezsi dnts, amely gyakran csak a lehetsges modellek keresse s az alternatvk gondos kirtkelse utn hozhat meg. Tovbb sok esetben egy modell csak akkor felel meg, ha megrtjk, hogy az elkpzelseknek egy j konkrt alkalmazshoz val igaztsa szmos mdostst ignyel. A programtervezs nem knny, gy minden megszerezhet segtsgre szksgnk van. Nem szabad visszautastanunk a modellek hasznlatt, csak a rossz utnzs erltetst. Az utnzs a hzelgs legszintbb mdja, s a modellek s elz munkk sztnzsknt val felhasznlsa a tulajdon s a msols jognak trvnyes hatrain bell az innovatv munka minden terleten elfogadhat mdszer: ami j volt Shakespeare-nek, az j neknk is. Vannak, akik a modellek ilyen hasznlatt a tervezsben terv-jrahasznostsnak nevezik.

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

957

Az ltalnosan hasznlt, tbb programban elfordul elemek feljegyzse az ltaluk megoldott problma s hasznlatuk feltteleinek lersval egytt kzenfekv tlet; feltve, hogy esznkbe jut. Az ilyen ltalnos s hasznos tervezsi elemek lersra ltalban a minta (pattern) szt hasznljuk, a mintk dokumentlsnak s hasznlatnak pedig komoly irodalma van (pl. [Gamma, 1994] s [Coplien, 1995]). Clszer, hogy a tervez ismerje az adott alkalmazsi terlet npszer mintit. Mint programoz, olyan mintkat rszestek elnyben, melyekhez valamilyen kd is tartozik, ami pldaknt tekinthet. Mint a legtbb ember, egy ltalnos tletet (ebben az esetben mintt) akkor tartok a legjobbnak, ha segtsgemre van egy konkrt plda is (ebben az esetben egy, a minta hasznlatt illusztrl kdrszlet). Akik gyakran hasznlnak mintkat, szakzsargont hasznlnak egyms kztt, ami sajnos privt nyelvv vlhat, ami kvlllk szmra lehetetlenn teszi a megrtst. Mint mindig, elengedhetetlen a megfelel kommunikci biztostsa a projekt klnbz rszeiben rsztvevk (23.3) s ltalban a tervez s programoz kzssgek kztt. Minden sikeres nagy rendszer egy valamivel kisebb mkd rendszer ttervezse. Nem ismerek kivtelt e szably all. Mg leginkbb olyan tervek jutnak eszembe, melyek vekig szenvedtek zrzavaros llapotban s nagy kltsggel, vekkel a tervezett befejezsi dtum utn esetleg sikeresek lettek. Az ilyen projektek eredmnye szndkolatlanul s persze elismers nlkl elszr mkdskptelen rendszer lett, amit ksbb mkdkpess tettek, vgl jraterveztek olyan rendszerr, mely megkzelti az eredeti clkitzseket. Ebbl kvetkezik, hogy ostobasg egy nagy rendszert jonnan megtervezni s megpteni, pontosan kvetve a legfrissebb elveket. Minl nagyobb s nagyratrbb a rendszer, melyet megcloztunk, annl fontosabb, hogy legyen egy modellnk, melybl dolgozunk. Egy nagy rendszerhez az egyetlen valban elfogadhat modell egy valamivel kisebb, mkd rokon rendszer.

23.4.4. Ksrletezs s elemzs


Egy ignyes fejleszts kezdetn nem tudjuk a mdjt, hogyan alkossuk meg legjobban a rendszert. Gyakran mg azt sem tudjuk pontosan, mit kellene a rendszernek tennie, mivel a konkrtumok csak akkor tisztulnak ki, amikor a rendszert ptjk, teszteljk s hasznljuk. Hogyan kapjuk meg a teljes rendszer felptse eltt a szksges informcikat ahhoz, hogy megrtsk, melyek a jelents tervezsi dntsek s felbecsljk azok buktatit?

Forrs: http://www.doksi.hu

958

Tervezs a C++ segtsgvel

Ksrleteket folytatunk. Mihelyt van mit, elemezzk a terveket s azok megvalstst is. A leggyakoribb s legfontosabb a lehetsgek megvitatsa. A tervezs ltalban kzssgi tevkenysg, melyben a tervek bemutatk s vitk ltal fejldnek. A legfontosabb tervezeszkz a tbla; enlkl az embrionlis llapot tervezsi fogalmak nem tudnak kifejldni s elterjedni a tervezk s programozk kztt. A ksrlet legnpszerbb formja egy prototpus ptse, vagyis a rendszer vagy egy rsznek lekicsinytett vltozatban val ltrehozsa. A prototpus teljestmnyre nzve nincsenek szigor elrsok, ltalban elegend a gpi s programozsi krnyezetbeli erforrs, a tervezk s programozk pedig igyekeznek klnsen kpzettnek, tapasztaltnak s motivltnak ltszani. Arrl van sz, hogy egy vltozatot a lehet leggyorsabban futtatni lehessen, hogy lehetv tegyk a tervezsi s megvalstsi mdszerek vlasztknak feldertst. Ez a megkzelts nagyon sikeres lehet, ha jl csinljuk, de lehet rgy a pongyolasgra is. Az a problma, hogy a prototpus slypontja a lehetsgek feldertstl eltoldhat a lehet leghamarabb valamilyen mkdkpes rendszert kapni fel. Ez knnyen a prototpus bels felptse irnti rdektelensghez (vgl is csak prototpusrl van sz) s a tervezsi erfesztsek elhanyagolshoz vezet, mert a prototpus gyors elksztse ezekkel szemben elnyt lvez. Az a bkken, hogy az ily mdon elksztett vltozatban sokszor nyoma sincs az erforrsok helyes felhasznlsnak vagy a mdosthatsg lehetsgnek, mikzben egy majdnem teljes rendszer illzijt adja. Szinte trvnyszer, hogy a prototpus nem rendelkezik azzal a bels felptssel, hatkonysggal s ttekinthetsggel, mely megengedn, hogy rajta keresztl a vals hasznlatot felmrhessk. Kvetkezskppen egy prototpus, melybl majdnem termk lesz, elszvja azt az idt s energit, amit a valdi termkre lehetett volna fordtani. Mind a fejlesztk, mind a vezetk szmra ksrtst jelent, hogy a prototpusbl termket csinljanak s a teljestmny megtervezst elhalasszk a kvetkez kiadsig. A prototpusksztsnek ez a helytelen hasznlata tagadsa mindennek, amirt a tervezs ltezik. Rokon problma, hogy a prototpus fejleszti beleszerethetnek az eszkzeikbe. Elfelejthetik, hogy az ltaluk lvezett knyelem kltsgeit egy valdi rendszer nem mindig engedheti meg magnak, s az a korltok nlkli szabadsg, melyet kis kutatcsoportjuk nyjtott, nemigen tarthat fenn egy nagyobb kzssgben, mely szoros s egymstl fgg hatridk szerint dolgozik. Msrszrl, a prototpusok felbecslhetetlen rtkek. Vegyk pldul egy felhasznli fellet tervezst. Ebben az esetben a rendszer a felhasznlval kzvetlen klcsnhatsban nem lev rsznek bels felptse tnyleg lnyegtelen, s a prototpus hasznlatn kvl nincs ms lehetsges md, hogy tapasztalatokat szerezznk arrl, mi a vlemnye a felhasznlknak a rendszer klsejvel s hasznlatval kapcsolatban. De pldaknt vehetjk

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

959

az ellenkezjt is, egy olyan prototpust, melyet szigoran egy program bels mkdsnek tanulmnyozsra terveztek. Itt a felhasznli fellet lehet kezdetleges lehetsg szerint a valdi felhasznlk helyett szimulltakkal. A prototpuskszts a ksrletezs egy mdja. Eredmnyei pedig azok a megltsok kellenek, hogy legyenek, melyeket nem maga a prototpus, hanem annak ptse hoz magval. Tulajdonkppen a prototpus legfbb ismrveknt a tkletlensget kellene meghatrozni, hogy vilgos legyen, ez csupn ksrleti eszkz s nem vlhat termkk nagymrtk jratervezs nlkl. Ha tkletlen prototpusunk van, az segt, hogy a ksrletre sszpontostsunk s a lehet legkisebbre cskkenti annak veszlyt, hogy a prototpusbl termk legyen, valamint megsznteti azt a ksrtst is, hogy a termk tervezst tl szorosan a prototpusra alapozzuk, elfelejtve vagy elhanyagolva annak eredend korltait. A prototpus hasznlat utn eldoband. Vannak olyan ksrleti mdszerek is, melyeket a prototpuskszts helyett alkalmazhatunk. Ahol ezek hasznlhatk, gyakran elnyben is rszestjk ket, mert szigorbbak s kevesebb tervezi idt s rendszer-erforrst ignyelnek. Ilyenek pldul a matematikai modellek s a klnfle szimultorok. Tulajdonkppen fel is vzolhatunk egy folyamatot, a matematikai modellektl az egyre rszletesebb szimulcikon, a prototpusokon, s a rszleges megvalstson t a teljes rendszerig. Ez ahhoz az tlethez vezet, hogy egy rendszert a kezdeti tervbl s megvalstsbl tbbszri jratervezssel s jbli elksztssel finomtsunk. Ez az idelis mdszer, de nagyon nagy ignyeket tmaszthat a tervezi s fejleszti eszkzkkel szemben. A megkzelts azzal a kockzattal is jr, hogy tl sok kd kszl a kezdeti dntsek alapjn, gy egy jobb tervet nem lehet megvalstani. gy ez a stratgia egyelre csak kis s kzepes mret programok ksztsnl mkdik jl, melyekben nem valszn, hogy az tfog tervek klnsebben mdosulnnak, illetve a programok els kiadsa utni jratervezsekre s jbli megvalstsokra, ahol elkerlhetetlen az ilyen megkzelts. A tervezsi lehetsgek ttekintsre szolgl ksrleteken kvl egy terv vagy megvalsts elemzse nmagban is tleteket adhat. A legnagyobb segtsg pldul az osztlyok kzti klnbz sszefggsek (24.3) tanulmnyozsa lehet, de nem elhanyagolhatk a hagyomnyos eszkzk sem, mint a hvsi fk (call graphs) megrajzolsa, a teljestmnymrsek stb. Ne feledjk, hogy a fogalmak meghatrozsa (az elemzsi szakasz kimenete) s a tervezs sorn ppgy elkvethetnk hibkat, mint a megvalsts folyamn. Valjban mg tbbet is, mivel ezen tevkenysgek eredmnye kevsb konkrt, kevsb pontosan meghatrozott, nem vgrehajthat s ltalban nem tmogatjk olyan kifinomult eszkzk,

Forrs: http://www.doksi.hu

960

Tervezs a C++ segtsgvel

mint amilyenek a megvalsts elemzshez s ellenrzshez hasznlhatk. Ha a tervezs kifejezsre hasznlt nyelvet vagy jellsmdot formlisabb tesszk, valamilyen mrtkig ilyen eszkzt adhatunk a tervez kezbe, ezt azonban nem tehetjk annak az rn, hogy szegnyebb tesszk a megvalstsra hasznlt programozsi nyelvet (24.3.1). Ugyanakkor egy formlis jellsmd maga is problmk forrsa lehet, pldul ha a hasznlt jellsrendszer nem illik jl arra a gyakorlati problmra, melyre alkalmazzuk; amikor a formai kvetelmnyek szigora tllpi a rsztvev tervezk s programozk matematikai felkszltsgt s rettsgt; vagy amikor a formlis lers elszakad a lerand rendszertl. A tervezs eredenden ki van tve a hibknak s nehz tmogatni hatkony eszkzkkel. Ezrt nlklzhetetlen a tapasztalat s a visszajelzs. Kvetkezskppen alapveten hibs a programfejlesztst egyenes vonal folyamatknt tekinteni, mely az elemzssel kezddik s a tesztelssel vgzdik. Figyelmet kell fordtani az ismtelt tervezsre s megvalstsra, hogy a fejleszts klnbz szakaszaiban elegend visszajelzsnk legyen a tapasztaltakrl.

23.4.5. Tesztels
Az a program, melyet nem teszteltek, nem tekinthet mkdnek. Az eszmnykp, hogy egy programot gy ksztsnk el, hogy mr az els alkalommal mkdjn, a legegyszerbb programokat kivve elrhetetlen. Trekednnk kell erre, de nem szabad ostobn azt hinnnk, hogy tesztelni knny. A Hogyan teszteljnk? olyan krds, melyre nem lehet csak gy ltalban vlaszolni. A Mikor teszteljnk? krdsre azonban van ltalnos vlasz: olyan hamar s olyan gyakran, amennyire csak lehetsges. A tesztelsi mdszert clszer mr a tervezs s megvalsts rszeknt, de legalbb azokkal prhuzamosan kidolgozni. Mihelyt van egy fut rendszer, el kell kezdeni a tesztelst. A komoly tesztels elhalasztsa a befejezs utnig a hatridk csszst s /vagy hibs kiadst eredmnyezhet. Ha csak lehetsges, a rendszert gy kell megtervezni, hogy viszonylag knnyen tesztelhet legyen. Az ellenrzst gyakran bele lehet tervezni a rendszerbe. Nha ezt nem tesszk, attl val flelmnkben, hogy a futsi idej tesztels rontja a hatkonysgot, vagy hogy az ellenrzshez szksges redundns elemek tlsgosan nagy adatszerkezeteket eredmnyeznek. Az ilyen flelem rendszerint alaptalan, mert a legtbb tesztkd szksg esetn levlaszthat a tnyleges kdrl, mieltt a rendszert tadjuk. Az assertion-k (24.3.7.2) hasznlata nha hasznos lehet. Maguknl a teszteknl is fontosabb, hogy a rendszer olyan felpts legyen, ami elfogadhat eslyt ad arra, hogy sajt magunkat s felhasznlinkat/fogyasztinkat meggyzzk

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

961

arrl, hogy a hibkat statikus ellenrzs, statikus elemzs s tesztels egyttes hasznlatval ki tudjuk kszblni. Ahol a hibatrs biztostsra kidolgoztak valamilyen mdszert (14.9), ott ltalban a tesztels is bepthet a teljes tervbe. Ha a tervezsi szakaszban a tesztelsi krdsekkel egyltaln nem szmoltunk, akkor az ellenrzssel, a hatridk betartsval s a program mdostsval problmink lesznek. Az osztlyfelletek s az osztlyfggsek rendszerint j kiindulpontot jelentenek a tesztelsi mdszer kidolgozshoz(24.3, 24.4.2). Rendszerint nehz meghatrozni, mennyi tesztels elegend. A tl kevs tesztels azonban ltalnosabb problma, mint a tl sok. Az, hogy a tervezshez s megvalstshoz viszonytva pontosan mennyi erforrst kell a tesztelsre kijellni, termszetesen fgg a rendszer termszettl s a mdszerektl, melyeket ptshez hasznlunk. Irnyelvknt javasolhatom azonban, hogy idben, erfesztsben s tehetsgben tbb erforrst fordtsunk a rendszer tesztelsre, mint els vltozatnak elksztsre. A tesztelsnek olyan problmkra kell sszpontostania, melyeknek vgzetes kvetkezmnyei lennnek s olyanokra, melyek gyakran fordulnnak el.

23.4.6. A programok karbantartsa


A program-karbantarts (software maintenance) helytelen elnevezs. A karbantarts sz flrevezet hasonlatot sugall a hardverrel. A programoknak nincs szksgk olajozsra, nincsenek mozg alkatrszeik, amelyek kopnak, s nincsenek repedseik, ahol a vz sszegylik s rozsdsodst okoz. A szoftver pontosan lemsolhat s percek alatt nagy tvolsgra tvihet. A szoftver nem hardver. Azok a tevkenysgek, melyeket program-karbantarts nven emlegetnk, valjban az jratervezs s az jbli megvalsts, teht a szoksos programfejlesztsi folyamathoz tartoznak. Amikor a tervezsben hangslyosan szerepel a rugalmassg, bvthetsg s hordozhatsg, kzvetlenl a program fenntartsval kapcsolatos hibk hagyomnyos forrsaira sszpontostunk. A tesztelshez hasonlan a karbantarts sem lehet utlagos megfontols vagy a fejleszts f sodrtl elvlasztott tevkenysg. Klnsen fontos, hogy a projektben vgig ugyanazok a szemlyek vegyenek rszt, vagy legalbbis biztostsuk a folytonossgot . Nem knny ugyanis karbantartst vgeztetni egy olyan j (s ltalban kisebb tapasztalat) emberekbl ll csoporttal, akik az eredeti tervezkkel s programozkkal nem llnak kapcsolatban. Ha mgis msoknak kell tadni a munkt, hangslyt kell fektetni arra, hogy az j emberek tisztban legyenek a rendszer felptsvel s clkitzseivel. Ha a karbantart

Forrs: http://www.doksi.hu

962

Tervezs a C++ segtsgvel

szemlyzet magra marad abban, hogy kitallja a rendszer szerkezett, vagy hogy a rendszer sszetevinek cljt megvalstsukbl kvetkeztesse ki, a rendszer a helyi foltozgats hatsra gyorsan degenerldik. A dokumentci nem segt, mert az inkbb a rszletek lersra val, nem pedig arra, hogy az j munkatrsakat segtse a f tletek s elvek megrtsben.

23.4.7. Hatkonysg
Donald Knuth megfigyelte, hogy az id eltti optimalizls a gykere minden rossznak. Nmelyek tlsgosan megtanultk ezt a leckt s minden hatkonysgi trekvst rossznak tekintenek, pedig a hatkonysgra vgig gyelni kell a tervezs s megvalsts sorn. Ez azonban nem azt jelenti, hogy a terveznek minden aprsgot hatkonny kell tennie, hanem csak azt, hogy az elsrend hatkonysgi krdsekkel foglalkoznia kell. A hatkonysg elrshez a legjobb mdszer egy vilgos s egyszer terv ksztse. Csak egy ilyen terv maradhat viszonylag lland a projekt lettartama alatt s szolglhat a teljestmny behangolsnak alapjul. Lnyeges, hogy elkerljk a nagy projekteket sjt megalomnit. Tl gyakori, hogy a programozk ha szksg lenne r tulajdonsgokat (23.4.3.2, 23.5.3) ptenek be, majd a sallangok tmogatsval megktszerezik, megngyszerezik a rendszerek mrett s futsi idejt. Ennl is rosszabb, hogy az ilyen tlfinomtott rendszereket gyakran szksgtelenl nehz elemezni, gy nehz lesz megklnbztetni az elkerlhet tlterhelst az elkerlhetetlentl. Ez pedig mg az alapvet elemzst s optimalizlst is akadlyozza. Az optimum belltsa elemzs s teljestmnymrs eredmnye kell, hogy legyen, nem tallomra piszmogs a kddal. A nagy rendszerek esetben a tervezi vagy programozi intuci klnsen megbzhatatlan tmutat a hatkonysgi krdsekben. Fontos, hogy elkerljk az eredenden rossz hatkonysg szerkezeteket, illetve az olyanokat, melyeknek elfogadhat teljestmnyszintre val optimalizlsa sok idt s gyessget kvn. Hasonlkppen fontos, hogy cskkentsk az eredenden hordozhatatlan szerkezetek s eszkzk hasznlatt, mert ez arra krhoztatja a projektet, hogy rgebbi (kisebb teljestmny) vagy ppen drgbb gpeken fusson.

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

963

23.5. Vezets
Feltve, hogy legalbb valamilyen rtelme van, a legtbb ember azt teszi, amire rveszik. Nevezetesen, ha egy projekttel sszefggsben bizonyos mkdsi mdokat djazunk s msokat bntetnk, csak kivteles programozk s tervezk fogjk kockztatni a karrierjket, hogy azt tegyk, amit helyesnek tartanak, a vezets ellenvlemnyvel, kznyvel s brokrcijval szemben?.3 Ebbl kvetkezik, hogy a cgnek kell, hogy legyen egy jutalmazsi rendszere, mely megfelel az ltala megllaptott tervezsi s programozsi clkitzseknek. Mindazonltal gyakran nem ez a helyzet: a programozsi stlusban nagy vltozs csak a tervezsi stlus megfelel mdostsval rhet el s ahhoz, hogy hatsos legyen, ltalban mindkett a vezetsi stlus megvltoztatst ignyli. A szellemi s szervezeti tehetetlensg knnyen eredmnyezhet olyan helyi vltoztatsokat, melyeket nem tmogatnak a sikert biztost globlis vltoztatsok. Meglehetsen jellemz plda egy objektumorientlt programozst tmogat nyelvre (mondjuk a C++-ra) val tlls, a nyelv lehetsgeit kihasznl megfelel tervezsi stratgiabeli vltoztats nlkl, vagy ppen fordtva, objektumorientlt tervezsre vlts azt tmogat programozsi nyelv bevezetse nlkl.

23.5.1. jrahasznosts
A kdok s tervek jrahasznostst gyakran emlegetik, mint f okot egy j programozsi nyelv vagy tervezsi mdszer bevezetsre. A legtbb szervezet azonban azokat az egyneket tmogatja, akik a melegvz jrafeltallst vlasztjk. Pldul ha egy programoz teljestmnyt kdsorokban mrik; vajon kis programokat fog rni a standard knyvtrakra tmaszkodva, bevtelnek s esetleg sttusznak rovsra? s ha egy vezett a csoportjban lv emberek szmval arnyosan fizetnek, vajon fel fogja-e hasznlni a msok ltal ksztett programot, ha helyette a sajt csoportjba jabb embereket vehet fel? Vagy ha egy cgnek odatlnek egy kormnyszerzdst, ahol a profit a fejlesztsi kltsgeknek egy rgztett szzalka, vajon ez a cg kzvetetten cskkenteni fogja-e a profitjt a leghatkonyabb fejleszteszkzk hasznlatval? Az jrahasznostst nehz jutalmazni, de ha a vezets nem tall mdot az sztnzsre s jutalmazsra, nem lesz jrahasznosts. Az jrahasznostsnak ms vonatkozsai is vannak. Akkor hasznlhatjuk fel valaki ms programjt, ha: 1. Mkdik: ahhoz, hogy jrahasznosthat legyen, a programnak elbb hasznlhatnak kell bizonyulnia.

3 Az olyan szervezetnek, mely a programozival gy bnik, mint az iditkkal, hamarosan olyan programozi

lesznek, akik csak iditaknt akarnak s kpesek cselekedni.

Forrs: http://www.doksi.hu

964

Tervezs a C++ segtsgvel

2. ttekinthet: fontos a program szerkezete, a megjegyzsek, a dokumentci s az oktatanyag. 3. Egytt ltezhet egy olyan programmal, melyet nem kimondottan a vele val egyttmkdsre rtak. 4. Tmogatott (vagy magunk akarjuk tmogatni; n ltalban nem akarom). 5. Gazdasgos (megoszthatom-e ms felhasznlkkal a fejlesztsi s karbantartsi kltsgeket?). 6. Meg lehet tallni. Ehhez hozztehetjk, hogy egy sszetev nem jrahasznosthat, amg nincs valaki, aki jra felhasznlta. Egy sszetev valamely krnyezetbe beillesztse jellemzen mkdsnek finomtsval, viselkedsnek ltalnostsval s ms programokkal val egyttmkdsi kpessgnek javtsval jr egytt. Amg legalbb egyszer el nem vgeztk ezt a feladatot, mg a leggondosabban megtervezett s elksztett sszetevknek is lehetnek szndkolatlan s vratlan egyenetlensgei. Az a tapasztalatom, hogy az jrahasznostsnak csak akkor vannak meg a szksges felttelei, ha valaki kzs gynek tekinti, hogy ilyen munkamegoszts mkdjn. Egy kis csoportban ez ltalban azt jelenti, hogy valaki tervezetten vagy vletlenl a kzs knyvtrak s dokumentci gazdja lesz, egy nagyobb cgnl pedig azt, hogy megbznak egy csoportot vagy osztlyt, hogy gyjtse ssze, ksztse el, dokumentlja, npszerstse s tartsa karban a tbb csoport ltal felhasznlt programsszetevket. Nem lehet tlbecslni a szabvnyos sszetevkkel foglalkoz csoport fontossgt. Vegyk szre, hogy egy program azt a kzssget tkrzi, amely ltrehozta. Ha a kzssgnek nincs egyttmkdst s munkamegosztst elsegt s jutalmaz rendszere, ritkn lesz egyttmkds s munkamegoszts. A szabvnyos sszetevkkel foglalkoz csoport aktvan kell, hogy npszerstse ezeket az sszetevket. Ebbl kvetkezik, hogy a jl megrt dokumentci nlklzhetetlen, de nem elegend. A csoportnak ezen kvl gondoskodnia kell oktatanyagokrl s minden ms informcirl is, ami lehetv teszi, hogy a remnybeli felhasznl megtalljon egy sszetevt s megrtse, mirt segthet az neki. Ennek kvetkezmnye, hogy a piackutatssal s az oktatssal kapcsolatos tevkenysgeket ennek a csoportnak kell vllalnia. Ha lehetsges, e csoport tagjainak szorosan egytt kell mkdnik az alkalmazsfejlesztkkel. Csak gy tudnak megfelelen rteslni a felhasznlk ignyeirl s felhvni a figyelmet az alkalmakra, amikor egyes sszetevket klnbz programok kzsen hasznlhatnak. Ezzel amellett rvelek, hogy az ilyen csoportoknak legyen vlemny-nyilvntsi s tancsadi szerepe, s hogy mkdjenek a bels kapcsolatok a csoportba bejv s onnan kimen informci tvitelre.

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

965

Az komponens-csoport sikert az gyfelek sikervel kell mrni. Ha a sikert egyszeren azzal mrjk, hogy mennyi eszkzt s szolgltatst tudnak elfogadtatni a fejleszt cgekkel, az ilyen csoport kereskedelmi gynkk alacsonyodik s llandan vltoz hbortok elmozdtja lesz. Nem minden kdnak kell jrahasznosthatnak lennie, ez nem mindenhat tulajdonsg. Ha azt mondjuk, hogy egy sszetev jrahasznosthat, ez azt jelenti, hogy felhasznlsa bizonyos kereteken bell kevs vagy semmi munkt nem ignyel. A legtbb esetben egy ms vzba trtn tkltztets jelents munkval jr. Ebbl a szempontbl az jrahasznosts ersen emlkeztet a hordozhatsgra (vagyis ms rendszerre val tltetsre). Fontos megjegyezni, hogy az jrahasznosts az ezt clz tervezsnek, az sszetevk tapasztalat alapjn val finomtsnak s annak a szndkos erfesztsnek az eredmnye, hogy mr ltez sszetevket keressnk (jbli) felhasznlsra. Az jrahasznosts nem az egyes nyelvi tulajdonsgok vagy kdolsi mdszerek vletlenszer hasznlatbl keletkezik, mintegy csodaknt. A C++ olyan szolgltatsai, mint az osztlyok, a virtulis fggvnyek s a sablonok lehetv teszik, hogy a tervezs kifejezsmdja az jrahasznostst knnyebb (s ezltal valsznbb) tegye, de nmagukban nem biztostjk azt.

23.5.2. Mret s egyensly


Az nll vagy kzssgben dolgoz programozt sokszor arra knyszertik, hogy dolgt a megfelel mdon vgezze. Intzmnyes felllsban ez gyakran gy hangzik: a megfelel eljrsok kifejlesztse s szigor kvetse szksges. Mindkt esetben elsknt a jzan sz eshet ldozatul annak a kvnsgnak, hogy mindenron javtsunk a dolgok vgzsnek mdjn. Sajnos, ha egyszer hinyzik a jzan sz, az akaratlanul okozhat kroknak nincs hatra. Vegyk a fejlesztsi folyamat 23.4-ben felsorolt szakaszait s a 23.4.3-ban felsorolt tervezsi lpseket. E lpsekbl viszonylag knnyen kidolgozhat egy megfelel tervezsi mdszer, ahol minden szakasz pontosan krlhatrolt, meghatrozott bemenete s kimenete van, valamint rendelkezik az ezek kifejezshez szksges, rszben formlis jellsmddal. Ellenrz listkkal biztosthatjuk, hogy a tervezsi mdszer kvetkezetesen tmogassa a nagy szm eljrsi s jellsbeli szably rvnyre juttatst, s hogy ehhez eszkzket lehessen kifejleszteni. Tovbb, az sszefggsek 24.3-ban bemutatott osztlyozst nzve valaki kinyilatkoztathatn, hogy bizonyos kapcsolatok jk, msok pedig rosszak, s elemz eszkzket adna annak biztostsra, hogy ezeket az rtktleteket az adott projekten bell egysgesen alkalmazzk. A programfejlesztsi folyamat e megerstst tklyre fejlesztend, valaki lerhatn a dokumentls szablyait (belertve a helyesrsi, nyelvtani s gpelsi szablyokat) s a kd ltalnos kllemre vonatkoz elrsokat

Forrs: http://www.doksi.hu

966

Tervezs a C++ segtsgvel

(belertve azt is, mely nyelvi tulajdonsgokat s knyvtrakat szabad s melyeket nem szabad hasznlni, hol hasznlhat behzs, hogyan lehet elnevezni a fggvnyeket, vltozkat s tpusokat stb.). Ezek kztt akad olyan, amely elsegtheti a sikert. Termszetesen ostobasg lenne egy esetleg tzmilli kdsorbl ll rendszer tervezst elkezdeni melyet tbb szz ember fejlesztene s tbb ezer ember tartana karban s tmogatna tz vagy mg tbb vig egy becsletesen kidolgozott s meglehetsen szilrd vz nlkl. Szerencsre a legtbb program nem ebbe a kategriba esik. Ha azonban egyszer elfogadtuk, hogy egy ilyen tervezsi mdszer vagy egy ilyen kdolsi s dokumentlsi szabvnygyjtemnyhez val ragaszkods a helyes t, ktelez lesz annak ltalnos s minden rszletre kiterjed hasznlata. Ez kis programoknl nevetsges korltozsokhoz s tbbletmunkhoz vezethet: a halads s siker mrtkt jelent hatkony munka helyett aktatologats s rlaptltgets folyik majd. Ha ez trtnik, az igazi tervezk s programozk tvozni fognak s brokratk lpnek a helykbe. Ha egy kzssgen bell mr elfordult, hogy egy tervezsi mdszert ilyen nevetsgesen rosszul alkalmaztak, annak sikertelensge rgyet szolgltat arra, hogy elkerljenek szinte minden szablyozst a fejlesztsi folyamatban. Ez viszont ppen olyan zrzavarhoz s balsikerekhez vezet, mint amilyeneket az j tervezsi mdszer kialaktsval meg akartunk elzni. Az igazi problma megtallni az egyenslyt a megfelel fok szablyozs s az adott program fejlesztshez szksges szabadsg kztt. Ne higgyk, hogy erre a problmra knny lesz megoldst tallni. Lnyegben minden megkzelts csak kis projekteknl mkdik, pontosabban ami mg rosszabb, hiszen a rendszer rosszul tervezett s kegyetlen a belevont egynekkel szemben nagy projekteknl is, feltve, hogy szemrmetlenl sok idt s pnzt akarunk elpocskolni a problmra. Minden programfejleszti munka kulcsproblmja, hogyan tartsuk magunkat a terv eredeti szempontjaihoz. Ez a problma a mrettel hatvnyozottan nvekszik. Csak egy nll programoz vagy egy kis csoport tudja szilrdan kzbentartani s betartani egy nagyobb munka tfog clkitzseit. A legtbb embernek olyan sok idt kell tltenie rszprojektekkel, technikai rszletkrdsekkel, napi adminisztrcival stb., hogy az tfog clok knnyen feledsbe merlnek vagy alrendeldnek helyi s kzvetlenebb cloknak. Az is sikertelensghez vezet, ha nincs egy egyn vagy egy csoport, melynek kizrlag az a feladata, hogy fenntartsa a terv srtetlensgt. Recept a siker ellen, ha egy ilyen egynnek vagy csoportnak nem engedjk, hogy a munka egszre hasson.

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

967

Egy sszer hossz tv clkitzs hinya krosabb, mint brmilyen egyedi tulajdonsg hinya. Egy ilyen tfog clkitzs megfogalmazsa, szben tartsa, az tfog tervdokumentci megrsa, a f fogalmak ismertetse, s ltalban msokat segteni, hogy szben tartsk az tfog clt, kis szm egyn feladata kell, hogy legyen.

23.5.3. Egynek
Az itt lertak szerinti tervezs jutalom a tehetsges tervezk s programozk szmra, viszont a sikerhez elengedhetetlen, hogy ezeket a tervezket s programozkat megtalljuk. A vezetk gyakran elfelejtik, hogy a szervezetek egynekbl llnak. Npszer vlemny, hogy a programozk egyformk s csereszabatosak. Ez tveszme, mely tnkreteheti a kzssget, azltal, hogy elzavarja a leghatkonyabb egyneket s mg a megmaradtakat is arra tli, hogy jval a kpessgeik alatti szinten dolgozzanak. Az egynek csak akkor felcserlhetk, ha nem engedjk, hogy hasznostsk azokat a kpessgeiket, melyek a krdses feladat ltal megkvetelt minimum fl emelik ket. A csereszabatossg elve teht nem humnus s eredenden pazarl. A legtbb programozsi teljestmnymrs sztnzi a pazarlst s kihagyja a szmtsbl az egyni hozzjrulst. A legkzenfekvbb plda az a viszonylag elterjedt gyakorlat, hogy a haladst a megrt kdsorok, a dokumentci lapjainak, vagy az elvgzett tesztek szmval mrik. Az ilyen szmok jl mutatnak diagramokon, de a valsghoz vajmi kevs kzk van. Pldul, ha a termelkenysget a kdsorok szmval mrjk, akkor egy sszetev sikeres jrahasznostsa negatv programozi teljestmnynek minslhet. Egy nagy programrsz jratervezsnl a legjobb elvek sikeres alkalmazsa ugyanilyen hats. A teljestett munka minsgt sokkal nehezebb mrni, mint a kimenet mennyisgt, az egyneket s csoportokat viszont a munka minsge alapjn kell jutalmazni, nem durva mennyisgmrs alapjn. Sajnos a minsg gyakorlati mrse legjobb tudsom szerint gyerekcipben jr. Ezenkvl azon teljestmny-elrsok, melyek a munknak csak egy szakaszra vonatkoznak, hajlamosak befolysolni a fejlesztst. Az emberek alkalmazkodnak a helyi hatridkhz s az egyni s csoportteljestmnyt a kvtk ltal elrthoz igaztjk. Ezt kzvetlenl a rendszer tfog psge s teljestmnye szenvedi meg. Ha egy hatridt pldul az eltvoltott vagy a megmaradt programhibkkal hatroznak meg, belthatjuk, hogy a hatrid csak a futsi idej teljestmny figyelmen kvl hagysval vagy a rendszer futtatshoz szksges hardver-erforrsok optimalizlsnak mellzsvel lesz tarthat. Ellenben ha csak a futsi idej teljestmnyt mrjk, a hibaarny biztosan nvekedni fog, amikor a fejlesztk a rendszert a futsi teljestmnyt mr programokra igyekeznek optimalizlni. Az rtelmes minsgi elrsok hinya nagy kvetelmnyeket tmaszt a ve-

Forrs: http://www.doksi.hu

968

Tervezs a C++ segtsgvel

zetk szakrtelmvel szemben, de az alternatva az, hogy a halads helyett egyes tallomra kivlasztott tevkenysgeket fognak jutalmazni. Ne feledjk, hogy a vezetk is emberek. Nekik is legalbb annyi oktatsra van szksgk, mint az ltaluk vezetetteknek. Mint a programfejleszts ms terletein, itt is hosszabb tvra kell terveznnk. Lnyegben lehetetlen megtlni egy egyn teljestmnyt egyetlen v munkja alapjn. A kvetkezetesen s hossz tvon vezetett feljegyzsek azonban megbzhat elrejelzst biztostanak az egyes munkatrsak teljestmnyvel kapcsolatban s hasznos segtsget adnak a kzvetlen mlt rtkelshez. Az ilyen feljegyzsek figyelmen kvl hagysa ahogy akkor trtnik, amikor az egyneket pusztn cserlhet fogaskerknek tekintik a szervezet gpezetben a vezetket kiszolgltatja a flrevezet mennyisgi mrseknek. A hossz tvra tervezs egyik kvetkezmnye, hogy az egynek (mind a fejlesztk, mind a vezetk) az ignyesebb s rdekesebb feladatokhoz hosszabb idt ignyelnek. Ez elveszi a kedvt az llsvltoztatknak ppgy, mint a karriercsinls miatt munkakrt vltoztatknak. Trekedni kell arra, hogy kicsi legyen a fluktuci mind a mszakiak, mind a kulcsfontossg vezetk krben. Egyetlen vezet sem lehet sikeres a kulcsemberekkel val egyetrts s friss, a trgyra vonatkoz technikai tuds nlkl, de ugyangy egyetlen tervezkbl s fejlesztkbl ll csoport sem lehet hossz tvon sikeres alkalmas vezetk tmogatsa nlkl s anlkl, hogy alapjaiban rtsk azt a krnyezetet, melyben dolgoznak. Ahol szksges az innovci, az idsebb szakemberek, elemzk, tervezk, programozk stb. ltfontossg szerepet jtszanak az j eljrsok bevezetsben. k azok, akiknek j mdszereket kell megtanulniuk s sok esetben el kell felejtenik a rgi szoksokat, ami nem knny, hiszen ltalban komoly erfesztseket tettek a rgi munkamdszerek elsajttsra, radsul az e mdszerekkel elrt sikereikre, szakmai tekintlykre tmaszkodnak. Sok szakmai vezetvel ugyanez a helyzet. Termszetesen az ilyen egynek gyakran flnek a vltozstl. Ezrt a vltozssal jr problmkat tlbecslik s nehezen ismerik el a rgi mdszerekkel kapcsolatos problmkat. Ugyanilyen termszetes, hogy a vltozs mellett rvelk hajlamosak tlbecslni az j mdszerek elnyeit s albecslni a vltozssal jr problmkat. A kt csoportnak kommuniklnia kell: meg kell tanulniuk ugyanazon a nyelven beszlni s segtenik kell egymsnak, hogy az tmenetre megfelel mdszert dolgozhassanak ki. Ha ez nem trtnik meg, a szervezet megbnul s a legjobb kpessg egynek tvoznak mindkt csoportbl. Mindkt csoportnak emlkeznie kell arra, hogy a legsikeresebb regek gyakran azok, akik tavaly az ifj titnok voltak. Ha adott az esly arra, hogy megalzkods nlkl tanuljanak, a tapasztaltabb programozk s tervezk lehetnek a legsikeresebb s legnagyobb betekintssel rendelkez hvei a vltoztatsnak. Egszsges szkepticizmusuk, a felhasznlk ismerete s a szervezet mkdsvel kapcsolatban szerzett tapasztalataik felbecslhetetlenl rtkesek lehetnek. Az azonnali s gykeres vltozsok javasli szre kell vegyk, hogy az

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

969

tmenet, amely az j eljrsok fokozatos elsajttsval jr, tbbnyire elengedhetetlen. Azoknak viszont, akik nem kvnnak vltoztatni, olyan terleteket kell keresnik, ahol nem szksges vltoztatni, nem pedig dhs htvdharcot vvni olyan terleteken, ahol az j kvetelmnyek mr jelentsen megvltoztattk a siker feltteleit.

23.5.4. Hibrid tervezs


j munkamdszerek bevezetse egy cgnl fradsgos lehet. Jelents trst okozhat mind a szervezetben, mind az egynekben. Egy vratlan vltozs, mely a rgi iskola hatkony s tapasztalt tagjait egyik naprl a msikra az j iskola zldfl joncaiv vltoztatja, ltalban elfogadhatatlan. Vltozsok nlkl azonban ritkn rhetnk el nagy nyeresget, a jelents vltozsok pedig tbbnyire kockzattal jrnak. A C++-t gy terveztk, hogy a kockzatot a lehet legkisebbre cskkentse, azltal, hogy lehetv teszi a mdszerek fokozatos elsajttst. Br vilgos, hogy a C++ hasznlatnak legnagyobb elnyei az elvont adatbrzolsbl s az objektumorientlt szemlletbl addnak, nem biztos, hogy e nyeresgeket a leggyorsabban a mlttal val gykeres szaktssal lehet elrni. Egyszer-egyszer keresztlvihet az ilyen egyrtelm szakts, de gyakoribb, hogy a javts vgyt egy idre flre kell tennnk, hogy meggondoljuk, hogyan kezeljk az tmenetet. A kvetkezket kell figyelembe vennnk: A tervezknek s programozknak id kell az j szakismeretek megszerzshez. Az j kdnak egytt kell mkdnie a rgi kddal. A rgi kdot karban kell tartani (gyakran a vgtelensgig). A ltez terveket s programokat be kell fejezni (idre). Az j eljrsokat tmogat eszkzket be kell vezetni az adott krnyezetbe.

Ezek a tnyezk termszetesen hibrid (kevert) tervezsi stlushoz vezetnek mg ott is, ahol nmelyik terveznek nem ez a szndka. Az els kt pontot knny alulrtkelni. Azltal, hogy szmos programozsi irnyelvet tmogat, a C++ vltozatos mdon tmogatja a nyelv hasznlatnak fokozatos bevezetst: A programozk produktvak maradhatnak a C++ tanulsa kzben. A C++ jelents elnyket nyjt egy eszkzszegny krnyezetben. A C++ programrszek jl egyttmkdnek a C-ben vagy ms hagyomnyos nyelven rt kddal. A C++-nak jelents C-kompatibilis rszhalmaza van.

Forrs: http://www.doksi.hu

970

Tervezs a C++ segtsgvel

Az alaptlet az, hogy a programozk egy hagyomnyos nyelvrl gy trhetnek t a C++-ra, hogy a nyelvre ttrve elszr mg megtartjk a hagyomnyos (eljrskzpont) programozsi stlust, azutn hasznlni kezdik az elvont adatbrzols mdszereit, vgl amikor mr elsajttottk a nyelvet s a hozz tartoz eszkzk hasznlatt ttrnek az objektumorientlt (object-oriented) s az ltalnostott programozsra (generic programming). Egy jl tervezett knyvtrat sokkal knnyebb hasznlni, mint megtervezni s elkszteni, gy egy kezd mr az elrehalads korai szakaszaiban is rszeslhet az elvont brzols hasznlatnak elnyeibl. Az objektumorientlt tervezst s programozst, valamint a C++ fokozatosan trtn megtanulst tmogatjk azok a szolgltatsok, melyekkel a C++ kdot keverhetjk olyan nyelveken rt kddal, melyek nem tmogatjk a C++ elvont adatbrzolsi s objektumorientlt programozsi fogalmait (24.2.1). Sok fellet eljrskzpont maradhat, mivel nincs kzvetlen haszna, ha valamit bonyolultabb tesznk. Sok kulcsfontossg knyvtrnl az tltetst mr elvgezte a knyvtr ltrehozja, gy a C++ programoznak nem kell tudnia, mi a tnyleges megvalsts nyelve. A C-ben vagy hasonl nyelven rt knyvtrak hasznlata az jrahasznosts elsdleges s kezdetben legfontosabb formja a C++-ban. A kvetkez lps melyet csak akkor kell elvgezni, amikor tnylegesen szksg van a kifinomultabb eljrsokra a C, Fortran vagy hasonl nyelven rt szolgltatsok osztlyok formjban val tlalsa, az adatszerkezetek s fggvnyek C++ nyelv felletosztlyokba zrsa ltal. Egy egyszer plda a jelents bvtsre az eljrs s adatszerkezet szintjrl az elvont adatbrzols szintjre a 11.12 string osztlya. Itt a C karakterlnc-brzolst s szabvnyos karakterlnc-fggvnyeit hasznljuk fel egy sokkal egyszerbben hasznlhat karakterlnc-tpus ltrehozsra. Hasonl mdszer hasznlhat egy beptett vagy egyedi tpusnak egy osztlyhierarchiba illesztsre (23.5.1). Ez lehetv teszi, hogy a C++-ra kszlt terveket az elvont adatbrzols s az osztlyhierarchik hasznlathoz tovbbfejlesszk mg olyan nyelveken rt kd jelenltben is, ahonnan hinyoznak ezek a fogalmak, st azzal a megszortssal is, hogy az eredmnyl kapott kd eljrskzpont nyelvekbl is meghvhat legyen.

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

971

23.6. Jegyzetek
Ez a fejezet csak rintette a programozs tervezsi s vezetsi krdseit A tovbbi tanulmnyokat segtend sszegyjtttnk egy rvid irodalomjegyzket. Rszletesebbet [Booch, 1994] alatt tallunk.
[Anderson, 1990] Bruce Anderson s Sanjiv Gossain: An Iterative Design Model for Reusable ObjectOriented Software. Proc. OOPSLA90. Ottawa, Canada. Egy tervez s jratervez modell lersa, pldkkal s a tapasztalatok trgyalsval. [Booch, 1994] Grady Booch: Object-Oriented Analysis and Design with Applications. Benjamin/Cummings. 1994. ISBN 0-8053-5340-2. Rszletes lerst tartalmaz a tervezsrl, s egy grafikai jellsmddal tmogatott tervezsi mdrl. Szmos nagyobb tervezsi plda ll rendelkezsre C++ nyelven. Kivl knyv, melybl e fejezet is sokat mertett. Az e fejezetben rintett krdsek j rszt nagyobb mlysgben trgyalja. [Booch, 1996] Grady Booch: Object Solutions. Benjamin/Cummings. 1996. ISBN 0-8053-0594-7. Az objektumkzpont rendszerek fejlesztst a vezets szemszgbl vizsglja. Szmos C++ pldt tartalmaz. [Brooks, 1982] Fred Brooks: The Mythical man Month. Addison-Wesley. 1982. Ezt a knyvet mindenkinek pr vente jra el kellene olvasnia! Ints a nagykpsg ellen. Technikailag kiss eljrt felette az id, de az emberi, szervezeti s mretezsi vonatkozsai idtllak. 1997-ben kibvtve jra kiadtk. ISBN 1-201-83595-9. [Brooks, 1987] Fred Brooks: No Silver Bullet. IEEE Computer. Vol 20. No. 4. 1987 prilis. A nagybani szoftverfejleszts megkzeltseinek sszegzse, a rgta aktulis figyelmeztetssel: nincsenek csodaszerek (nincs ezstgoly). [Coplien, 1995] James O. Coplien s Douglas C. Schmidt (szerk.): Pattern Languages of Program Design. Addison-Wesley. 1995. ISBN 1-201-60734-4. [De Marco, 1987] T. DeMarco s T. Lister: Peopleware. Dorset House Publishing Co. 1987. Azon ritka knyvek egyike, melyek az egynnek a programfejlesztsben betlttt szerepvel foglalkoznak. Vezetknek ktelez, kellemes esti olvasmny, szmos ostoba hiba ellenszert tartalmazza. [Gamma, 1994] Eric Gamma s msok: Design Patterns. Addison-Wesley. 1994. ISBN 0-201-633612. Gyakorlati katalgus, mely rugalmas s jrahasznosthat programok ksztsi mdszereit tartalmazza, bonyolultabb, jl kifejtett pldkkal. Szmos C++ pldt tartalmaz. [Jacobson, 1992] Ivar Jacobson s msok: Object-Oriented Software Engineering. Addison-Wesley. 1992. ISBN 0-201-54435-0. Alapos s gyakorlati lers, amely hasznlati esetek (use case, 23.4.3.1) alkalmazsval rja le a szoftverfejlesztst ipari krnyezetben. Flrerti a C++ nyelvet, 10 vvel ezeltti llapotval lerva.

Forrs: http://www.doksi.hu

972

Tervezs a C++ segtsgvel

[Kerr, 1987]

Ron Kerr: A Materialistic View of the Software Engineering Analogy. SIGPLAN Notices, 1987 mrcius. Az ebben s a kvetkez fejezetekben hasznlt hasonlatok nagyban ptenek e cikkre s a Ron ltal tartott bemutatkra, illetve vele folytatott beszlgetsekre. [Liskov, 1987] Barbara Liskov: Data Abstraction and Hierarchy. Proc. OOPSLA87 (Fggelk). Orlando, Florida. Az rklds szerepe az elvont adatbrzolsban. Megjegyzend, hogy a C++ szmos eszkzt biztost a felvetett problmk tbbsgnek megoldsra (24.3.4). [Martin, 1995] Robert C. Martin: Designing Object-Oriented C++ Applications Using the Booch Method. Prentice-Hall. 1995. ISBN 0-13-203837-4. Rendszerezetten mutatja be, hogyan jutunk el a problmtl a C++ kdig. Lehetsges tervezsi mdokat s a kztk val dnts elveit ismerteti. A tbbi tervezssel foglalkoz knyvnl gyakorlatiasabb s konkrtabb. Szmos C++ pldt tartalmaz. [Meyer, 1988] Bertrand Meyer: Object Oriented Software Construction. Prentice Hall. 1988. Az 1-64. s 323-334. oldalakon j bevezetst ad az objektumkzpont programozs s tervezs egy nzetrl, szmos hasznlhat gyakorlati tanccsal. A knyv maradk rsze az Eiffel nyelvet rja le. Hajlamos az Eiffelt s az ltalnos elveket sszekeverni. [Parkinson, 1957] C. N. Parkinson: Parkinsons Law and other Studies in Administration. Houghton Mifflin. Boston. 1957. Az egyik leghumorosabb s legpontosabb lers, melyet a brokrcirl rtak. [Shlaer, 1988] S. Shlaer s S. J. Mellor: Object-Oriented Systems Analysis s Object Lifecycles. Yourdon Press. ISBN 0-13-629023-X s 0-13-629940-7. Az elemzs, tervezs s programozs egy olyan szemllett mutatja be, mely jelentsen eltr az itt bemutatottl s a C++ ltal tmogatottl. [Snyder, 1986] Alan Snyder: Encapsulation and Inheritance in Object-Oriented Programming Languages. Proc. OOPSLA86. Portland, Oregon. Valsznleg a betokozs (enkapszulci) s rklds kapcsolatnak els j lersa. A tbbszrs rkldst ugyancsak trgyalja. [Wirfs-Brock, 1990] Rebecca Wirfs-Brock, Brian Wilkerson s Lauren Wiener: Designing ObjectOriented Software. Prentice Hall. 1990. Szerepmintkon alapul emberkzpont tervezsi mdszertant r le CRC krtyk hasznlatval. A szveg (taln a mdszertan is) rszrehajl a Smalltalk irnyban.

Forrs: http://www.doksi.hu

23. Fejleszts s tervezs

973

23.7. Tancsok
[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] Legynk tisztban vele, mit akarunk elrni. 23.3. Tartsuk szben, hogy a programfejleszts emberi tevkenysg. 23.2, 23.5.3. Hasonlat ltal bizonytani mts. 23.2. Legyenek meghatrozott, kzzelfoghat cljaink. 23.4. Ne prbljunk emberi problmkat technikai megoldsokkal orvosolni. 23.4. Tekintsnk hosszabb tvra a tervezsben s az emberekkel val bnsmdban. 23.4.1, 23.5.3. Nincs mretbeli als hatra azon programoknak, melyeknl rtelme van a kdols eltti tervezsnek. 23.2. sztnzzk a visszajelzst. 23.4. Ne cserljk ssze a tevkenysget a haladssal. 23.3, 23.4. Ne ltalnostsunk jobban, mint szksges, mint amivel kapcsolatban kzvetlen tapasztalataink vannak, s ami tesztelhet. 23.4.1, 23.4.2. brzoljuk a fogalmakat osztlyokknt. 23.4.2, 23.4.3.1. A program bizonyos tulajdonsgait nem szabad osztlyknt brzolni. 23.4.3.1. A fogalmak kztti hierarchikus kapcsolatokat brzoljuk osztlyhierarchikknt. 23.4.3.1. Aktvan keressk a kzs vonsokat az alkalmazs s a megvalsts fogalmaiban s az eredmnyl kapott ltalnosabb fogalmakat brzoljuk bzisosztlyokknt. 23.4.3.1, 23.4.3.5. Mshol alkalmazott osztlyozsok nem szksgszeren hasznlhatak egy program rklsi modelljben. 23.4.3.1. Az osztlyhierarchikat a viselkeds s a nem vltoz (invarins) tulajdonsgok alapjn ptsk fel. 23.4.3.1, 23.4.3.5, 24.3.7.1. Vizsgljuk meg a hasznlati eseteket. 23.4.3.1. Hasznljunk CRC krtykat, ha szksges. 23.4.3.1. Modellknt, sztnzsknt s kiindulpontknt hasznljunk ltez rendszereket. 23.4.3.6. vakodjunk a rajzos tervezstl. 23.4.3.1. Dobjuk el a prototpust, mieltt teherr vlik. 23.4.4. Szmoljunk a vltoztats lehetsgvel, sszpontostsunk a rugalmassgra, a bvthetsgre, a hordozhatsgra s az jrahasznosthatsgra. 23.34.2. A kzppontba az sszetevk tervezst helyezzk. 23.4.3. A felletek az egyes fogalmakat egyetlen elvonatkoztatsi szinten brzoljk. 23.4.3.1. A vltoztats kszbn tartsuk szem eltt a stabilitst. 23.4.2.

[15] [16] [17] [18] [19] [20] [21] [22] [23] [24] [25]

Forrs: http://www.doksi.hu

974

Tervezs a C++ segtsgvel

[26] A gyakran hasznlt felletek legyenek kicsik, ltalnosak s elvontak, hogy az eredeti terv lnyegt ne kelljen mdostani. 23.4.3.2, 23.4.3.5.. [27] Trekedjnk a minimalizmusra. Ne hasznljunk ha szksg lenne r tulajdonsgokat. 23.4.3.2. [28] Mindig vizsgljuk meg egy osztly ms lehetsges brzolsait. Ha nincs kzenfekv alternatva, az osztly valsznleg nem kpvisel tiszta fogalmat. 23.4.3.4. [29] Tbbszr is vizsgljuk fell s finomtsuk mind a tervezst, mind a megvalsts mdjt. 23.4, 23.4.3. [30] Hasznljuk az elrhet legjobb eszkzket a tesztelshez s a problma, a terv, illetve a megvalsts elemzshez. 23.3, 23.4.1, 23.4.4. [31] Ksrletezznk, elemezznk s teszteljnk a lehet leghamarabb s leggyakrabban. 23.4.4, 23.4.5. [32] Ne feledkezznk meg a hatkonysgrl. 23.4.7. [33] Teremtsnk egyenslyt a formalitsok szintje s a projekt mrete kztt. 23.5.2. [34] Mindenkppen bzzunk meg valakit, aki az tfog tervezsrt felel. 23.5.2. [35] Dokumentljuk, npszerstsk s tmogassuk az jrahasznosthat sszetevket. 23.5.1. [36] Dokumentljuk a clokat s elveket ppgy, mint a rszleteket. 23.4.6. [37] Gondoskodjunk oktatanyagrl az j fejlesztk rszre a dokumentci rszeknt. $23.4.6. [38] Jutalmazzuk s sztnzzk a tervek, knyvtrak s osztlyok jrahasznostst. 23.5.1.

Forrs: http://www.doksi.hu

24
Tervezs s programozs
Legyen egyszer: annyira egyszer, amennyire csak lehet de ne egyszerbb. (A. Einstein) A tervezs s a programozsi nyelv Osztlyok rkls Tpusellenrzs Programozs Mit brzolnak az osztlyok? Osztlyhierarchik Fggsgek Tartalmazs Tartalmazs s rkls Tervezsi kompromisszumok Hasznlati kapcsolatok Beprogramozott kapcsolatok Invarinsok Hibaellenrzs felttelezsekkel Betokozs Komponensek Sablonok Fellet s megvalsts Tancsok

24.1. ttekints
Ebben a fejezetben azt vizsgljuk meg, hogy a programozsi nyelvek s maga a C++ hogyan tmogatjk a tervezst. 24.2 Az osztlyok, osztlyhierarchik, a tpusellenrzs s maga a programozs alapvet szerepe 24.3 Az osztlyok s osztlyhierarchik hasznlata, klns tekintettel a programrszek kztti fggsgekre

Forrs: http://www.doksi.hu

976

Tervezs a C++ segtsgvel

24.4 A komponens mint a tervezs alapegysge fogalma, s a felletek felptsre vonatkoz gyakorlati megfigyelsek Az ltalnosabb tervezsi krdsekkel a 23. fejezetben foglalkoztunk, mg az osztlyok klnbz hasznlati mdjait rszletesebben a 25. fejezet trgyalja.

24.2. A tervezs s a programozsi nyelv


Ha hidat szeretnnk pteni, figyelembe kellene vennnk az anyagot, amibl ptjk. A hd tervezst ersen befolysoln az anyag megvlasztsa s viszont. A khidakat mskpp kell megtervezni, mint az aclhidakat vagy a fahidakat s gy tovbb. Nem lennnk kpesek kivlasztani a hdhoz a megfelel anyagot, ha nem tudnnk semmit a klnbz anyagokrl s hasznlatukrl. Termszetesen nem kell csmesternek lenni ahhoz, hogy valaki fahidat tervezzen, de ismernie kell a faszerkezetek alapelveit, hogy vlasztani tudjon, fbl vagy vasbl ptsen-e hidat. Tovbb br nem kell valakinek szemlyesen csmesternek lennie egy fahd tervezshez kell, hogy rszletesen ismerje a fa tulajdonsgait s az csok szoksait. Ehhez hasonlan, ahhoz, hogy valamilyen programhoz nyelvet vlasszunk, tbb nyelv ismerete szksges, a program egyes rszeinek sikeres megtervezshez pedig meglehetsen rszletesen kell ismernnk a megvalstshoz vlasztott nyelvet mg akkor is, ha szemlyesen egyetlen kdsort sem runk. A j hdtervez figyelembe veszi az anyagok tulajdonsgait s azok megfelel felhasznlsval nveli a terv rtkt. A j szoftvertervez ugyangy a vlasztott programozsi nyelv erssgeire pt s amennyire csak lehet elkerli annak olyan hasznlatt, ami problmkat okozhat a kd rinak. Azt gondolhatnnk, hogy ez a nyelvi krdsek irnti rzkenysg termszetes, ha csak egyetlen tervezt vagy programozt rint. Sajnos azonban mg az nll programoz is ksrtsbe eshet, hogy a nyelvet hinyos tapasztalata vagy gykeresen eltr nyelvekben kialakult programozsi stlusa miatt helytelenl hasznlja. Amikor a tervez s a programoz nem azonos s klnsen ha szakmai s kulturlis htterk klnbz , szinte bizonyos, hogy a program hibs, krlmnyes vagy nem hatkony lesz. Mit nyjthat teht a programozsi nyelv a terveznek? Olyan tulajdonsgokat, melyek lehetv teszik a terv alapfogalmainak kzvetlen brzolst a programban. Ez megknnyti

Forrs: http://www.doksi.hu

24. Tervezs s programozs

977

a kd megrst, knnyebb teszi a tervezs s megvalsts kztti klcsns megfelels fenntartst, javtja a tervezk s programozk kztti kapcsolattartst s jobb eszkzk ksztst teszi lehetv mindkt csoport tmogatsra. A legtbb tervezsi mdszer a program klnbz rszei kzti fggsgekkel foglalkozik (rendszerint azrt, hogy a lehet legkisebbre cskkentse szmukat s biztostsa, hogy a fggsgek pontosan meghatrozottak s tlthatak legyenek). Egy nyelv, mely tmogatja a programrszek kapcsolatt biztost felleteket, kpes tmogatni az ezekre pl tervezst is, illetve garantlni tudja, hogy tnylegesen csak az elre ltott fggsgek ltezzenek. Mivel az ilyen nyelvekben sok fggs kzvetlenl a kdban is megjelenik, beszerezhetk olyan eszkzk, melyek a programot olvasva fggsgi diagramokat ksztenek. Ez megknnyti a tervezk s azok dolgt, akiknek szksgk van a program szerkezetnek megrtsre. Egy olyan programozsi nyelv, mint a C++ felhasznlhat a terv s a program kzti szakadk kisebbtsre s a zavarok s flrertsek krnek kvetkezetes szktsre. A C++ legfontosabb fogalma az osztly. A C++ osztlyai tpusok. A nvterekkel egytt az osztlyok is az adatrejts elsdleges eszkzei. A programok felhasznli tpusok hierarchiiknt pthetk fel. Mind a beptett, mind a felhasznli tpusok a statikusan ellenrztt tpusokra vonatkoz szablyoknak engedelmeskednek. A virtulis fggvnyek ezen szablyok megsrtse nlkl a futsi idej ktsrl gondoskodnak. A sablonok a paramterezett tpusok tervezst tmogatjk, a kivtelek pedig szablyozottabb hibakezelsre adnak mdot. A C++ ezen szolgltatsai anlkl hasznlhatk, hogy tbbletterhet jelentennek a C programokhoz kpest. Ezek a C++ azon elsrend tulajdonsgai, melyeket a terveznek meg kell rtenie s tekintetbe kell vennie. Ezenkvl a szles krben elrhet nagy programknyvtrak a mtrixknyvtrak, adatbzis-felletek, a grafikus felhasznli felletek knyvtrai s a prhuzamossgot tmogat knyvtrak is ersen befolysolhatjk a tervezsi dntseket. Az jdonsgtl val flelem nha a C++ optimlisnl rosszabb felhasznlshoz vezet. A ms nyelveknl, ms rendszereken s alkalmazsi terleteken tanultak helytelen alkalmazsa ugyanezt eredmnyezi. A gyenge tervezeszkzk szintn elronthatjk a terveket. me t a leggyakrabban elkvetett a nyelvi tulajdonsgok rossz kihasznlst s korltozsok felrgst eredmnyez tervezi hibk kzl: 1. 2. 3. Az osztlyok figyelmen kvl hagysa s olyan tervezs, amely a programozkat arra knyszerti, hogy csak a C rszhalmazt hasznljk. A szrmaztatott osztlyok s virtulis fggvnyek figyelmen kvl hagysa, csak az absztrakt adatbrzolsi mdszerek rszhalmaznak hasznlata. A statikus tpusellenrzs figyelmen kvl hagysa s olyan tervezs, amely a programozkat arra knyszerti, hogy utnozzk a dinamikus tpusellenrzst.

Forrs: http://www.doksi.hu

978

Tervezs a C++ segtsgvel

4. 5.

A programozs figyelmen kvl hagysa s a rendszer olyan megtervezse, amely a programozk kikszblst clozza. Az osztlyhierarchik kivtelvel mindennek a figyelmen kvl hagysa.

Ezeket a hibkat ltalban a kvetkez httrrel rendelkez tervezk kvetik el: 1. 2. 3. 4. 5. akik korbban a C-vel, a hagyomnyos CASE eszkzzel, vagy struktrlt tervezssel foglalkoztak, akik korbban Ada83, Visual Basic vagy ms absztrakt brzolst tmogat nyelven dolgoztak, akik Smalltalk vagy Lisp mlttal rendelkeznek, akik nem mszaki vagy nagyon specilis terleten dolgoztak, s akik olyan terletrl rkeztek, ahol ers hangslyt kapott a tiszta objektumorientlt programozs.

Mindegyik esetben ktelkednnk kell, vajon jl vlasztottk-e meg a megvalsts nyelvt, a tervezsi mdszert, illetve hogy a tervez elsajttotta-e a kezben lv eszkzk hasznlatt. Nincs semmi szokatlan vagy szgyellni val az ilyen problmkban. Ezek egyszeren olyan hinyossgok, amelyek nem optimlis terveket eredmnyeznek s a programozkra felesleges terheket hrtanak. A tervezk ugyanezekkel a problmkkal talljk magukat szemben, ha a tervezsi mdszer fogalmi felptse szreveheten szegnyesebb, mint a C++-. Ezrt ahol lehetsges, kerljk az ilyen hibkat. A kvetkez fejtegets ellenvetsekre adott vlaszokbl ll, mivel ez a valsgban is gy szokott lenni.

24.2.1. Az osztlyok figyelmen kvl hagysa


Vegyk azt a tervezst, amely figyelmen kvl hagyja az osztlyokat. Az eredmnyl kapott C++ program nagyjbl egyenrtk az ugyanezen tervezsi folyamat eredmnyeknt kaphat C programmal s ez a program ugyancsak nagyjbl egyenrtk azzal a COBOL programmal, melyet ugyanezen tervezsi folyamat eredmnyeknt kapnnk. A tervezs lnyegben programozsi nyelvtl fggetlenl folyt, a programozt arra knyszertve, hogy a C s a COBOL kzs rszhalmazban kdoljon. Ennek a megkzeltsnek vannak elnyei. Pldul az adat s a kd szigor elklntse, ami knnyv teszi az ilyen programokhoz tervezett hagyomnyos adatbzisok hasznlatt. Mivel egy minimlis programozsi nyelvet hasznlunk, kevesebb tudst vagy legalbb is kevesebb fle tudst kvetelnk

Forrs: http://www.doksi.hu

24. Tervezs s programozs

979

meg a programozktl. Sok programnl mondjuk egy hagyomnyos, szekvencilis adatbzist frisstnl ez a gondolkodsmd egszen sszer, az vtizedek alatt kifejlesztett hagyomnyos eljrsok pedig megfelelek a feladathoz. Tegyk fel azonban, hogy a program a rekordokat (vagy karaktereket) a hagyomnyos szekvencilis feldolgozstl eltren kezeli, vagy bonyolultabb mondjuk, egy interaktv CASE rendszerrl van sz. Az absztrakt adatbrzols nyelvi tmogatsnak hinya, amit az osztlyok elhanyagolsa melletti dnts okoz, fj lesz. Az eredend bonyolultsg az alkalmazsban valahol meg fog mutatkozni, s ha a rendszert egy szegnyes nyelven ksztettk, a kd nem fogja a tervet kzvetlenl tkrzni. A program kdja tl hossz lesz, hinyzik belle a tpusellenrzs s ltalban nem megkzelthet segdeszkzk szmra. A program fenntartsa s ksbbi mdosthatsga szempontjbl ez igazi rmlom. A problmra ltalnos megolds, ha eszkzket ksztnk a tervezsi mdszer fogalmainak tmogatsra. Ezek az eszkzk magasabb szint ptkezst s ellenrzst tesznek lehetv, ami ellenslyozza a (szndkosan legyengtett) programozsi nyelv gyengesgt. A tervezsi mdszer teht egy egyedi cl (s ltalban testleti tulajdont kpez) programozsi nyelvv vlik. Az ilyen programozsi nyelvek legtbb esetben csak gyenge ptlkai a szles krben elrhet ltalnos cl programozsi nyelveknek, melyeket hozzjuk val tervezeszkzk tmogatnak. Az osztlyok tervezsbl val kihagysnak legltalnosabb oka egyszeren a tehetetlensg. A hagyomnyos programozsi nyelvek nem tmogatjk az osztly fogalmt, a hagyomnyos tervezsi mdszerek pedig tkrzik ezt a gyengesget. A tervezs legtbbszr a problmk eljrsokra bontsra sszpontosul, melyek a kvnt mveleteket hajtjk vgre. Ezt a 2. fejezetben eljrskzpont (procedurlis) programozsnak nevezett fogalmat a tervezssel sszefggsben ltalban funkcionlis (fggvnyekre vagy mveletekre val) lebontsnak (functional decomposition) nevezzk. Gyakori krds, hogy tudjuk-e hasznlni a C++-t egy funkcionlis lebontson alapul tervezsi mdszerrel egytt? A vlasz igen, de a legvalsznbb, hogy a C++-t vgl egyszeren csak mint egy jobb C-t fogjuk hasznlni s a fentebb emltett problmkkal fogunk knldni. tmeneti idszakban, mr befejezett tervezsnl, vagy olyan alrendszereknl, ahol (a bevont szemlyek tapasztalatt figyelembe vve) nem vrhat, hogy az osztlyok jelents elnnyel jrnak, ez elfogadhat. Hosszabb tvon s ltalban azonban az osztlyok hasznlatnak a funkcionlis lebontsbl kvetkez ellenzse nem sszeegyeztethet a C++ vagy brmely ms, az absztrakt brzolst tmogat nyelv hatkony hasznlatval. A programozs eljrskzpont s objektumorientlt szemlletei alapveten klnbznek s ugyanarra a problmra jellemzen gykeresen klnbz megoldsokat adnak. Ez

Forrs: http://www.doksi.hu

980

Tervezs a C++ segtsgvel

a megfigyels ppgy igaz a tervezsi, mint a megvalstsi szakaszra: lehet sszpontostani az elvgzend tevkenysgekre s az brzoland fogalmakra, de nem lehet egyszerre mindkettre. Mirt rszestjk elnyben az objektumorientlt tervezst a funkcionlis lebontson alapul hagyomnyos tervezsi mdszerekkel szemben? Elssorban azrt, mert az utbbi nem biztost elegend lehetsget az absztrakt adatbrzolsra. Ebbl pedig az kvetkezik, hogy az eredmnyl kapott terv kevsb mdosthat, eszkzkkel kevsb tmogathat, kevsb alkalmas prhuzamos fejlesztsre, kevsb alkalmas prhuzamos vgrehajtsra.

A problma az, hogy a funkcionlis lebonts kvetkeztben a lnyeges adatok globlisak lesznek, mivel amikor egy rendszer fggvnyekbl ll fa szerkezet, brmely adat, melyre kt fggvnynek van szksge, mindkett szmra elrhet kell, hogy legyen. Ez azt eredmnyezi, hogy az rdekes adatok egyre feljebb vndorolnak a fn a gykr fel (ne feledjk, a szmtstechnikban a fk mindig a gykrtl lefel nvekednek), ahogy egyre tbb fggvny akar hozzjuk frni. Pontosan ugyanez a folyamat figyelhet meg az egygyker osztlyhierarchikban, melyekben az rdekes adatok s fggvnyek hajlamosak felfel vndorolni egy gykrosztly fel (24.4). A problmt gy oldhatjuk meg, ha az osztlyok meghatrozsra s az adatok betokozsra (encapsulation) sszpontostunk, gy ugyanis a programrszek kztti fggseket ttekinthetv tehetjk, s ami mg fontosabb cskkentjk a programban lev fggsgek szmt, azltal, hogy az adatokra val hivatkozsok loklisak lesznek. Egyes problmkat azonban a legjobban a megfelel eljrsok megrsval oldhatunk meg. Az objektumorientlt megkzeltsnl a tervezs lnyege nem az, hogy egyetlen nem tag fggvny se legyen a programban vagy hogy a rendszer egyetlen rsze se legyen eljrskzpont. Lnyegesebb, hogy a program klnbz rszeit gy vlasszuk el, hogy jobban tkrzzk a fogalmakat. Ez ltalban gy rhet el a legjobban, ha elssorban az osztlyok s nem a fggvnyek llnak a tervezs kzppontjban. Az eljrskzpont stlus hasznlata tudatos dnts kell, hogy legyen, nem pedig az alaprtelmezs. Az osztlyokat s eljrsokat az alkalmazsnak megfelelen kell hasznlni, nem egy rugalmatlan tervezsi mdszer mellktermkeiknt.

Forrs: http://www.doksi.hu

24. Tervezs s programozs

981

24.2.2. Az rkls elkerlse


Tegyk fel, hogy a tervezsnl nem ptnk az rklsre. Az eredmnyl kapott program nem fog lni a C++ egyik legelnysebb tulajdonsgval, mikzben persze kihasznlja a C++ sok ms elnyt a C, Pascal, Fortran, COBOL stb. nyelvekkel szemben. A leggyakrabban hangoztatott rvek a tehetetlensgtl eltekintve az rkls hasznlata csak rszletkrds a megvalsts sorn, az rkls megsrti az adatrejts elvt s az rkls megnehezti az egyttmkdst ms programokkal. Az rklst pusztn rszletkrdsnek tekintve nem vesszk figyelembe azt, hogy az osztlyhierarchik kzvetlenl brzoljk az alkalmazsi terlet fogalmai kztti kapcsolatokat. Mrpedig az ilyen kapcsolatokat nyilvnvalv kell tenni a tervezsben, hogy a tervezk vitatkozhassanak rluk. Elfordulhat az is, hogy az rklst olyan C++ programrszekbl zrjuk ki, amelyek kzvetlenl rintkeznek ms nyelveken rott kddal. Ez azonban nem elgsges ok arra, hogy a program egszben elkerljk az rklst, csupn a program klvilg fel mutatott fellett kell gondosan lernunk s betokoznunk. Hasonlkppen, az adatrejtsnek az rkls ltali veszlyeztetse miatti agglyok (24.3.2.1) csak arra adnak okot, hogy elvigyzatosak legynk a virtulis fggvnyek s vdett tagok hasznlatval (15.3), az rklds ltalnos elkerlsre nem. Sok esetben nem szrmazik valdi elny az rklsbl. A nagyobb programoknl azonban a nincs rkls megkzelts kevsb ttekinthet s rugalmatlanabb rendszert eredmnyez. Az rklst ekkor csak tettetjk, hagyomnyos nyelvi szerkezetek s tervezsi mdok hasznlatval. Az is valszn, hogy az ilyen hozzlls ellenre az rklst mgis hasznlni fogjuk, mert a C++ programozk a program tbb rszben is meggyz rveket fognak tallni az rkls alap tervezs mellett. Ezrt a nincs rkls csak azt fogja eredmnyezni, hogy a program felptse nem lesz kvetkezetes s az osztlyhierarchik hasznlata csak egyes alrendszerekre fog korltozdni. Ms szval, ne legynk elfogultak. Az osztlyhierarchik nem minden j program nlklzhetetlen rszei, de sok esetben segteni tudnak mind az alkalmazs megrtsben, mind egy megolds kifejezsben. Az a tny, hogy az rklst lehet helytelen vagy tlzott mdon hasznlni, ok az vatossgra, de a tiltsra nem.

Forrs: http://www.doksi.hu

982

Tervezs a C++ segtsgvel

24.2.3. A statikus tpusellenorzs figyelmen kvl hagysa


Vegynk azt az esetet, amikor a tervezsnl elhanyagoljuk a statikus tpusellenrzst. Ezt ltalban a kvetkezkkel indokoljk: a tpusok a programozsi nyelv termkei, termszetesebb objektumokban s nem tpusokban gondolkodni s a statikus tpusellenrzs arra knyszert, hogy tl korn gondoljunk a megvalsts krdseire. Ez a hozzlls addig j, amg nem okoz krt. Tervezskor sszernek tnhet nem foglalkozni a tpusellenrzs rszleteivel, s az elemzsi s a korai tervezsi szakaszban ltalban nyugodtan figyelmen kvl is hagyhatjuk ezeket a krdseket. Az osztlyok s osztlyhierarchik azonban nagyon hasznosak a tervezsben. Nevezetesen megengedik, hogy a fogalmakat brzolhassuk, kapcsolataikat meghatrozhassuk, s segtenek, hogy a fogalmakrl vitzzunk. A tervezs elrehaladtval ez a pontos brzols az osztlyokrl s felleteikrl tett egyre preczebb megllaptsok formjban jelentkezik. Fontos, hogy szrevegyk, hogy a pontosan meghatrozott s ersen tpusos (lnyegben tpusokra pt) felletek a tervezs alapvet eszkzei. A C++ felptse is ennek figyelembe vtelvel trtnt. Egy ersen tpusos fellet biztostja (egy hatrig), hogy csak kompatibilis programrszeket lehessen egytt fordtani s sszeszerkeszteni, ami lehetv teszi, hogy ezek a programrszek egymsrl viszonylag ers felttelezsekkel lhessenek. Ezeket a felttelezseket a tpusrendszer biztostja; hatsra cskkenteni lehet a futsi idej ellenrzst, ezltal n a hatkonysg s jelentsen rvidl a tbbszemlyes projektek integrlsi szakasza. Valjban az ersen tpusos felletekrl gondoskod rendszerek integrlsban szerzett nagyon pozitv tapasztalatok okozzk, hogy az integrls nem kap nagy teret e fejezetben. Nzznk egy hasonlatot. Gyakran kapcsolunk ssze klnbz szerkentyket, a csatlakoz-szabvnyok szma pedig ltszlag vgtelen. A dugaszoknl kzenfekv, hogy egyedi clra tervezettek, ami lehetetlenn teszi kt szerkezet egymssal val sszekapcsolst, hacsak nem pont erre terveztk ket, ez esetben viszont csak a helyes mdon kapcsolhatk ssze. Nem lehet egy villanyborotvt egy nagyfeszltsg aljzatba bedugni. Ha lehetne, az eredmny vagy egy slt villanyborotva vagy gsi srls lenne. A tervezk igen tallkonynak bizonyultak, hogy biztostsk, hogy az ssze nem ill hardvereszkzket ne lehessen egymssal sszedugni. A nem megfelel dugaszok ellen lehet olyan kszlkeket kszteni, melyek az aljzataikba dugott kszlkek nemkvnatos viselkedsvel szemben megvdik magukat. J plda erre egy elektromos zavarvd. Miutn a dugaszok szintjn nem garantlhat a teljes sszeegyeztethetsg, alkalmanknt szksgnk van drgbb vdramkrkre, melyek dinamikusan alkalmazkodnak a bemenethez vagy vdelmet nyjtanak azzal szemben.

Forrs: http://www.doksi.hu

24. Tervezs s programozs

983

A hasonlat majdnem pontos. A statikus tpusellenrzs a dugasz megfelelsgnek biztostsval egyenrtk, a dinamikus ellenrzs pedig az alkalmazkod/vd ramkrnek felel meg. Ha mindkt ellenrzs hinyzik, az komoly krt okozhat. Nagy rendszerekben mindkt ellenrzsi formt hasznljk. A tervezs korai szakaszban sszer lehet egyszeren kijelenteni, hogy ezt a kt kszlket ssze kell dugni, hamarosan fontos lesz azonban, hogy pontosan megmondjuk, hogyan kell sszedugni ket. Milyen garancikat ad a dugasz a viselkedssel kapcsolatban? Milyen krlmnyek kztt fordulhatnak el hibk? Milyen kltsgekkel jr a megfelel viselkeds biztostsa? A statikus tpusellenrzs hasznlata nem korltozdik a fizikai vilgra. A mrtkegysgek (pl. mter, kilogramm, msodperc) hasznlata a fizikban s a mrnki tudomnyokban az ssze nem egyeztethet elemek sszekeverst akadlyozza meg. Amikor a 23.4.3-ban a tervezs lpseit ismertettk, a tpusinformcik a 2. lpsben kerltek el (az 1. lpsben rendszerint csak felletesen foglalkozunk velk), s a 4. lpsben vltak kzponti krdss. A statikusan ellenrztt felletek a klnbz programozi csoportok ltal fejlesztett C++ programok egyttmkdsnek f biztostkai. Ezek dokumentcija (belertve a hasznlt tpusokt is) az elsdleges kapcsolattartsi eszkz az egyes programozi csoportok kztt. Ezen felletek jelentik a tervezsi folyamat legfontosabb eredmnyt s ezek llnak a tervezk s programozk kztti kapcsolat kzppontjban. A tpusok elhanyagolsa a felletek kialaktsnl olyan felptshez vezet, amely homlyba burkolja a program szerkezett s a futs idejig elhalasztja a hibk szlelst. Tegyk fel pldul, hogy egy felletet nazonost objektumokkal runk le:
// a plda dinamikus tpusellenrzst felttelez statikus ellenrzs helyett Stack s; // a verem brmilyen tpus objektumra hivatkoz mutatkat trolhat void f() { s.push(new Saab900); s.push(new Saab37B); s.pop()->takeoff(); s.pop()->takeoff();

// ez egy auttpus // ez egy repltpus // j: a Saab 37B egy replgp // futsi idej hiba: egy aut nem tud felszllni

Forrs: http://www.doksi.hu

984

Tervezs a C++ segtsgvel

Ez a fellet (a Stack::push() felletnek) komoly tlegyszerstse, ami statikus ellenrzs helyett dinamikus ellenrzsre pt. Az s verem replgpek (Plane) trolsra val, de ez a kdban rejtett maradt, gy a felhasznl ktelessge lesz e kvetelmny betartst biztostani. Egy preczebb meghatrozs egy sablon s egy virtulis fggvny a megszorts nlkli dinamikus tpusellenrzs helyett a hibk szlelst a futsi idbl tteszi a fordtsi idbe:
Stack<Plane*> s; void f() { s.push(new Saab900); s.push(new Saab37B); s.pop()->takeoff(); s.pop()->takeoff(); // a verem Plane-ekre hivatkoz mutatkat trolhat

// hiba: a Saab900 nem Plane tpus // rendben: a Saab 37B egy replgp

Hasonl krdst trgyal a 16.2.2 pont. A klnbsg a futsi idej dinamikus ellenrzs s a statikus ellenrzs kztt jelents lehet. A dinamikus ellenrzs rendszerint 3-10-szer tbb feladatot r a rendszerre. Nem szabad viszont a msik vgletbe sem esni. Nem lehet statikus ellenrzssel minden hibt elcspni. Mg a legalaposabb statikusan ellenrztt program is ki van tve a hardverhibk okozta srlsnek. A 25.4.1 pontban tovbbi pldt tallhatunk arra, hogy nem lehet tkletes statikus ellenrzst megvalstani, az idelis azonban az, ha a felletek nagy tbbsge alkalmazsszint statikus tpusokat hasznl (lsd 24.4.2). Egy msik problma, hogy a terv elvont szinten lehet tkletesen sszer, de komoly problmkat okozhat, ha nem szmol a hasznlt eszkz (esetnkben a C++) korltaival. Pldul, egy f() fggvny, mely egy paramtern a turn_right() mveletet hajtja vgre, csak akkor hozhat ltre, ha minden paramtere ugyanolyan tpus:
class Plane { // ... void turn_right(); }; class Car { // ... void turn_right(); };

Forrs: http://www.doksi.hu

24. Tervezs s programozs

985

void f(X* p) { p->turn_right(); // ... }

// milyen tpus kell legyen X?

Egyes nyelvek (mint a Smalltalk s a CLOS) megengedik kt ugyanazon mveletekkel rendelkez tpus felcserlt hasznlatt, azltal, hogy minden tpust egy kzs bzisosztly ltal kapcsolnak ssze s a futsi idre halasztjk a nv feloldst. A C++ azonban ezt (szndkosan) csak sablonokkal (template) s fordtsi idej feloldssal tmogatja. Egy nem sablon fggvny kt klnbz tpus paramtert csak akkor fogad el, ha a kt tpus automatikusan kzs tpusra konvertlhat. Az elbbi pldban teht az X-nek a Plane s Car (Aut) kzs bzisosztlynak kell lennie (pl. a Vehicle (Jrm) osztlynak). A C++-tl idegen fogalmakra pl rendszerek termszetesen brzolhatk a C++-ban is, ha a kapcsolatokra vonatkoz felttelezseket kifejezetten megadjuk. A Plane s a Car pldul (kzs bzisosztly nlkl is) osztlyhierarchiba helyezhet, ami lehetv teszi, hogy tadjunk egy Car-t vagy Plane-t tartalmaz objektumot f(X*)-nek (25.4.1). Ha azonban ezt tesszk, az gyakran nemkvnatos mennyisg mveletet s gyessget kvetel, de a sablonok hasznos eszkznek bizonyulhatnak az ilyen lekpezsek egyszerstsre. A tervezsi fogalmak s a C++ kzti rossz megfeleltets ltalban termszetellenes kinzet s kis hatkonysg kdhoz vezet. A karbantart programozk nem szeretik a nyelvben szokatlan kdot, amely ilyen rossz megfeleltetsekbl szrmazik. A tervezsi md s a megvalstshoz hasznlt nyelv kzti rossz megfeleltets hasonlt a (termszetes nyelvek esetben vgzett) szrl szra fordtshoz. Pldul az angol nyelv magyar nyelvtannal ugyanolyan nehzkes, mint a magyar nyelv angol nyelvtannal, annak pedig, aki csak a kt nyelv egyikt beszli folykonyan, mindkt vltozat rthetetlen lehet. A programban lv osztlyok a tervezs fogalmainak konkrt brzolsai. Kvetkezskppen, ha az osztlyok kztti kapcsolatok nem vilgosak, a terv alapfogalmai sem lesznek azok.

24.2.4. A programozs elkerlse


A programozs sok ms tevkenysghez kpest kltsges s elre nehezen felmrhet munka, az eredmnyl kapott kd pedig gyakran nem 100%-ig megbzhat. A programozs munkaignyes s szmos okbl a munkt ltalban az htrltatja a legkomolyabban, ha egy kdrsz nem tadsra ksz. Nos, mirt ne kszbljk ki a programozst, mint tevkenysget, egszben vve?

Forrs: http://www.doksi.hu

986

Tervezs a C++ segtsgvel

Sok vezet szmra jelents elnnyel jrna megszabadulni az arrogns, tlfizetett, szakmailag megszllott, nem megfelel ltzk stb. programozktl?4. Egy programoznak persze ez a javaslat abszurdnak hangzik. Vannak azonban olyan fontos terletek, melyeknl a programozsnak vannak vals alternatvi. Egyes esetekben lehet kzvetlenl egy magas szint brzolsbl ltrehozni a kdot; msutt a kpernyn lv alakzatok kezelsvel. Kzvetlen kezelssel hasznlhat felhasznli felleteket lehet pteni annak az idnek trt rsze alatt, ami ugyanezen felletnek hagyomnyos kddal val lershoz kellene. Ugyangy kdot kszthetnk adatbzis-kapcsolatokhoz s az adatok ilyen kapcsolatok szerinti hozzfrshez pusztn azokbl a specifikcikbl, melyek sokkal egyszerbbek, mint a mveletek kzvetlen kifejezshez szksges C++-ban vagy ms, ltalnos cl programozsi nyelven rt kd. Ilyen lersokbl/meghatrozsokbl vagy egy kzvetlen kezelfellet segtsgvel llapotautomatk (state machines) kszthetk, melyek kisebbek, gyorsabbak s jobban mkdnek, mint amit a legtbb programoz kpes volna alkotni. Ezek a mdszerek olyan terleteken hasznlhatk jl, ahol ersek az elmleti alapok (pl. matematika, llapotautomatk, relcis adatbzisok) vagy van egy ltalnos vz, amelybe be lehet gyazni kis programtredkeket (pl. grafikus felhasznli felletek, hlzatszimulcik, adatbzis-smk). Az a tny, hogy ezen mdszerek egyes lnyeges terleteken (br ezek kre korltozott) igen hasznosak lehetnek, elhitethetik velnk, hogy a hagyomnyos programozs kivltsa e mdszerek segtsgvel mr a kszbn ll. Nem gy van: ha az brzolsi mdszerek az ers elmleti vzon tllpnek, a ler nyelv szksgszeren ppoly bonyolult lesz, mint egy ltalnos cl programozsi nyelv. Nha elfelejtjk, hogy a vz, mely valamely terleten lehetsget ad a hagyomnyos programozs kikszblsre, valjban egy hagyomnyos mdon tervezett, programozott s tesztelt rendszer vagy knyvtr. A C++ s az e knyvben lert eljrsok egyik npszer felhasznlsa is pontosan ilyen rendszerek tervezse s ptse. A legrosszabb eset, ha egy ltalnos cl nyelv kifejez kpessgnek csak a tredkt biztost kompromisszumos megoldst az eredeti (korltozott) alkalmazsi terleten kvl kell felhasznlnunk. A tervezk, akik egy magas szint modellezsi szemponthoz ragaszkodnak, bosszankodnak a bonyolultsg miatt s olyan rendszerlerst ksztenek, melybl szrnysges kd jn ltre, a kznsges programozsi eljrsokat hasznl programozk pedig csaldottak lesznek a nyelvi tmogats hinya miatt, s jobb kdot csak tlzott erfesztssel s a magasszint modellek elhagysval lesznek kpesek kszteni. Nem ltom jelt annak, hogy a programozs, mint tevkenysg sikeresen kikszblhet lenne az olyan terleteken kvl, amelyeknek jl megalapozott elmlete van vagy amelyekben az alapvet programozsi mdszer egy vzhoz igazodik. A hatkonysg mindkt esetben drmai mdon lecskken, amint elhagyjuk az eredeti vzat s ltalnosabb cl mun4 Igen. n programoz vagyok.

Forrs: http://www.doksi.hu

24. Tervezs s programozs

987

kt ksrelnk meg elvgezni. Mst sznlelni csbt, de veszlyes dolog. Ugyanakkor rltsg lenne figyelmen kvl hagyni a magas szint lersokat s a kzvetlen kezelsre szolgl eljrsokat olyan terleteken, ahol azok jl megalapozottak s meglehetsen kiforrottak. Az eszkzk, knyvtrak s vzak tervezse a tervezs s programozs egyik legmagasabb rend fajtja. Jl hasznlhat matematikai alap modellt pteni egy alkalmazsi terletre az egyik legmagasabb rend elemzsfajta. Adni egy eszkzt, nyelvet, vzat stb., amely az ilyen munka eredmnyt ezrek szmra teszi elrhetv, mdot ad a programozknak s a tervezknek elkerlni a csapdt, hogy tucattermkek ksztiv vljanak. A legfontosabb, hogy az adott ler rendszer vagy alapknyvtr kpes legyen felletknt hatsosan egyttmkdni egy ltalnos cl programozsi nyelvvel. Egybknt az adott vz magban hordja korltait. Ebbl kvetkezik, hogy azon ler vagy kzvetlen kezelst biztost rendszereknek, melyek megfelelen magas szint kdot ksztenek valamilyen elfogadott ltalnos cl programozsi nyelven, nagy elnyk van. Az egyedi nyelvek hossz tvon csak ksztiknek jelentenek knnyebbsget. Ha a ltrehozott kd olyan alacsony szint, hogy a hozztett ltalnos kdot az absztrakt brzols elnyei nlkl kell megrni, elvesztjk a megbzhatsgot, a mdosthatsgot s a gazdasgossgot. Egy kdkszt rendszert lnyegben gy kell megrni, hogy egyestjk a magasabb szint lersok s a magasabb szint nyelvek erssgeit. Kihagyni az egyiket vagy a msikat annyi, mint felldozni a rendszerptk rdekeit az eszkzksztk rdekeirt. A sikeres nagy rendszerek tbbszintek, modulrisak s folytonosan fejldnek. Kvetkezskppen az ilyen rendszerek megalkotst clz sikeres erfesztsekbe sokfle nyelvet, knyvtrat, eszkzt s mdszert kell bevonni.

24.2.5. Az osztlyhierarchik kizrlagos hasznlata


Amikor gy talljuk, hogy egy jdonsg tnyleg mkdik, gyakran esnk tlzsba, s nyakra-fre azt alkalmazzuk. Ms szval, az egyes problmk esetben j megoldsrl gyakran hisszk, hogy gygyrt jelenthet majdnem minden problmra. Az osztlyhierarchik s az objektumokon vgzett tbbalak (polimorf) mveletek sok problmra adnak j megoldst, de nem minden fogalom brzolhat a legjobban egy hierarchia rszeknt, s nem minden programkomponens legjobb brzolsa egy osztlyhierarchia. Mirt nem? Az osztlyhierarchia kapcsolatokat fejez ki osztlyai kztt, az osztly pedig egy fogalmat kpvisel. Nos, akkor mi a kzs kapcsolat egy mosoly, a CD-meghajtm, Richard Strauss Don Juanjnak egy felvtele, egy sor szveg, egy mhold, az orvosi leleteim s egy valsidej ra kztt? Ha az egszet egyetlen hierarchiba helyezzk, mikzben egyetlen kzs tulajdonsguk, hogy mindnyjan programozsi elemek (objektumok), csak kevs

Forrs: http://www.doksi.hu

988

Tervezs a C++ segtsgvel

rtkkel br, zavaros rendszert hozunk ltre (15.4.5). Ha mindent egyetlen hierarchiba erltetnk, mestersges hasonlsgok jhetnek ltre s elhomlyosthatjk a valdi egyezseket. Hierarchit csak akkor szabad hasznlnunk, ha az elemzs fogalmi kzssget mutat ki, vagy ha a tervezs s programozs fed fel egyezseket a fogalmak brzolsra hasznlt szerkezetekben. Az utbbi esetben nagyon figyelnnk kell arra, hogy megklnbztessk a valdi (altpusok ltal rklt nyilvnos tulajdonsgban tkrzd) kzssget s a hasznos egyszerstseket (ami privt rklsben tkrzdik, 24.3.2.1). Ez a gondolatmenet olyan programhoz vezet, melyben szmos egymssal kapcsolatban nem lv, vagy gyengn kapcsold osztlyhierarchia van, s ezek mindegyike szorosan sszekapcsolt fogalmak halmazt kpviseli. Elvezet a konkrt osztly (25.2) fogalmhoz is, mely nem hierarchia tagja, mert egy ilyen osztlyt hierarchiba helyezve csorbtannk az osztly teljestkpessgt s fggetlensgt a rendszer tbbi rsztl. A hatkonysg szemszgbl egy osztlyhierarchia rszt kpez osztly leglnyegesebb mveleteinek virtulis fggvnyeit kell tekintennk, tovbb az osztly szmos adatnak privt (private) helyett vdettnek (protected) kell lennie. Ez persze sebezhetv teszi a tovbbi szrmaztatott osztlyok mdostsaival szemben s komoly bonyodalmakat okozhat a tesztelsnl. Ott, ahol tervezsi szempontbl szigorbb betokozst (enkapszulcit) rdemes hasznlni, nem virtulis fggvnyeket s privt adatokat kell alkalmazni (24.3.2.1). Ha egy mveletnek egyetlen paramtere van (az, amelyik az objektumot jelli), a terv ltalban torzul. Ha tbb paramterrel egyformn lehet bnni, a mvelet egy nem tag fggvnnyel brzolhat a legjobban. Ebbl nem kvetkezik, hogy az ilyen fggvnyeket globliss kell tennnk, csupn az, hogy majdnem minden ilyen nll fggvnynek egy nvtr tagjnak kell lennie (24.4).

24.3. Osztlyok
Az objektumorientlt tervezs s programozs alapvet elve, hogy a program a valsg valamely rsznek modellje. A programban az osztlyok a modellezett valsg alapfogalmait, mg ezen osztlyok objektumai a vals vilgbeli objektumokat s a megvalsts sorn ltrehozott elemeket brzoljk. Az osztlyok kzti s egy osztly rszein belli kapcsolatok elemzse a rendszer tervezsben kzponti szerepet jtszik:

Forrs: http://www.doksi.hu

24. Tervezs s programozs

989

24.3.2 24.3.3 24.3.5 24.2.4 24.3.7

rklsi kapcsolatok Tartalmazsi kapcsolatok Hasznlati kapcsolatok Beprogramozott kapcsolatok Osztlyon belli kapcsolatok

Mivel a C++ osztlyai tpusok, az osztlyok s az osztlyok kztti kapcsolatok jelents tmogatst kapnak a fordtprogram rszrl s ltalban a statikus elemzs al tartoznak. Ahhoz, hogy a rendszerben fontos legyen, egy osztlynak nemcsak hasznlhat fogalmat kell brzolnia; megfelel felletet is kell nyjtania. Az idelis osztly alapveten csekly mrtkben, de pontosan meghatrozottan fgg a tbbi programelemtl, s olyan felletet nyjt, amely azok szmra csak a felttlenl szksges informcikat biztostja (24.4.2).

24.3.1. Mit brzolnak az osztlyok?


Egy rendszerben lnyegben ktfajta osztly van: 1. Osztlyok, melyek kzvetlenl az alkalmazsi terlet fogalmait brzoljk; azaz olyan fogalmakat, melyeket a vgfelhasznlk hasznlnak a problmk s megoldsok brzolsra. 2. Osztlyok, melyek a megvalstsbl kvetkeznek, vagyis olyan fogalmak, melyeket a tervezk s programozk a megvalstsi mdszerek lersra hasznlnak. Nhny osztly, mely a megvalsts termke, brzolhat valsgos dolgot is. A rendszer hardver- s szoftver-erforrsai pldul alkalmasak arra, hogy egy programban osztlyok legyenek. (Ez azt a tnyt tkrzi, hogy egy rendszer tbb nzpontbl is tekinthet.) Ebbl kvetkezik, hogy ami valaki szmra csak a megvalsts rszletkrdse, az ms szmra a teljes alkalmazs lehet. Egy jl tervezett rendszer olyan osztlyokat tartalmaz, melyek a rendszer logikailag nll nzeteit brzoljk: 1. 2. 3. 4. 5. 6. Felhasznli szint fogalmakat brzol osztlyok (pl. autk s teherautk) Felhasznli fogalmak ltalnostsait brzol osztlyok (pl. jrmvek) Hardver-erforrsokat brzol osztlyok (pl. egy memriakezel osztly) Rendszer-erforrsokat brzol osztlyok (pl. kimeneti adatfolyamok) Ms osztlyok megvalstsa hasznlt osztlyok (pl. listk, vrakozsi sorok, zrak) Beptett adattpusok s vezrlsi szerkezetek

Forrs: http://www.doksi.hu

990

Tervezs a C++ segtsgvel

Nagyobb rendszerekben kihvst jelent a logikailag nll osztlytpusok elvlasztsa s a klnbz elvonatkoztatsi (fogalmi) szintek kztti elklnts fenntartsa. Vegynk egy egyszer pldt, ahol hrom fogalmi szintnk van: 1+2 3+4 5+6 A rendszer alkalmazsszint nzete Annak a gpnek az brzolsa, amelyen a modell fut A megvalsts alacsonyszint (programozsi nyelvi) nzete

Minl nagyobb a rendszer, annl tbb fogalmi szintre van szksg annak lershoz s annl nehezebb a szinteket meghatrozni s fenntartani. Vegyk szre, hogy az ilyen fogalmi szinteknek kzvetlen megfeleli vannak mind a termszetben, mind a ms tpus, ember ltal ltrehozott rendszerekben. Egy hz pldul gy is tekinthet, mint ami az albbiakbl ll: 1. 2. 3. 4. 5. atomok, molekulk, faanyag s tgla, padl, falak s mennyezet, szobk.

Mindaddig, amg ezek a szintek kln maradnak, fenntarthat a hz fogalmnak kvetkezetes megkzeltse, ha azonban keverjk ket, abszurditsok keletkeznek. Pldul az a kijelents, hogy Az n hzam tbbezer kil sznbl, komplex polimerbl, kb. 5000 tglbl, kt frdszobbl s 13 mennyezetbl ll, ostobasg. A programok absztrakt termszetbl addik, hogy ha egy bonyolult programrendszerrl hasonl kijelentst tesznk, azt nem mindig lehet ilyen egyszeren minsteni. Az alkalmazsi terlet valamely fogalmnak lefordtsa egy tervezsi osztlyra nem egyszer, mechanikus mvelet, gyakran jelents ttekintst kvetel. Vegyk szre, hogy magukat az adott alkalmazsi terlet fogalmait is elvonatkoztats segtsgvel rjuk le. Pldul adfizetk, szerzetesek s alkalmazottak a termszetben nem lteznek; az ilyen fogalmak csupn cmkk, melyeket egynekre aggatunk, hogy valamely rendszer szerint osztlyozzuk ket. A vals, st a kpzelt vilgbl (az irodalombl, klnsen a tudomnyos fantasztikumbl) mertett fogalmak gykeresen megvltoznak, amikor osztlyokkal brzoljuk azokat. A PC kpernyje pldul nem igazn emlkeztet az rasztalra, sokszor mgis ezzel a hasonlattal rjk le?5, a kpernyn lv ablakok pedig csak halvnyan emlkeztetnek azokra a szerkezetekre, melyek huzatot engednek be az irodba. A valsg modellezsnl nem az a lnyeg, hogy szolgai mdon kvessk azt, amit ltunk, hanem hogy a tervezs kiindul pontjaknt, sztnz forrsknt, s horgonyknt hasznljuk azt, melybe kapaszkodhatunk, amikor a program megfoghatatlan termszete azzal fenyeget, hogy legyzi azt a kpessgnket, hogy megrtsk sajt programjainkat.
5 n semmikppen sem trnk akkora rendetlensget a kpernymn.

Forrs: http://www.doksi.hu

24. Tervezs s programozs

991

Egy figyelmeztets: a kezdk gyakran nehezen talljk meg az osztlyokat, de ezt a problmt rendszerint hamarosan lekzdik, maradand kros hatsok nlkl. Ezt azonban gyakran kveti egy szakasz, amelyben az osztlyok s azok rklsi kapcsolatai ltszlag ellenrizhetetlenl megsokszorozdnak, ami hossz tvon viszont bonyolultabb s ttekinthetetlenebb teheti az eredmnyl kapott programot s ronthatja annak hatkonysgt. Nem kell minden kis rszletet kln osztllyal s minden osztlyok kztti kapcsolatot rklsi kapcsolattal brzolni. Prbljuk szben tartani, hogy a tervezs clja a rendszer megfelel rszletessggel s megfelel elvonatkoztatsi szinten val modellezse. Az egyszersg s az ltalnossg kztt nem knny az egyenslyt megtallni.

24.3.2. Osztlyhierarchik
Vegyk egy vros forgalmnak szimulcijt: meg kell hatroznunk, vrhatan mennyi idre van szksg, hogy a mentjrmvek rendeltetsi helykre rjenek. Vilgos, hogy brzolnunk kell autkat, teherautkat, mentautkat, klnfle tzoltjrmveket, rendrautkat, buszokat s gy tovbb. Az rkls mindenkppen szerephez jut, mivel a vals vilgbeli fogalmak nem lteznek elszigetelten, csak ms fogalmakkal kapcsolatban. A kapcsolatok megrtse nlkl nem rthetjk meg a fogalmakat sem. Kvetkezskppen az a modell, amely nem brzol ilyen kapcsolatokat, nem brzolja megfelelen a fogalmakat sem. Programjainkban teht szksgnk van osztlyokra a fogalmak brzolshoz, de ez nem elg; szksgnk van az osztlyok kapcsolatainak brzolsra is. Az rkls kitn mdszer hierarchikus kapcsolatok kzvetlen brzolsra. Pldnkban a mentjrmveket valsznleg klnlegesnek tekintennk s megklnbztetnnk autszer s teherautszer jrmveket is. Az osztlyhierarchia ezek alapjn gy nzne ki:

Vehicle

Car

Emergency

Truck

Police_car

Ambulance

Fire_engine

Hook_and_ladder

Forrs: http://www.doksi.hu

992

Tervezs a C++ segtsgvel

Itt az Emergency a megklnbztetett jrmvek azon jellemzit kpviseli, melyek a szimulci szempontjbl lnyegesek: megsrthet bizonyos forgalmi szablyokat, elsbbsge van az tkeresztezdsekben, diszpcser irnytja stb. me a C++ vltozat:
class Vehicle { /* ... */ }; class Emergency { /* ... */ }; class Car : public Vehicle { /* ... */ }; class Truck : public Vehicle { /* ... */ }; class Police_car : public Car , protected Emergency { /* ... */ }; class Ambulance : public Car , protected Emergency { /* ... */ }; class Fire_engine : public Truck , protected Emergency { /* ... */ }; class Hook_and_ladder : public Fire_engine { /* ... */ }; // Jrm // Megklnbztetett // Aut // Teheraut // Rendraut // Mentaut // Tzoltaut // Ltrsaut

Az rkls a C++-ban kzvetlenl brzolhat legmagasabb szint kapcsolat, s a tervezs korai szakaszban a legnagyobb a szerepe. Gyakran vlaszthatunk, hogy rklst vagy tagsgot hasznlunk-e egy kapcsolat brzolsra. Nzznk egy msik megkzeltst, mit is jelent megklnbztetett jrmnek lenni: egy jrm megklnbztetett, ha villog fnyjelzje van. Ez lehetv tenn, hogy gy egyszerstsk az osztlyhierarchit, hogy az Emergency osztlyt a Vehicle osztly egyik tagjval helyettestjk:

Vehicle { eptr }

Car

Truck

Police_car

Ambulance

Fire_engine

Hook_and_ladder

Az Emergency osztlyt most egyszeren azon osztlyok tagjaknt hasznljuk, melyeknek szksgk lehet arra, hogy megklnbztetett jrmknt szerepeljenek:

Forrs: http://www.doksi.hu

24. Tervezs s programozs

993

class Emergency { /* ... */ }; class Vehicle { protected: Emergency* eptr; /* ... */ }; // jobb: helyes hozzfrst biztost eptrhez class Car : public Vehicle { /* ... */ }; class Truck : public Vehicle { /* ... */ }; class Police_car : public Car { /* ... */ }; class Ambulance : public Car { /* ... */ }; class Fire_engine : public Truck { /* ... */ }; class Hook_and_ladder : public Fire_engine { /* ... */ };

Itt egy jrm akkor megklnbztetett, ha a Vehicle::eptr nem nulla. A sima autknl s teherautknl a Vehicle::eptr kezdrtke nulla; a tbbinl nem nulla:
Car::Car() { eptr = 0; } Police_car::Police_car() { eptr = new Emergency; } // Car konstruktor

// Police_car konstruktor

Az elemek ilyen meghatrozsa lehetv teszi, hogy egy megklnbztetett jrmvet kznsges jrmv alaktsunk t s megfordtva:
void f(Vehicle* p) { delete p->eptr; p->eptr = 0; // ... } p->eptr = new Emergency; // Ismt megklnbztetett jrm

// Tbb nem megklnbztetett jrm

Nos, melyik jobb osztlyhierarchia? Az ltalnos vlasz: Az a program, amely a vals vilg bennnket legjobban rdekl rszt a legkzvetlenebb mdon modellezi. Vagyis a modellek kzti vlasztskor a cl a valsg minl jobb megkzeltse, persze a hatkonysgra s egyszersgre val trekvs mellett. Esetnkben a knny tvltozs a kznsges s megklnbztetett jrmvek kztt szmomra nem tnik valszernek. A tzoltautk s a mentautk klnleges clra kszlt jrmvek, kikpzett szemlyzettel; eligaztsuk pedig egyedi kommunikcis berendezseket ignyel. Ez a szemllet jelzi, hogy megkln-

Forrs: http://www.doksi.hu

994

Tervezs a C++ segtsgvel

bztetett jrmnek lenni alapvet fogalom s a programban kzvetlenl kell brzolni, hogy segtse a tpusellenrzst s ms eszkzk hasznlatt. Ha olyan helyet modelleznnk, ahol a jrmvek szerepe kevsb szigoran meghatrozott mondjuk egy olyan terletet, ahol magnjrmveket szoks a mentszemlyzet helysznre szlltsra hasznlni s ahol a kommunikci elssorban hordozhat rdikon keresztl folyik, ms modellezsi mdszer megfelelbb lenne. Azok szmra, akik a forgalomszimulcit elvontnak tekintik, rdemes lehet rmutatni, hogy az rklds s a tagsg kzti vlaszts szinte minden esetben elkerlhetetlen (lsd a 24.3.3 grdtsv pldjt).

24.3.2.1. Osztlyhierarchin belli fggsek


A szrmaztatott osztlyok termszetesen fggnek bzisosztlyaiktl. Ritkbban esik sz rla, de az ellenkezje is igaz lehet?6. Ha egy osztlynak van virtulis fggvnye, az osztly fgg a belle szrmaztatott osztlyoktl, melyek e fggvny fellbrlsval az osztly egyes szolgltatsait biztostjk. Ha magnak a bzisosztlynak egy tagja hvja meg az osztly egyik virtulis fggvnyt, megint csak a bzisosztly fgg a szrmaztatott osztlyaitl, sajt megvalstsa miatt. Vegyk az albbi pldt:
class B { // ... protected: int a; public: virtual int f(); int g() { int x = f(); return x-a; } };

Mit csinl g() ? A vlaszt jelentsen befolysolja, definilja f()-et valamelyik szrmaztatott osztly. me egy vltozat, mely biztostja, hogy g() visszatrsi rtke 1 lesz:
class D1 : public B { int f() { return a+1; } };

me egy msik, amely kirja a Hell, vilg! szveget s nullval tr vissza:


class D2 : public B { int f() { cout<<"Hell, vilg!\n"; return a; } };

6 Ez a megfigyels gy sszegezhet: Az esztelensg rklhet. A gyerekeidtl kapod meg.

Forrs: http://www.doksi.hu

24. Tervezs s programozs

995

A fentiek a virtulis fggvnyekkel kapcsolatos egyik legfontosabb dolgot szemlltetik. Mirt rossz a plda? Mirt nem rna programoz soha ilyet? Azrt, mert a virtulis fggvnyek a bzisosztly felletnek rszei, s az osztly felteheten a belle szrmaz osztlyok ismerete nlkl is hasznlhat. Kvetkezskppen gy kell tudnunk meghatrozni a bzisosztly egy objektumnak elvrt viselkedst, hogy a szrmaztatott osztlyok ismerete nlkl is rhassunk programokat. Minden osztly, mely fellrja (override) a virtulis fggvnyt, e viselkedsnek egy vltozatt kell, hogy lerja. Pldul a Shape (Alak) osztly rotate() (forgats) virtulis fggvnye egy alakzatot forgat el. A szrmaztatott Circle (Kr) s Triangle (Hromszg) osztlyok rotate() fggvnyeinek a nekik megfelel tpus objektumokat kell forgatniuk, klnben megsrtennek egy alapvet felttelezst a Shape osztlyrl. A fenti B osztlyrl s a belle szrmaz D1 s D2 osztlyokrl ilyesmit nem tteleztnk fel, ezrt a plda rtelmetlen. Mg a B, D1, D2, f s g neveket is gy vlasztottuk meg, hogy brmilyen lehetsges jelentst homlyban hagyjanak. A virtulis fggvnyek elvrt viselkedsnek meghatrozsa az osztlyok tervezsnek egyik f szempontja. J neveket vlasztani az osztlyok s fggvnyek szmra szintn fontos de nem mindig knny. J vagy rossz-e egy fggs az ismeretlen (esetleg mg meg sem rt) szrmaztatott osztlyoktl? Termszetesen ez fgg a programoz szndktl. Ha egy osztlyt gy akar elszigetelni minden kls befolystl, hogy az meghatrozott mdon viselkedjen, akkor legjobb elkerlni a vdett (protected) tagokat s a virtulis fggvnyeket. Ha azonban egy vzat akar adni, amelyhez egy ksbbi programoz (vagy sajt maga nhny httel ksbb) kdot adhat hozz, a virtulis fggvnyek hasznlata ehhez elegns mdszert jelenthet, a protected tagfggvnyek pedig jl tmogatjk az ilyen megoldsokat. Ezt a megoldst vlasztottuk az adatfolyam I/O knyvtrban (21.6), az Ival_box hierarchia vgs vltozatnl (12.4.2) pedig pldt is mutattunk r. Ha egy virtulis fggvnyt csak a szrmaztatott osztlyok ltali kzvetett hasznlatra sznunk, private maradhat. Pldaknt vegyk egy tmeneti tr (puffer) egyszer sablonjt:
template<class T> class Buffer { public: void put(T); // overflow(T) meghvsa, ha az tmeneti tr megtelt T get(); // underflow() meghvsa, ha a tr res // ... private: virtual int overflow(T); virtual int underflow(); // ... };

Forrs: http://www.doksi.hu

996

Tervezs a C++ segtsgvel

A put() s get() fggvnyek a virtulis overflow(), illetve underflow() fggvnyeket hvjk meg. A felhasznl ezen fggvnyek fellrsval a klnbz ignyek szerint egy sor trtpust hatrozhat meg:
template<class T> class Circular_buffer : public Buffer<T> { int overflow(T); // krbelp, ha teli int underflow(); // ... }; template<class T> class Expanding_buffer : public Buffer<T> { int overflow(T); // megnveli az tmeneti trat, ha megtelt int underflow(); // ... };

Az overflow() s underflow() fggvnyeknek csak akkor kellene private helyett protectednek lennik, ha a szrmaztatott osztlynak szksge lenne e fggvnyek kzvetlen meghvsra.

24.3.3. Tartalmazsi kapcsolatok


Ott, ahol tartalmazst hasznlunk, egy X osztly egy objektumt kt f mdszerrel brzolhatjuk: 1. 2. Bevezetnk egy X tpus tagot. Bevezetnk egy X* tpus vagy egy X& tpus tagot.

Ha a mutat rtke sohasem vltozik, ezek a megoldsok egyenrtkek, kivve a hatkonysg krdseit s azt a mdot, ahogyan konstruktorokat s destruktorokat runk:
class X { public: X(int); // ... }; class C { X a; X* p; X& r; public:

Forrs: http://www.doksi.hu

24. Tervezs s programozs

997

};

C(int i, int j, int k) : a(i), p(new X(j)), r(*new X(k)) { } ~C() { delete p; delete &r; }

Ilyen esetekben rendszerint elnyben kell rszestennk magnak az objektumnak a tagsgt (C::a), mert ez biztostja a leggyorsabb mkdst, ez ignyli a legkevesebb helyet, s persze ezt lehet a leggyorsabban lerni. Kevesebb hiba forrsa is, mivel a tartalmaz s a tartalmazott objektum kapcsolata a ltrehozs s megsemmists szablya al tartozik (10.4.1, 12.2.2, 14.4.1, de lsd mg: 24.4.2 s 25.7). A mutatt hasznl megoldst akkor hasznljuk, ha a tartalmaz objektum lettartama alatt a tartalmazott objektumra hivatkoz mutatt meg kell vltoztatnunk:
class C2 { X* p; public: C2(int i) : p(new X(i)) { } ~C2() { delete p; } X* change(X* q) { X* t = p; p = q; return t; }

};

Egy msik ok a mutat tag hasznlatra, hogy meg akarjuk engedni a tartalmazott objektum paramterknt val szereplst:
class C3 { X* p; public: C3(X* q) : p(q) { } // ... };

Azltal, hogy olyan objektumaink vannak, melyek ms objektumokra hivatkoz mutatkat tartalmaznak, tulajdonkppen egy objektumhierarchit hoztunk ltre. Ez az osztlyhierarchik hasznlatt egyszerre helyettestheti s kiegsztheti. Mint a 24.3.2 jrm pldja mutatta, gyakran nehz tervezskor vlasztani, hogy egy osztlytulajdonsgot bzisosztlyknt vagy tagknt brzoljunk. Ha fellrt fggvnyeket kell hasznlnunk, az azt jelzi, hogy az els a jobb vlaszts, ha pedig arra van szksg, hogy egy tulajdonsgot tbb tpussal brzolhassunk, valsznleg clszerbb a msodik megolds mellett dnteni:

Forrs: http://www.doksi.hu

998

Tervezs a C++ segtsgvel

class XX : public X { /* ... */ }; class XXX : public X { /* ... */ }; void f() { C3* p1 = new C3(new X); C3* p2 = new C3(new XX); C3* p3 = new C3(new XXX); // ... }

// C3 "tartalmaz" egy X objektumot // C3 "tartalmaz" egy XX objektumot // C3 "tartalmaz" egy XXX objektumot

Itt nem lenne megfelel brzols, ha C3-at X-bl szrmaztatnnk vagy ha C3-nak egy X tpus tagja lenne, mivel a tag pontos tpust kell hasznlni. Ez a virtulis fggvnyekkel rendelkez osztlyoknl fontos, pldul egy alakzatosztlynl (2.6.2) vagy egy absztrakt halmazosztlynl (25.3). A mutat tagokra pl osztlyok egyszerstsre azokban az esetekben, amikor a tartalmaz objektum lettartama alatt csak egyetlen objektumra hivatkozunk, referencikat hasznlhatunk:
class C4 { X& r; public: C4(X& q) : r(q) { } // ... };

Akkor is szksg van mutat s referencia tpus tagokra, amikor egy objektumon osztozni kell:
X* p = new XX; C4 obj1(*p); C4 obj2(*p);

// obj1 s obj2 most osztoznak az j XX objektumon

Termszetesen a kzsen hasznlt objektumok kezelse kln vatossgot kvn, fleg a prhuzamos feldolgozst tmogat rendszerekben.

24.3.4. Tartalmazs s rkls


Az rklsi kapcsolatok fontossgnak ismeretben nem meglep, hogy ezeket a kapcsolatokat gyakran tlzottan hasznljk vagy flrertik. Ha egy D osztly nyilvnos s a B osztlybl szrmaztatott, gyakran azt mondjuk, hogy egy D valjban egy B (D is a B):

Forrs: http://www.doksi.hu

24. Tervezs s programozs

999

class B { /* ... */ }; class D : public B { /* ... */ }; // D is a kind of B: D egyfajta B

gy is kifejezhetjk, hogy az rkls egy is-a kapcsolat, vagy valamivel preczebben hogy egy D valjban egyfajta B (D is a kind of B). Ezzel szemben ha a D osztly valamelyik tagja egy B osztly, azt mondjuk, hogy van (have) egy B-je, illetve tartalmaz (contain) egy B-t:
class D { public: B b; // ... }; // D tartalmaz egy B-t

Mskppen ezt gy fejezzk ki, hogy a tagsg egy has-a kapcsolat. Adott B s D osztlyok esetben hogyan vlasszunk rkls s tagsg kztt? Vegynk egy Replgp-et s egy Motor-t. A kezdk gyakran kvncsiak, j tlet-e egy Replgp osztlyt a Motor-bl szrmaztatni. Rossz tlet, mert br a replgpnek van motorja, a replgp nem motor. Vegyk figyelembe, hogy egy replgpnek kett vagy tbb motorja is lehet. Mivel sszernek ltszik mg akkor is, ha az adott programban minden Replgp egymotoros , hasznljunk rkls helyett tagsgot. A lehet-e neki kett? krds sok esetben hasznos lehet, ha ktsg merl fel. Mint rendszerint, a programok megfoghatatlan termszete az, ami fontoss teszi ezt a vizsglatot. Ha minden osztlyt olyan knnyen elkpzelhetnnk, mint a Replgp-et s a Motor-t, knnyen elkerlhetnnk az olyan nyilvnval tvedseket, mint egy Replgp szrmaztatsa egy Motor-bl. Az ilyen tvedsek azonban igen gyakoriak klnsen azoknl, akik a szrmaztatst egyszeren egy olyan eljrsnak tekintik, mellyel programozsi nyelvi szint szerkezeteket lehet egytt hasznlni. Annak ellenre, hogy az rkls hasznlata knyelmes s a kdot is rvidti, majdnem kivtel nlkl olyan kapcsolatok kifejezsre hasznljuk, melyeket a tervezsnl pontosan meghatroztunk. Vegyk a kvetkezt:
class B { public: virtual void f(); void g(); }; class D1 { public: B b; void f(); }; // D1 tartalmaz egy B-t // nem rja fell a b.f() virtulis fggvnyt

Forrs: http://www.doksi.hu

1000

Tervezs a C++ segtsgvel

void h1(D1* pd) { B* pb = pd; pb = &pd->b; pb->g(); pd->g(); pd->b.g(); pb->f(); pd->f(); }

// hiba: nincs konverzi D1* -rl B* -ra // B::g() meghvsa // hiba: D1-nek nincs g() tagja // B::f() meghvsa (D1::f() nem rta fell) // D1::f() meghvsa

Vegyk szre, hogy egy osztly nem konvertlhat automatikusan (implicit mdon) sajt tagjv, s egy osztly, mely egy msik osztly egy tagjt tartalmazza, nem rja fell a tag virtulis fggvnyeit. Ez ellenttes a nyilvnos szrmaztatssal:
class D2 : public B { public: void f(); }; void h2(D2* pd) { B* pb = pd; pb->g(); pd->g(); pb->f(); pd->f(); } // D2 egy B // fellrja a B::f() virtulis fggvnyt

// rendben: automatikus konverzi D2* -rl B* -ra // B::g() meghvsa // B::g() meghvsa // virtulis hvs: D2::f() meghvsa // D2::f() meghvsa

A D2 plda a D1 pldhoz kpest knyelmesebb jellst biztost, s ez olyan tnyez, ami a megolds tlzott hasznlathoz vezethet. Emlkeztetni kell arra, hogy ezrt a knyelemrt a B s a D2 kzti nagyobb fggssel kell fizetni (lsd 24.3.2.1). A D2-rl B-re trtn automatikus konverzirl klnsen knny megfeledkezni. Hacsak az ilyen konverzik nem tartoznak szorosan a hasznlt osztlyok fogalombrzolshoz, kerljk a public szrmaztatst. Ha egy osztlyt egy fogalom brzolsra hasznlunk, az rkls pedig is-a kapcsolatot fejez ki, szinte biztos, hogy ppen ilyen konverzikra lesz szksgnk. Vannak esetek, melyekben rklst szeretnnk, de nem engedhetjk meg, hogy konverzi trtnjen. Vegyk egy Cfield osztly rst (controlled field, ellenrztt mez), amely egyebeken kvl futsi idej hozzfrs-ellenrzst biztost egy msik Field osztly rszre. Els rnzsre a Cfield meghatrozsa a Field-bl val szrmaztatssal ppen jnak ltszik:
class Cfield : public Field { /* ... */ };

Forrs: http://www.doksi.hu

24. Tervezs s programozs

1001

Ez kifejezi, hogy egy Cfield valjban egyfajta Field, knyelmesebb jellst biztost, amikor olyan Cfield fggvnyt runk, mely a Cfield Field rsznek egy tagjt hasznlja s ez a legfontosabb megengedi, hogy egy Cfield fellrja a Field virtulis fggvnyeit. Az a baj, hogy a Cfield*-rl Field*-ra trtn konverzi, amit a Cfield deklarcija sugall, meghist minden, a Field-hez val hozzfrs ellenrzsre irnyul ksrletet:
void g(Cfield* p) { *p = "asdf"; Field* q = p; *q = "asdf";

// a Field elrse a Cfield rtkad opertorval vezrelt: // p->Cfield::operator=("asdf") // automatikus talakts Cfield*-rl Field*-ra // hopp! nincs Cfield-en keresztli ellenrzs

Egy megolds az lehetne, hogy gy hatrozzuk meg a Cfield-et, mint amelynek Field egy tagja, de ha ezt tesszk, eleve kizrjuk, hogy Cfield fellrhassa Field virtulis fggvnyeit. Jobb megolds, ha private rkldst hasznlunk:
class Cfield : private Field { /* ... */ };

Tervezsi szempontbl a privt szrmaztats egyenrtk a tartalmazssal, kivve a fellrs (alkalmanknt lnyeges) krdst. E mdszer fontos felhasznlsi terlete egy osztly nyilvnos szrmaztatsa egy felletet ler absztrakt bzisosztlybl, valamint a privt vagy vdett szrmaztats egy konkrt osztlybl, implementci cljbl (2.5.4, 12.3, 25.3). Mivel a private s protected szrmaztatsbl kvetkez rklds nem tkrzdik a szrmaztatott osztly tpusban, nha implementcis rkldsnek nevezzk a nyilvnos szrmaztatssal szemben, melynl a bzisosztly fellete rkldik s az alaptpus automatikus konverzija megengedett. Az utbbira nha mint altpusksztsre (subtyping) vagy felletrklsre (interface inheritance) hivatkozunk. gy is megfogalmazhatjuk ezt, hogy rmutatunk, egy szrmaztatott osztly objektuma hasznlhat kell legyen mindentt, ahol bzisosztlynak objektumai hasznlhatk. Ezt nha Liskov-fle helyettestsi elvnek (Liskov Substitution Principle) nevezik (23.6 [Liskov, 1987]). A nyilvnos/vdett/privt megklnbztets kzvetlenl tmogatja ezt, amikor tbbalak tpusokat mutatkon s hivatkozsokon keresztl kezelnk.

Forrs: http://www.doksi.hu

1002

Tervezs a C++ segtsgvel

24.3.4.1. Tag vagy hierarchia?


Hogy tovbb vizsgljuk a tartalmazst s rklst magval von tervezsi vlasztsokat, vegyk egy grdtsv (scrollbar) brzolst egy interaktv grafikus rendszerben s azt, hogyan kapcsolhatunk egy grdtsvot egy ablakhoz. Ktfle grdtsvra van szksgnk: vzszintesre s fgglegesre. Ezt kt tpussal brzolhatjuk Horizontal_scrollbar s Vertical_scrollbar vagy egyetlen olyan Scrollbar tpussal, mely paramterknt megkapja, vzszintes vagy fggleges-e. Az elbbi vlaszts kvetkezmnye, hogy egy harmadik tpusra, a sima Scrollbar-ra is szksg van, mint a kt grdtsv-tpus alaptpusra. Az utbbi vlaszts azt eredmnyezi, hogy egy kln paramtert kell hasznlnunk s rtkeket kell vlasztanunk a kt tpus brzolsra:
enum Orientation { horizontal, vertical };

Vlasztsunk meghatrozza, milyen mdostsok szksgesek a rendszer bvtshez. Lehet, hogy be kell vezetnnk egy harmadik tpus grdtsvot. Eredetileg gy gondolhattuk, elg ktfle grdtsv (egy ablaknak vgl is csak kt dimenzija van), de szinte minden esetben lehetsgesek bvtsek, melyek az jratervezs szksgessgt vonjk maguk utn. Pldul lehet, hogy valaki a kt grdtsv helyett egy navigl gombot szeretne hasznlni. Egy ilyen gomb klnbz irny grgetst okozna, aszerint, hol nyomja meg a felhasznl. Fell kzpen nyomva felfel grget, bal oldalon kzpen nyomva balra, mg a bal fels sarkot nyomva balra felfel. Az ilyen gombok nem szokatlanok. A grdtsvok tovbbfejlesztett vltozatainak tekinthetk, s klnsen illenek olyan programokhoz, melyekben a grgetett adatok nem csupn szvegek, hanem kpek. Ha egy programba, melyben egy hrom grdtsvbl ll osztlyhierarchia van, egy navigl gombot tesznk, ltre kell hoznunk hozz egy j osztlyt, de a grdtsv rgi kdjt nem kell mdostanunk:

Scrollbar

Horizontal_scrollbar

Vertical_scrollbar

Navigation_button

Forrs: http://www.doksi.hu

24. Tervezs s programozs

1003

Ez a hierarchikus megolds szp oldala. Ha paramterben adjuk meg a grgets irnyt, a grdtsv-objektumokban tpusmezknek, a grdtsv-tagfggvnyek kdjban pedig switch utastsoknak kell szerepelnik. Ez azt jelenti, hogy vlasztanunk kell, deklarcik vagy kd ltal fejezzk ki a rendszer szerkezetnek ezt a vonst. Az elbbi nveli a statikus ellenrzs fokt s azt az adatmennyisget, melyet az eszkzknek fel kell dolgozniuk. Az utbbi a futsi idre halasztja el a dntseket s az egyes fggvnyek mdostsval anlkl tesz lehetv vltoztatsokat, hogy befolysoln a rendszer tfog szerkezett a tpusellenrz s ms eszkzk szempontjbl. A legtbb helyzetben azt javaslom, hasznljunk osztlyhierarchit a fogalmak kapcsolatainak kzvetlen modellezsre. Az egyetlen tpust hasznl megolds megknnyti a grdtsv fajtjt ler adatok trolst s tovbbtst:
void helper(Orientation oo) { // ... p = new Scrollbar(oo); // ... } void me() { helper(horizontal); // ... }

Ez az brzolsmd megknnyti a grgets irnynak futsi idben trtn megvltoztatst. Nem valszn, hogy ennek itt nagy jelentsge van, de ms hasonl pldk esetben fontos lehet. A lnyeg, hogy mindig vlasztani kell, s a vlaszts gyakran nem knny.

24.3.4.2. Tartalmazs vagy hierarchia?


Most vegyk azt a krdst, hogyan kapcsoljunk egy grdtsvot egy ablakhoz. Ha egy Window_with_scrollbar-t gy tekintnk, mint ami egyszerre Window s Scrollbar is, valami ilyesmit kapunk:
class Window_with_scrollbar : public Window, public Scrollbar { // ... };

Forrs: http://www.doksi.hu

1004

Tervezs a C++ segtsgvel

Ez megengedi, hogy egy Window_with_scrollbar gy is viselkedhessen, mint egy Scrollbar s gy is, mint egy Window, de arra knyszert, hogy az egyetlen tpust hasznl megoldst hasznljuk. Msrszt, ha a Window_with_scrollbar-t egy Scrollbar-ral rendelkez Window-nak tekintjk, valami ilyesmit kapunk:
class Window_with_scrollbar : public Window { // ... Scrollbar* sb; public: Window_with_scrollbar(Scrollbar* p, /* ... */) : Window(/* ...*/), sb(p) { /* ... */ } // ... };

Ez megengedi, hogy a grdtsv-hierarchia megoldst hasznljuk. Ha a grdtsvot paramterben adjuk t, az ablak figyelmen kvl hagyhatja annak pontos tpust. St, egy Scrollbar-t ahhoz hasonlan is tadhatunk, ahogy az Orientation-t a 24.3.4.1 pontban. Ha arra van szksg, hogy a Window_with_scrollbar grdtsvknt mkdjn, hozztehetnk egy konverzis mveletet:
Window_with_scrollbar::operator Scrollbar&() { return *sb; }

n azt rszestem elnyben, ha az ablak a grdtsvot tartalmazza. Knnyebb egy olyan ablakot elkpzelni, melynek grdtsvja van, mint egy olyat, amely amellett, hogy ablak, mg grdtsv is. Kedvenc mdszerem az, hogy olyan grdtsvot hatrozok meg, amely egy klnleges ablak, s ezt tartalmazza egy, a grdtsv-szolgltatsokat ignyl ablak. Ez a tartalmazs hasznlatt ignyli, de emellett szl egy msik rv is, mely a lehet neki kett is szablybl kvetkezik (24.3.4). Mivel nincs logikus indok, mirt ne lehetne egy ablaknak kt grdtsvja (valjban sok ablaknak van vzszintes s fggleges is), nem ktelez a Window_with_scrollbar-t a Scrollbar-bl szrmaztatni. Vegyk szre, hogy ismeretlen osztlybl nem lehet szrmaztatni, fordtskor ismerni kell a bzisosztly pontos tpust (12.2). Msrszt, ha egy osztly egy tulajdonsgt paramterben adjuk t konstruktornak, akkor valahol az osztlyban kell, hogy legyen egy tag, mely azt brzolja. Ha azonban ez a tag egy mutat vagy referencia, akkor a taghoz megadott osztlybl szrmaztatott osztly egy objektumt is tadhatjuk. Az elz pldban pldul a Scrollbar* sb tagja mutathat egy olyan Scrollbar tpusra, mint a Navigation_button, amely a Scrollbar* felhasznlja szmra ismeretlen.

Forrs: http://www.doksi.hu

24. Tervezs s programozs

1005

24.3.5. Hasznlati kapcsolatok


Annak ismerete, hogy egy osztly milyen ms osztlyokat hasznl s milyen mdon, gyakran ltfontossg a programszerkezet kifejezse s megrtse szempontjbl. Az ilyen fggseket a C++ csak rejtetten (implicit mdon) tmogatja. Egy osztly csak olyan elemeket hasznlhat, melyeket (valahol) deklarltak, de azok felsorolsa nem szerepel a C++ forrskdban. Eszkzk szksgesek (vagy megfelel eszkzk hinyban gondos elolvass) az ilyen adatok kinyershez. A mdok, ahogy egy X osztly egy Y osztlyt felhasznlhat, tbbflekppen osztlyozhatk. me egy lehetsges vltozat: X hasznlja az Y nevet. X hasznlja Y-t. X meghvja Y egy tagfggvnyt. X olvassa Y egy tagjt. X rja Y egy tagjt. X ltrehoz egy Y-t. X lefoglal egy auto vagy static Y vltozt. X a new segtsgvel ltrehoz egy Y-t. veszi egy Y mrett. Az utols azrt kerlt kln kategriba, mivel ehhez ismerni kell az osztly deklarcijt, de fggetlen a konstruktoroktl. Az Y nevnek hasznlata szintn kln mdszert jelent, mert ehhez nmagban pldul egy Y* deklarlsnl vagy Y-nak egy kls fggvny deklarcijban val emltsnl egyltaln nem kell hozzfrni Y deklarcijhoz (5.7):
class Y; // Y az osztly neve Y* p; extern Y f(const Y&);

Gyakran fontos, hogy klnbsget tegynk egy osztly felletnek (az osztly deklarcijnak) s az osztly megvalstsnak fggsei kztt. Egy jl tervezett rendszerben az utbbinak ltalban jval tbb fggsge van, s azok egy felhasznl szmra sokkal kevsb rdekesek, mint az osztly deklarcijnak fggsei (24.4.2). A tervezskor arra kell trekednnk, hogy a felletek fggseinek szmt cskkentsk, mert ezekbl az osztly felhasznlinak fggsei lesznek (8.2.4.1, 9.3.2, 12.4.1.1 s 24.4). A C++ nem kvnja meg egy osztly ksztjtl, hogy rszletesen lerja, milyen ms osztlyokat s hogyan hasznl fel. Ennek egyik oka, hogy a legjelentsebb osztlyok oly sok ms osztlytl fggnek, hogy az olvashatsg miatt ezen osztlyok rvidtett felsorolsra pldul egy #include utastsra lenne szksg. A msik ok, hogy az ilyen fggsek osztlyo-

Forrs: http://www.doksi.hu

1006

Tervezs a C++ segtsgvel

zsa nem a programozsi nyelv krdse. Az, hogy a hasznlja (use) tpus fggseket pontosan hogyan szemlljk, fgg a tervez, a programoz vagy az eszkz cljtl, az pedig, hogy mely fggsek az rdekesek, fgghet az adott fejlesztkrnyezettl is.

24.3.6. Beprogramozott kapcsolatok


Egy programozsi nyelv nem kpes kzvetlenl tmogatni s nem is szabad, hogy tmogassa minden tervezsi mdszer minden fogalmt. Hasonlkppen egy tervezsi nyelvnek sem clszer tmogatnia minden programozsi nyelv minden tulajdonsgt. Egy tervezsi nyelv legyen gazdagabb s kevsb trdjn a rszletekkel, mint amennyire ez egy rendszerprogramozsi nyelvnl szksges. Megfordtva, egy programozsi nyelvnek kpesnek kell lennie tbbfle tervezsi filozfia tmogatsra, klnben csorbul az alkalmazhatsga. Ha egy programozsi nyelvben nincs lehetsg egy tervezsi fogalom kzvetlen brzolsra, nem szabad hagyomnyos lekpezst alkalmazni a tervezsi szerkezetek s a programozsi nyelv szerkezetei kztt. A tervezsi mdszer pldul hasznlhatja a delegls (delegation) fogalmt, vagyis a terv meghatrozhatja, hogy minden olyan mveletet, amely A osztlyhoz nincs definilva, egy p mutat ltal mutatott B osztlybeli objektum szolgltasson. A C++ ezt nem tudja kzvetlenl kifejezni, de rendelkezsre llnak olyan szerkezetek, melyek segtsgvel knny elkpzelni egy programot, mely a fogalombl kdot hoz ltre. Vegyk az albbiakat:
class B { // ... void f(); void g(); void h(); }; class A { B* p; // ... void f(); void ff(); };

Annak meghatrozsa, hogy A osztly az A::p mutatn keresztl truhzza (deleglja) a feladatot B-re, ilyen kdot eredmnyezne:

Forrs: http://www.doksi.hu

24. Tervezs s programozs

1007

class A { B* p; // ... void f(); void ff(); void g() { p->g(); } void h() { p->h(); };

// delegls p-n keresztl

// g() delegls // h() delegls

Egy programoz szmra nyilvnval, hogy mi trtnik itt, de vilgos, hogy egy tervezsi fogalom kddal val utnzsa kevesebb, mint valamely egy az egyhez megfeleltets. Az ilyen beprogramozott kapcsolatokat a programozsi nyelv nem rti olyan jl, ezrt nehezebb azokat eszkzkkel tmogatni. A szabvnyos eszkzk pldul nem ismernk fel, hogy az A::p-n keresztl A feladatt ruhztuk t B-re, nem pedig ms clra hasznltuk a B*-ot. Ahol csak lehetsges, hasznljunk egy az egyhez lekpezst a tervezs s a programozsi nyelv fogalmai kztt. Az ilyen lekpezs biztostja az egyszersget, s azt, hogy a program valban tkrzi a tervezst, hogy a programozk s az eszkzk hasznosthassk annak fogalmait. A beprogramozott kapcsolatokkal rendelkez osztlyok kifejezst a nyelv konverzis mveletekkel segti. Ez azt jelenti, hogy az X::operator Y() konverzis opertor meghatrozza, hogy ahol elfogadhat egy Y, ott hasznlhatunk egy X-et is (11.4.1). A Y::Y(X) konstruktor ugyanezt a kapcsolatot fejezi ki. Vegyk szre, hogy a konverzis opertorok (s a konstruktorok) j objektumokat hoznak ltre, nem pedig ltez objektumok tpust vltoztatjk meg. Egy konverzis fggvny deklarlsa Y-ra nem ms, mint egy Y-t visszaad fggvny rejtett alkalmazsnak egy mdja. Mivel a konstruktorok s konverzis opertorok ltal definilt talaktsok automatikus alkalmazsa csalka lehet, nha hasznos, ha a terven bell kln elemezzk azokat. Fontos, hogy biztostsuk, hogy egy program konverzis diagramjai ne tartalmazzanak ciklusokat. Ha tartalmaznak, az ebbl add tbbrtelmsgek megakadlyozzk, hogy a ciklusokban szerepl tpusokat egytt hasznlhassuk:
class Rational; class Big_int { public: friend Big_int operator+(Big_int,Big_int); operator Rational(); // ... };

Forrs: http://www.doksi.hu

1008

Tervezs a C++ segtsgvel

class Rational { public: friend Rational operator+(Rational,Rational); operator Big_int(); // ... };

A Rational s a Big_int tpusok nem fognak olyan grdlkenyen egyttmkdni, mint remlnnk:
void f(Rational r, Big_int i) { g(r+i); // hiba, tbbrtelm: operator+(r,Rational(i)) vagy operator+(Big_int(r),i) ? g(r+Rational(i)); // explicit felolds g(Big_int(r)+i); // msik explicit felolds }

Az ilyen klcsns konverzikat elkerlhetjk, ha legalbb nhnyat kzlk explicitt tesznk. A Big_int talaktsa Rational-l pldul konverzis opertor helyett definilhat lett volna make_Rational()-knt, az sszeadst pedig g(Big_int,i)-re lehetett volna feloldani. Ahol nem kerlhetk el az ilyen klcsns konverzik, a keletkezett ellentmondsokat vagy explicit talaktsokkal (lsd fent), vagy ktoperandus opertorok (pl. +) tbb kln vltozatnak deklarlsval kell feloldanunk.

24.3.7. Osztlyon belli kapcsolatok


Az osztlyok a megvalsts mdjt (s a rossz megoldsokat) csaknem teljes egszben elrejthetik s nha el is kell rejtenik. A legtbb osztly objektumainak azonban szablyos szerkezete van s oly mdon kezelhet, amit meglehetsen knny lerni. Egy osztly valamely objektuma alobjektumok (tagok) gyjtemnye, melyek kzl sok ms objektumokra mutat vagy hivatkozik. Egy objektum teht gy tekinthet, mint egy objektumfa gykere, a benne szerepl objektumok pedig mint egy objektum-hierarchia alkotelemei, ami az osztlyhierarchia kiegsztje (lsd 25.3.2.1). Vegynk pldul egy nagyon egyszer String-et:
class String { int sz; char* p; public: String(const char* q); ~String(); // ... };

Forrs: http://www.doksi.hu

24. Tervezs s programozs

1009

Egy String objektum grafikusan gy brzolhat:

...elemek...\0 int sz ; char* p ;

24.3.7.1. Invarinsok
A tagok s a tagok ltal hivatkozott objektumok rtkeit gyjtnven az objektum llapotnak (vagy egyszeren rtknek) nevezzk. Egy osztly tervezsnl fontos az objektumok pontosan definilt llapotba hozsa (kezdeti rtkads/ltrehozs), ezen llapot fenntartsa a mveletek vgrehajtsa sorn, s vgl az objektumok megfelel megsemmistse. Az a tulajdonsg, amely pontosan definilt teszi egy objektum llapott, az objektum invarinsa (llapotbiztostja, invariant). A kezdeti rtkads clja teht az objektumot abba az llapotba helyezni, amelyet az invarins ler. Ezt ltalban egy konstruktorral vgezzk. Egy osztlyon vgzett minden mvelet felttelezheti, hogy belpskor rvnyesnek tallja az llapotot (vagyis hogy az invarins logikai rtke igaz) s kilpskor ugyanez a helyzet ll fenn. Az invarinst vgl a destruktor rvnytelenti, az objektum megsemmistsvel. A String::String(const char*) konstruktor pldul biztostja, hogy p egy legalbb sz+1 elem tmbre mutat, ahol sz-nek rtelmes rtke van s p[sz]==0. Ezt az lltst minden karakterlnc-mveletnek igaznak kell hagynia. Az osztlytervezsben a legtbb szakrtelem ahhoz kell, hogy az adott osztlyt elg egyszerv tegyk, hogy hasznlhat, egyszeren kifejezhet invarinssal valsthassuk meg. Elg knny kijelenteni, hogy minden osztlynak szksge van invarinsra. Ami nehz, egy hasznlhat invarinssal elhozakodni, amely knnyen rthet s nem jr elfogadhatatlan megszortsokkal a ksztre vagy a mveletek hatkonysgra vonatkozan. Vegyk szre, hogy az invarinst itt egy olyan kdrszlet megjellsre hasznljuk, melyet futtathatunk egy objektum llapotnak ellenrzsre. Vilgos, hogy be lehetne vezetni egy szigorbb, matematikaibb s bizonyos krnyezetben megfelelbb fogalmat is. Az invarins, ahogyan itt trgyaljuk, az objektum llapotnak egyfajta gyakorlati s emiatt ltalban gazdasgos, de logikailag nem tkletes ellenrzse.

Forrs: http://www.doksi.hu

1010

Tervezs a C++ segtsgvel

Az invarins fogalma Floyd, Naur s Hoare elfelttelekkel s utfelttelekkel foglalkoz munkibl ered s lnyegben minden absztrakt adattpusokrl s programhelyessg-ellenrzsrl szl munka emlti, amit az utbbi 30 vben rtak, gy a C hibakeressnek is fontos elemt kpezi. Az invarins ellenrzsre a tagfggvnyek vgrehajtsa kzben ltalban nem kerl sor. Azok a fggvnyek, melyek meghvhatk, mikzben az invarins rvnytelen, nem lehetnek a nyilvnos fellet rszei; e clra a privt s vdett fggvnyek szolglhatnak. Hogyan fejezhetjk ki egy C++ programban az invarins fogalmt? Erre egy egyszer md egy invarins-ellenrz fggvnyt megadni s a nyilvnos mveletekbe e fggvny meghvst beiktatni:
class String { int sz; char* p; public: class Range {}; class Invariant {}; enum { TOO_LARGE = 16000 }; void check(); String(const char* q); String(const String&); ~String(); char& operator[](int i); int size() { return sz; } }; // ...

// kivtelosztly // kivtelosztly // mrethatr // llapot-ellenrzs

void String::check() { if (p==0 || sz<0 || TOO_LARGE<=sz || p[sz]) throw Invariant(); } char& String::operator[](int i) { check(); if (i<0 || sz<=i) throw Range(); // itt vgezzk a tnyleges munkt check(); return p[i]; }

// ellenrzs belpskor // ellenrzs kilpskor

Forrs: http://www.doksi.hu

24. Tervezs s programozs

1011

Ez szpen fog mkdni s alig jelent munkt a programoz szmra. Viszont egy egyszer osztlynl, mint a String, az llapotellenrzs dnten befolysolja a futsi idt s lehet, hogy mg a kd mrett is. Ezrt a programozk gyakran csak hibakeress alatt hajtjk vgre az invarins ellenrzst:
inline void String::check() { #ifndef NDEBUG if (p==0 || sz<0 || TOO_LARGE<=sz || p[sz]) throw Invariant(); #endif }

Itt az NDEBUG makrt hasznljuk, hasonl mdon, mint ahogy azt a szabvnyos C assert() makr hasznlja. Az NDEBUG rendszerint azt jelzi, hogy nem trtnik hibakeress. Az invarinsok megadsa s hibakeress alatti hasznlata felbecslhetetlen segtsget jelent a kd belvsnl s ami mg fontosabb akkor, amikor az osztlyok ltal brzolt fogalmakat pontosan meghatrozott s szablyoss tesszk. A lnyeg az, hogy az invarinsok beptsvel az osztlyokat ms szempontbl vizsglhatjuk s a kdban ellenrzst helyezhetnk el. Mind a kett nveli a valsznsgt, hogy megtalljuk a hinyossgokat, kvetkezetlensgeket s tvedseket.

24.3.7.2. Felttelezsek
Az invarins kd egyfajta felttelezs (assertion), ami viszont nem ms, mint annak a kijelentse, hogy egy adott logikai felttelnek teljeslnie kell. Az a krds, mit kell tenni, amikor nem teljesl. A C standard knyvtra s kvetkezskppen a C++ standard knyvtra tartalmazza az assert() makrt a <cassert>-ben vagy <assert.h>-ban. Az assert() kirtkeli a paramtert s meghvja az abort()-ot, ha az eredmny nulla (vagyis hamis):
void f(int* p) { assert(p!=0); // ... }

// felttelezi, hogy p!=0; abort() meghvsa, ha p nulla

A program megszaktsa eltt az assert() kirja sajt forrsfjljnak nevt s annak a sornak a szmt, amelyben elfordult. Ezltal az assert() hasznos hibakeres eszkzt jelent. Az NDEBUG belltsa rendszerint fordti utastsok segtsgvel trtnik, fordtsi egys-

Forrs: http://www.doksi.hu

1012

Tervezs a C++ segtsgvel

genknt. Ebbl kvetkezik, hogy az assert() hasznlata tilos olyan helyben kifejtett (inline) fggvnyekben s sablon fggvnyekben, melyek tbb fordtsi egysgben is elfordulnak, hacsak nem fordtunk igen nagy gondot az NDEBUG kvetkezetes belltsra (9.2.3). Mint minden makr-varzslat, az NDEBUG hasznlata is tl alacsony szint, rendetlen s hibaforrst jelenthet. ltalban j tlet, ha mg a legjobban ellenrztt programban is hagyunk nhny ellenrz kdot; az NDEBUG viszont erre nem nagyon alkalmas, s az abort() meghvsa is ritkn fogadhat el a vgleges kdban. A msik megolds egy Assert() sablon hasznlata, mely a programbl val kilps helyett egy kivtelt vlt ki, gy kvnsgra a vgleges kdban bennmaradhatnak az ellenrzsek. Sajnos a standard knyvtr nem gondoskodik ilyen Assert()-rl, de mi knnyen elkszthetjk:
template<class X, class A> inline void Assert(A assertion) { if (!assertion) throw X(); }

Az Assert() egy X() kivtelt vlt ki, ha az assertion hamis:


class Bad_arg { }; void f(int* p) { Assert<Bad_arg>(p!=0); // ... }

// felttelezi, hogy p!=0; Bad_arg kivtelt vlt ki, hacsak p!=0

Az ellenrzsben a felttelt pontosan meg kell hatroznunk, teht ha csak hibakeress alatt akarunk ellenrizni, jeleznnk kell e szndkunkat:
void f2(int* p) { Assert<Bad_arg>(NDEBUG || p!=0); // ... }

// vagy nem hibakeress folyik vagy p!=0

A || hasznlata az ellenrzsben && helyett meglepnek tnhet, de az Assert<E> (a||b) a !(a||b)-t ellenrzi, ami !a&&!b. Az NDEBUG ilyen mdon val hasznlata megkveteli, hogy az NDEBUG-nak megfelel rtket adjunk, aszerint, hogy akarunk-e hibakeresst vgezni vagy sem. A C++ egyes vltozatai alaprtelmezs szerint nem teszik ezt meg neknk, ezrt jobb sajt rtket hasznlni:

Forrs: http://www.doksi.hu

24. Tervezs s programozs

1013

#ifdef NDEBUG const bool ARG_CHECK = false; #else const bool ARG_CHECK = true; #endif void f3(int* p) { Assert<Bad_arg>(!ARG_CHECK || p!=0); // ... }

// nem hibakeress: ellenrzs kikapcsolsa // hibakeress

// vagy nem hibakeress folyik vagy p!=0

Ha egy ellenrzssel kapcsolatos kivtelt nem kapunk el, az Assert() lelltja a programot (terminate()), hasonlan ahhoz, ahogyan a megfelel assert() abort()-tal jrna. Egy kivtelkezel azonban kpes lehet valamilyen kevsb durva beavatkozsra. A valsgos mret programokban azon veszem szre magamat, hogy egyes csoportokban be- s kikapcsolom a felttelezseket, aszerint, hogy kell-e tesztelni. Ennek az eljrsnak az NDEBUG hasznlata a legnyersebb formja. A fejleszts elejn a legtbb ellenrzs be van kapcsolva, mg az tadott kdban csak a legfontosabbak megengedettek. Ez akkor rhet el a legknnyebben, ha a tnyleges ellenrzsek kt rszbl llnak, ahol az els egy megenged felttel (mint az ARG_CHECK), s csak a msodik maga a felttelezs. Amennyiben a megenged felttel konstans kifejezs, az egsz ellenrzs kimarad a fordtsbl, ha nincs bekapcsolva. Lehet azonban vltoz is, mely a hibakeress szksgletei szerint futsi idben be- s kikapcsolhat:
bool string_check = true; inline void String::check() { Assert<Invariant>(!string_check || (p && 0<=sz && sz<TOO_LARGE && p[sz]==0)); } void f() { String s = "csoda"; // a karakterlncokat itt ellenrizzk string_check = false; // itt a karakterlncokat nem ellenrizzk }

Forrs: http://www.doksi.hu

1014

Tervezs a C++ segtsgvel

Termszetesen ilyen esetekben ltrejn a megfelel programkd, teht gyelnnk kell, nehogy a program meghzzon az ilyen ellenrzsek kiterjedt hasznlattl. Ha azt mondjuk:
Assert<E>(a);

az egyszeren egy msik mdja ennek:


if (!a) throw E();

gy viszont felmerl a krds: minek bajldjunk az Assert()-tel az utasts kzvetlen kirsa helyett? Azrt, mert az Assert() hasznlata nyilvnvalv teszi a tervez szndkt. Azt fejezi ki, hogy valamirl felttelezzk, hogy mindig igaz. Ez nem a program logikjnak lnyegtelen rsze, hanem rtkes informci a program olvasja szmra. Gyakorlati elnye, hogy egy assert()-et vagy Assert()-et knny megtallni, mg a kivteleket kivlt feltteles utastsokat nehz. Az Assert() ltalnosthat olyan kivtelek kivltsra is, melyek paramtereket vagy kivtel-vltozkat vehetnek t:
template<class A, class E> inline void Assert(A assertion, E except) { if (!assertion) throw except; } struct Bad_g_arg { int* p; Bad_g_arg(int* pp) : p(pp) { } }; bool g_check = true; int g_max = 100; void g(int* p, exception e) { Assert(!g_check || p!=0, e); Assert(!g_check || (0<*p&&*p<=g_max),Bad_g_arg(p)); // ... }

// a mutat rvnyes // az rtk sszer

Sok programban dnt fontossg, hogy ne jjjn ltre kd olyan Assert() esetn, ahol az ellenrzs a fordts sorn kirtkelhet. Sajnos egyes fordtk az ltalnostott Assert()-nl

Forrs: http://www.doksi.hu

24. Tervezs s programozs

1015

ezt kptelenek elrni. Kvetkezskppen a ktparamter Assert()-et csak akkor hasznljuk, ha a kivtel nem E() alak vagy ha valamilyen, az ellenrztt rtktl fggetlen kdot kell kszteni. A 23.4.3.5 pontban emltettk, hogy az osztlyhierarchia tszervezsnek kt legkznsgesebb formja az osztly ketthastsa, illetve kt osztly kzs rsznek kiemelse egy bzisosztlyba. Az tszervezs lehetsgt mindkt esetben megfelel invarinsokkal biztosthatjuk. Viszont ha sszehasonltjuk az invarinst a mveletek kdjval, egy hastsra rett osztlyban az llapotellenrzst flslegesnek tallhatjuk. Ilyen esetekben a mveletek rszhalmazai csak az objektum llapotnak rszhalmazaihoz fognak hozzfrni. Megfordtva, azok az osztlyok, melyek sszefslhetk, hasonl invarinsokkal rendelkeznek akkor is, ha megvalstsuk klnbzik.

24.3.7.3. Elfelttelek s utfelttelek


A felttelezsek (assert) egyik npszer hasznlata egy fggvny elfeltteleinek s utfeltteleinek kifejezse. Ez azt jelenti, hogy ellenrizzk a bemenetre vonatkoz alapfelttelezsek rvnyessgt s azt, hogy kilpskor a fggvny a programot a vrt llapotban hagyja-e. Sajnos amit a felttelezssel ki szeretnnk fejezni, gyakran magasabb szinten van, mint aminek a hatkony kifejezsre a programozsi nyelv lehetsget ad:
template<class Ran> void sort(Ran first, Ran last) { Assert<Bad_sequence>("A [first,last) rvnyes sorozat"); // lkd // ... rendez algoritmus ... } Assert<Failed_sort>("A [first,last) nvekv sorrend"); // lkd

Ez alapvet problma. Amit egy programrl feltteleznk, az matematikai alap, magasabb szint nyelven fejezhet ki a legjobban, nem azon az algoritmusokra pl programozsi nyelven, amelyben a programot rjuk. Az invarinsok meghatrozshoz hasonlan a felttelezsek megfogalmazsa is bizonyos fok gyessget ignyel, mert az ellenrizni kvnt lltst gy kell megadnunk, hogy az egy algoritmussal valban ellenrizhet legyen:

Forrs: http://www.doksi.hu

1016

Tervezs a C++ segtsgvel

template<class Ran> void sort(Ran first, Ran last) { // A [first,last) rvnyes sorozat; a hihetsg ellenrzse: Assert<Bad_sequence>(NDEBUG || first<=last); // ... rendez algoritmus ... // A [first,last) nvekv sorrend; prba-ellenrzs: Assert<Failed_sort>(NDEBUG || (last-first<2 || (*first<=last[-1] && *first<=first[(last-first)/2] && first[(last-first)/2]<=last[-1])));

n gyakran egyszerbbnek tallom a kznsges kdellenrz paramterek s eredmnyek hasznlatt, mint a felttelezsek sszelltst. Fontos azonban, hogy megprbljuk a vals (idelis) el- s utfeltteleket kifejezni s legalbb megjegyzsek formjban dokumentlni azokat mieltt olyan, kevsb absztrakt mdon brzolnnk, ami a programozsi nyelven hatkonyan kifejezhet. Az elfelttelek ellenrzse knnyen egyszer paramterrtk-ellenrzss fajulhat. Mivel a paramterek gyakran tbb fggvnyen keresztl addnak t, az ellenrzs ismtldv s kltsgess vlhat. Annak az egyszer kijelentse azonban, hogy minden fggvnyben minden mutat-paramter nem nulla, nem sokat segt, s hamis biztonsgrzetet adhat klnsen akkor, ha a tbbletterhels elkerlse vgett az ellenrzseket csak hibakeress alatt vgezzk. Ez az egyik f oka, hogy javaslom, fordtsunk figyelmet az invarinsokra.

24.3.7.4. Betokozs
Vegyk szre, hogy a C++-ban az osztlyok nem az egyes objektumok a betokozs (enkapszulci, encapsulation) alapegysgei:
class List { List* next; public: bool on(List*); // ... }; bool List::on(List* p) { if (p == 0) return false; for(List* q = this; q; q=q->next) if (p == q) return true; return false; }

Forrs: http://www.doksi.hu

24. Tervezs s programozs

1017

A privt List::next mutat betokozsa elfogadhat, mivel a List::on() hozzfr a List osztly minden objektumhoz, amelyre hivatkozni tud. Ahol ez knyelmetlen, egyszersthetnk, ha nem hasznljuk ki azt a kpessget, hogy egy tagfggvnybl ms objektumok brzolshoz hozz lehet frni:
bool List::on(List* p) { if (p == 0) return false; if (p == this) return true; if (next==0) return false; return next->on(p); }

Ez azonban a bejrst (itercit) ismtlss (rekurziv) alaktja t, ami komoly teljestmnyromlst okozhat, ha a fordt nem kpes az optimlis mkdshez az eljrst visszaalaktani.

24.4. Komponensek
A tervezs egysgei az osztlyok, fggvnyek stb. gyjtemnyei, nem egy egyes osztlyok. Ezeket a gyjtemnyeket gyakran knyvtrnak (library) vagy keretrendszernek (framework) nevezzk (25.8), s az jrahasznosts (23.5.1) s karbantarts is ezeket helyezi a kzppontba. A logikai felttelek ltal sszekapcsolt szolgltatsok halmazt jelent fogalom kifejezsre a C++ hrom mdot biztost: 1. Osztly ltrehozsa, mely adat-, fggvny-, sablon- s tpustagok gyjtemnyt tartalmazza. 2. Osztlyhierarchia ltrehozsa, mely osztlyok gyjtemnyt foglalja magban. 3. Nvtr meghatrozsa, mely adat-, fggvny-, sablon- s tpustagok gyjtemnybl ll. Az osztlyok szmos lehetsget biztostanak arra, hogy knyelmesen hozhassunk ltre ltaluk meghatrozott tpus objektumokat. Sok jelents komponens azonban nem rhat le gy, mint adott tpus objektumokat ltrehoz rendszer. Az osztlyhierarchia egymssal rokon tpusok halmaznak fogalmt fejezi ki. A komponensek egyes tagjainak kifejezsre azonban nem mindig az osztlyok jelentik a legjobb mdszert s nem minden osztly illeszthet hasonlsg alapjn osztlyhierarchiba (24.2.5). Ezrt a komponens fogalmnak

Forrs: http://www.doksi.hu

1018

Tervezs a C++ segtsgvel

a C++-ban a nvtr a legkzvetlenebb s legltalnosabb megtestestse. A komponenseket nha osztlykategriknak nevezzk, de nem minden esetben llnak osztlyokbl (s ez nem is elrs). Idelis esetben egy komponens a megvalstsra hasznlt felletekkel, valamint azokkal a felletekkel rhat le, melyeket felhasznli rszre biztost. Minden ms rszletkrds s a rendszer tbbi rsze szmra rejtett. A tervez szemszgbl ez a komponens meghatrozsa. A programoz ezt a fogalmat deklarcikra lekpezve brzolhatja. Az osztlyok s osztlyhierarchik adjk a felleteket, melyek csoportostsra a nvterek adnak lehetsget, s ugyancsak a nvterek biztostjk, hogy a programoz elvlaszthassa a felhasznlt felleteket a szolgltatottaktl. Vegyk az albbi pldt: X fellete ltal hasznlt X megvalstsa ltal hasznlt

X fellete

X megvalstsa

A 8.2.4.1-ben lert mdszerek felhasznlsval ebbl az albbi lesz:


namespace A { // ... } namespace X { using namespace A; // ... void f(); // az X fellete ltal hasznlt szolgltatsok

// az X komponens fellete // az A deklarciitl fgg

namespace X_impl { using namespace X; // ... }

// az X megvalstshoz szksges szolgltatsok

Forrs: http://www.doksi.hu

24. Tervezs s programozs

1019

void X::f() { using namespace X_impl; // az X_impl deklarciitl fgg // ... }

Az X ltalnos fellet nem fgghet az X_impl megvalstsi fellettl. Egy komponensnek szmos olyan osztlya lehet, melyek nem ltalnos hasznlatra szntak. Az ilyen osztlyokat megvalst osztlyokba vagy nvterekbe kell elrejteni:
namespace X_impl { class Widget { // ... }; } // ... // az X megvalstsnak rszletei

Ez biztostja, hogy a Widget-et nem hasznlhatjuk a program ms rszeibl. Az egysges fogalmakat brzol osztlyok ltalban jrahasznosthatk, ezrt rdemes befoglalni azokat a komponens felletbe:
class Car { class Wheel { // ... }; Wheel flw, frw, rlw, rrw; // ... public: // ... };

A legtbb krnyezetben el kell rejtennk a kerekeket (Wheel), hogy megtartsuk az aut (Car) brzolsnak pontossgt (egy valdi aut esetben sem tudjuk a kerekeket fggetlenl mkdtetni). Maga a Wheel osztly viszont alkalmasnak tnik szlesebb kr hasznlatra, gy lehet, hogy jobb a Car osztlyon kvlre tenni:
class Wheel { // ... };

Forrs: http://www.doksi.hu

1020

Tervezs a C++ segtsgvel

class Car { Wheel flw, frw, rlw, rrw; // ... public: // ... };

A dnts, hogy begyazzuk-e az egyik fogalmat a msikba, attl fgg, mik a tervezs cljai s mennyire ltalnosak a fogalmak. Mind a begyazs, mind a nem begyazs szles krben alkalmazhat mdszer. Az alapszably az legyen, hogy az osztlyokat a lehet legnllbb (helyiv) tesszk, amg ltalnosabb elrsket szksgesnek nem ltjuk. Az rdekes fggvnyeknek s adatoknak van egy kellemetlen tulajdonsga, mgpedig az, hogy a globlis nvtr fel, a szles krben hasznlt nvterekbe vagy a hierarchik gykrosztlyaiba trekednek. Ez knnyen vezethet a megvalsts rszleteinek nem szndkos nyilvnoss ttelhez s a globlis adatokkal s globlis fggvnyekkel kapcsolatos problmkhoz. Ennek valsznsge az egyetlen gyker hierarchikban s az olyan programokban a legnagyobb, ahol csak nagyon kevs nvteret hasznlunk. E jelensg lekzdsre az osztlyhierarchikkal kapcsolatban a virtulis bzisosztlyokat (15.2.4) hasznlhatjuk fel, a nvtereknl pedig a kis implementcis nvterek kialaktsa lehet megolds. Megjegyzend, hogy a fejllomnyok (header) komoly segtsget nyjthatnak abban, hogy a komponenseket felhasznliknak klnbz szempontbl mutathassuk, s kizrjk azokat az osztlyokat, melyek a felhasznl szempontjbl a megvalsts rszeinek tekinthetk (9.3.2).

24.4.1. Sablonok
A tervezs szempontjbl a sablonok (template) kt, laza kapcsolatban lv szksgletet szolglnak ki: ltalnostott (generic) programozs Eljrsmdot (policy) meghatroz paramterezs

A tervezs korai szakaszban a mveletek csupn mveletek. Ksbb, amikor az operandusok tpust meg kell hatrozni, az olyan, statikus tpusokat hasznl programozsi nyelvekben, mint a C++, a sablonok lnyeges szerepet kapnak. Sablonok nlkl a fggvny-defincikat jra meg jra meg kellene ismtelni, vagy az ellenrzseket a futsi idre kellene elhalasztani (24.2.3). Azokat a mveleteket, amelyek egy algoritmust tbbfle operandustpusra kell, hogy lerjanak, clszer sablonknt elkszteni. Ha az sszes ope-

Forrs: http://www.doksi.hu

24. Tervezs s programozs

1021

randus egyetlen osztlyhierarchiba illeszthet (s klnsen akkor, ha futsi idben szksg van j operandustpusok hozzadsra is), az operandustpusok osztlyknt, mgpedig ltalban absztrakt osztlyknt brzolhatk a legjobban. Ha az operandustpusok nem illenek bele egyetlen hierarchiba (s klnsen akkor, ha a futsi idej teljestmny ltfontossg), a mveletet legjobb sablonknt elkszteni. A szabvnyos trolk s az azokat tmogat algoritmusok annak pldi, amikor a tbbfle, nem rokon tpus operandus hasznlatnak s a futsi idej teljestmny fokozsnak egyidej szksgessge vezet sablonok hasznlathoz (16.2). Vegyk egy egyszer bejrs ltalnostst, hogy jobban szemgyre vehessk a sablon vagy hierarchia? dilemmt:
void print_all(Iter_for_T x) { for (T* p = x.first(); p; p = x.next()) cout << *p; }

Itt azzal a felttelezssel ltnk, hogy az Iter_for_T olyan mveleteket biztost, melyek T*-okat hoznak ltre. Az Iter_for_T-t sablonparamterr tehetjk:
template<class Iter_for_T> void print_all(Iter_for_T x) { for (T* p = x.first(); p; p = x.next()) cout << *p; }

Ez lehetsget ad arra, hogy egy sereg egymssal nem rokon bejrt (iterator) hasznljunk, ha ezek mindegyike biztostja a helyes jelents first()-t s next()-et s ha fordtsi idben minden print_all() hvs bejrjnak tpust ismerjk. A standard knyvtr troli s algoritmusai ezen az tleten alapulnak. Felhasznlhatjuk azt a megfigyelst is, hogy a first() s a next() a bejrk szmra felletet alkotnak s ltrehozhatunk egy osztlyt, amely ezt az felletet kpviseli:
class Iter { public: virtual T* first() const = 0; virtual T* next() = 0; }; void print_all2(Iter& x) { for (T* p = x.first(); p; p = x.next()) cout << *p; }

Forrs: http://www.doksi.hu

1022

Tervezs a C++ segtsgvel

Most mr az Iter-bl szrmaztatott valamennyi bejrt hasznlhatjuk. A kdok azonosak, fggetlenl attl, hogy sablonokat vagy osztlyhierarchit hasznlunk a paramterezs brzolsra csak a futsi id, jrafordts stb. tekintetben klnbznek. Az Iter osztly klnsen alkalmas arra, hogy az albbi sablon paramtere legyen:
void f(Iter& i) { print_all(i); print_all2(i); }

// a sablon hasznlata

Kvetkezskppen a kt megkzelts egymst kiegsztnek tekinthet. Egy sablonnak gyakran van szksge arra, hogy fggvnyeket s osztlyokat gy hasznljon, mint megvalstsnak rszeit. Soknak kzlk maguknak is sablonoknak kell lennik, hogy megtartsuk az ltalnossgot s a hatkonysgot. gy az algoritmusok tbb tpusra ltalnosthatk. A sablonok hasznlatnak ezt a mdjt nevezzk ltalnostott vagy generikus programozsnak (2.7). Ha az std::sort()-ot egy vector-ra hvjuk meg, a sort() operandusai a vektor elemei lesznek, vagyis a sort() az egyes elemtpusokra ltalnostott. A szabvnyos rendez fggvny emellett ltalnos a troltpusokra nzve is, mert brmely, a szabvnyhoz igazod trol bejrjra meghvhat (16.3.1). A sort() algoritmus az sszehasonltsi felttelekre is paramterezett (18.7.1). Tervezsi szempontbl ez ms, mint amikor vesznk egy mveletet s ltalnostjuk az operandus tpusra. Sokkal magasabb szint tervezsi dnts, hiszen egy objektumon (vagy mveleten) dolgoz algoritmust gy paramtereznk, hogy az az algoritmus mkdsmdjt vezrli. Ez egy olyan dnts, ami rszben a tervez/programoz kezbe adja a vezrlst az algoritmus mkdsi elveinek kialaktsban.

24.4.2. Fellet s megvalsts


Az idelis fellet teljes s egysges fogalomkszletet ad a felhasznlnak, a komponensek minden rszre nzve kvetkezetes, nem fedi fel a megvalsts rszleteit a felhasznlnak, tbbflekppen elkszthet, tpusai fordtsi idben ellenrizhetk, alkalmazsszint tpusok hasznlatval kifejezett, ms felletektl korltozott s pontosan meghatrozott mdokon fgg.

Forrs: http://www.doksi.hu

24. Tervezs s programozs

1023

Miutn megjegyeztk, hogy az osztlyok kztt, melyek a klvilg fel a komponens fellett kpezik, kvetkezetessgre van szksg (24.4), a trgyalst egyetlen osztlyra egyszersthetjk. Vegyk az albbi pldt:
class Y { /* ... */ }; class Z { /* ... */ }; // X ltal ignyelt // X ltal ignyelt

class X { // plda a szegnyes felletre Y a; Z b; public: void f(const char * ...); void g(int[],int); void set_a(Y&); Y& get_a(); };

Ennl a felletnl tbb problma is felmerlhet: A fellet Y s Z tpust gy hasznlja, hogy a fordtnak ismernie kell Y s Z deklarcijt. Az X::f() fggvny tetszleges szm ismeretlen tpus paramtert vehet t (valsznleg egy, az els paramterben megkapott formzott karakterlnc ltal vezrelt mdon, 21.8). Az X::g() egy int[] paramtert vesz t. Ez elfogadhat lehet, de annak a jele, hogy az elvonatkoztats szintje tl alacsony. Egy egszekbl ll tmb nem nler, teht nem magtl rtetd, hny eleme van. A set_a()s get_a() fggvnyek valsznleg felfedik X osztly objektumainak brzolst, azltal, hogy X::a-hoz kzvetlen hozzfrst tesznek lehetv. Ezek a tagfggvnyek nagyon alacsony elvonatkoztatsi (fogalmi) szinten nyjtanak felletet. Az ilyen szint felletekkel rendelkez osztlyok lnyegben egy nagyobb komponens megvalstsnak rszletei kz tartoznak ha egyltaln tartoznak valahov. Idelis esetben egy felletfggvny paramtere elg informcit tartalmaz ahhoz, hogy nler legyen. Alapszably, hogy a krelmek vkony drton t legyenek tovbbthatak a tvoli kiszolgl fel. A C++ a programoznak lehetsget ad arra, hogy az osztlyokat a fellet rszeknt brzolja. Az brzols lehet rejtett (private vagy protected hasznlatval), de a fordt megengedi automatikus vltozk ltrehozst, fggvnyek helyben kifejtst stb. is. Ennek negatv hatsa, hogy az osztlytpusok hasznlata az osztly brzolsban nemkvnatos

Forrs: http://www.doksi.hu

1024

Tervezs a C++ segtsgvel

fggseket idzhet el. Az, hogy problma-e Y s Z tpusok tagjainak hasznlata, attl fgg, valjban milyen fajta tpus Y s Z. Ha egyszer tpusok, mint a list, a complex vagy a string, hasznlatuk ltalban teljesen megfelel. Az ilyen tpusok stabilnak minslnek s osztlydeklarciik beptse a fordt rszre elfogadhat terhels. Ha azonban Y s Z maguk is jelents komponensek (pldul egy grafikus rendszer vagy egy banki kezelrendszer osztlyai), blcsebb lenne, ha nem fggnnk tlk tl kzvetlenl. Ilyen esetekben gyakran jobb vlaszts egy mutat vagy referencia hasznlata:
class Y; class Z; class X { Y* a; Z& b; // ... }; // X csak mutatn s referencin t ri el Y-t s Z-t

Ez levlasztja X defincijt Y s Z definciirl, vagyis X defincija csak Y s Z nevtl fgg. X megvalstsa termszetesen mg mindig fgg Y s Z defincijtl, de ez nem rinti X felhasznlit. Ezzel egy fontos dolgot szemlltettnk: egy felletnek, mely jelents adatmennyisget rejt el ahogyan ez egy hasznlhat fellettl elvrhat sokkal kevesebb lesz a fggse, mint az ltala elrejtett megvalstsnak. Az X osztly defincija pldul anlkl fordthat le, hogy Y s Z definciihoz hozzfrnnk. A fggsek elemzsekor kln kell kezelni a fellet s kln a megvalsts fggseit. Mindkt esetben idelis, ha a rendszer fggsgi bri irnytott, krmentes grfok, melyek megknnytik a rendszer megrtst s tesztelst. Ennek elrse a felleteknl fontosabb (s knnyebb is), mint a megvalstsnl. Vegyk szre, hogy egy osztly hrom felletet rhat le:
class X { private: // csak tagok s bart fggvnyek szmra elrhet protected: // csak tagok s "bartok", valamint // a szrmaztatott osztly tagjai szmra elrhet public: // ltalnosan elrhet };

Ezenkvl mg a nyilvnos fellet rszt kpezik a bartok is (friend, 11.5).

Forrs: http://www.doksi.hu

24. Tervezs s programozs

1025

A tagokat a legkorltozottabb elrs fellet rszeknt clszer megadni. Ez azt jelenti, hogy a tagok mindig privtok legyenek, hacsak nincs okunk arra, hogy hozzfrhetbb tegyk azokat. Ha hozzfrhetbbnek kell lennik, legyenek vdettek (protected), hacsak nincs okunk arra, hogy nyilvnosknt (public) adjuk meg ket. Az adattagokat szinte soha nem szerencss nyilvnoss vagy vdett tenni. A nyilvnos felletet alkot fggvnyeknek s osztlyoknak az osztly olyan nzett kell biztostaniuk, amely illik ahhoz a szerephez, hogy az osztly egy fogalmat brzol. Az brzols elrejtsnek egy tovbbi szintjt absztrakt osztlyok hasznlatval biztosthatjuk (2.5.4, 12.3, 25.3).

24.4.3. Kvr felletek


Idelis esetben egy fellet csak olyan mveleteket knlhat fel, melyeknek rtelmk van s a felletet megvalst brmely szrmaztatott osztly ltal megfelelen lerhatk. Ez azonban nem mindig knny. Vegyk a listkat, tmbket, asszociatv tmbket, fkat stb. Mint ahogy a 16.2.2 pontban rmutattunk, csbt s nha hasznos ezeket a tpusokat ltalnostani rendszerint egy trol (kontner) hasznlatval, melyet mindegyikk felleteknt hasznlhatunk. Ez (ltszlag) felmenti a felhasznlt a trol rszleteinek kezelse all. Egy ltalnos trolosztly felletnek kialaktsa azonban nem knny feladat. Tegyk fel, hogy a Container-t absztrakt tpusknt akarjuk meghatrozni. Milyen mveleteket biztostson a Container? Megadhatnnk csak azokat a mveleteket, melyeket minden trol kpes tmogatni a mvelethalmazok metszett , de ez nevetsgesen szk fellet. Sok esetben a metszet valjban res. A msik md, ha az sszes mvelethalmaz unijt adjuk meg, futskor pedig hibajelzst adunk, ha ezen a felleten keresztl valamelyik objektumra egy nem ltez mvelet alkalmazsa trtnik. Az olyan felletet, mely egy fogalomhalmaz felleteinek unija, kvr felletnek (fat interface) nevezzk. Vegynk egy T tpus objektumokat tartalmaz ltalnos trolt:
class Container { public: struct Bad_oper { // kivtelosztly const char* p; Bad_oper(const char* pp) : p(pp) { } }; virtual void put(const T*) { throw Bad_oper("Container::put"); } virtual T* get() { throw Bad_oper("Container::get"); }

Forrs: http://www.doksi.hu

1026

Tervezs a C++ segtsgvel

};

virtual T*& operator[](int) { throw Bad_oper("Container::[](int)"); } virtual T*& operator[](const char*) { throw Bad_oper("Container::[](char*)"); } // ...

A Container-eket ezutn gy vezethetjk be:


class List_container : public Container, private list { public: void put(const T*); T* get(); // ... nincs operator[] ... }; class Vector_container : public Container, private vector { public: T*& operator[](int); T*& operator[](const char*); // ... nincs put() vagy get() ... };

Amg vatosak vagyunk, minden rendben van:


void f() { List_container sc; Vector_container vc; // ... user(sc,vc); } void user(Container& c1, Container& c2) { T* p1 = c1.get(); T* p2 = c2[3]; // ne hasznljuk c2.get() vagy c1[3] mveletet // ... }

Kevs azonban az olyan adatszerkezet, mely mind az indexelses, mind a lista stlus mveleteket jl tmogatja. Kvetkezskppen valsznleg nem j tlet olyan felletet meghatrozni, mely mindkettt megkveteli. Ha ezt tesszk, a hibk elkerlse rdekben futsi idej tpuslekrdezst (15.4) vagy kivtelkezelst (14. fejezet) kell alkalmaznunk:

Forrs: http://www.doksi.hu

24. Tervezs s programozs

1027

void user2(Container& c1, Container& c2) // szlelni knny, de a helyrellts nehz lehet { try { T* p1 = c1.get(); T* p2 = c2[3]; // ... } catch(Container::Bad_oper& bad) { // Hopp! // Most mit tegynk? } }

vagy
void user3(Container& c1, Container& c2) // a korai szlels fraszt, de a helyrellts mg mindig nehz { if (dynamic_cast<List_container*>(&c1) && dynamic_cast<Vector_container*>(&c2)) { T* p1 = c1.get(); T* p2 = c2[3]; // ... } else { // Hopp! // Most mit tegynk? } }

A futsi idej teljestmny mindkt esetben cskkenhet s a ltrehozott kd meglepen nagy lehet. Ez azt eredmnyezi, hogy a programozk hajlamosak figyelmen kvl hagyni a lehetsges hibkat, remlve, hogy valjban nem is fordulnak el, amikor a program a felhasznlk kezbe kerl. E megkzeltssel az a problma, hogy a kimert tesztels szintn nehz s drga munka. Kvetkezskppen a kvr felleteket a legjobb elkerlni ott, ahol a futsi idej teljestmny elsrenden fontos, ahol megkveteljk a kd helyessgre vonatkoz garancit, s ltalban ott, ahol van ms j megolds. A kvr felletek hasznlata gyengti a megfeleltetst a fogalmak s osztlyok kztt s gy szabad utat enged annak, hogy a szrmaztatst pusztn a knyelmesebb megvalsts kedvrt hasznljuk.

Forrs: http://www.doksi.hu

1028

Tervezs a C++ segtsgvel

24.5. Tancsok
[1] Trekedjnk az absztrakt adatbrzolsra s az objektumorientlt programozsra. 24.2. [2] A C++ tulajdonsgait s technikit (csak) szksg szerint hasznljuk. 24.2. [3] Teremtsnk sszhangot a tervezs s programozs stlusa kztt. 24.2.1. [4] A fggvnyek s a feldolgozs helyett a tervezsben elsdlegesen az osztlyokra s fogalmakra sszpontostsunk. 24.2.1. [5] A fogalmak brzolsra hasznljunk osztlyokat. 24.2.1, 24.3. [6] A fogalmak kztti hierarchikus kapcsolatokat (s csak azokat) rklssel brzoljuk. 24.2.2, 24.2.5, 24.3.2. [7] A felletekre vonatkoz garancikat fejezzk ki alkalmazsszint statikus tpusokkal. 24.2.3. [8] A pontosan meghatrozhat feladatok elvgzsnek megknnytsre hasznljunk programgenertorokat s kzvetlen kezeleszkzket. 24.2.4. [9] Kerljk az olyan programgenertorokat s kzvetlen kezeleszkzket, melyek nem illeszkednek pontosan az adott ltalnos cl programozsi nyelvhez. 24.2.4. [10] Az elhatrolhat fogalmi szinteket vlasszuk el. 24.3.1. [11] sszpontostsunk a komponensek tervezsre. 24.4. [12] Mindig gyzdjnk meg rla, hogy egy adott virtulis fggvnynek pontosan definilt jelentse van-e s hogy az azt fellbrl minden fggvny a kvnt viselkeds egy vltozatt rja-e le. 24.3.5, 24.3.2.1. [13] Hasznljunk nyilvnos rklst az is-a kapcsolatok brzolsra. 24.3.4. [14] Hasznljunk tagsgot a has-a kapcsolatok brzolsra. 24.3.4. [15] nllan ltrehozott objektumra hivatkoz mutatk helyett hasznljunk inkbb kzvetlen tagsgot az egyszer tartalmazsi kapcsolatok kifejezsre. 24.3.3, 24.3.4. [16] A uses fggsek legyenek vilgosak, (ahol lehetsges) klcsns fggstl mentesek, s minl kevesebb legyen bellk. 24.3.5. [17] Minden osztlyhoz adjunk meg invarinsokat. 24.3.7.1. [18] Az elfeltteleket, utfeltteleket s ms lltsokat felttelezsekkel (lehetleg az Assert() hasznlatval) fejezzk ki. 24.3.7.2. [19] A felletek csak a felttlenl szksges informcikat tegyk elrhetv. 24.4. [20] Cskkentsk a felletek fggseit ms felletektl. 24.4.2. [21] A felletek legyenek ersen tpusosak. 24.4.2. [22] A felleteket alkalmazsszint tpusokkal fejezzk ki. 24.4.2. [23] A felletek tegyk lehetv, hogy a krsek tvihetk legyenek egy tvoli kiszolglra. 24.4.2. [24] Kerljk a kvr felleteket. 24.4.3.

Forrs: http://www.doksi.hu

24. Tervezs s programozs

1029

[25] Hasznljunk private adatokat s tagfggvnyeket, ahol csak lehetsges. 24.4.2. [26] A tervezk, illetve az ltalnos felhasznlk ltal ignyelt szrmaztatott osztlyok elklntsre hasznljuk a public/protected megklnbztetst. 24.4.2. [27] Az ltalnostott programozs rdekben hasznljunk sablonokat. 24.4.1. [28] Az algoritmusok eljrsmdot megad paramterezsre szintn sablonokat hasznljunk. 24.4.1. [29] Ha fordtsi idej tpusfeloldsra van szksg, hasznljunk sablonokat. 24.4.1. [30] Ha futsi idej tpusfeloldsra van szksg, hasznljunk osztlyhierarchikat. 24.4.1.

Forrs: http://www.doksi.hu

25
Az osztlyok szerepe
Vannak dolgok, melyek jobb, ha vltoznak... de az alapvet tmknak llandnak kell maradniuk. (Stephen J. Gould) Az osztlyok fajti Konkrt tpusok Absztrakt tpusok Csompontok Vltoz felletek Objektum I/O Mveletek Felletosztlyok Lerk Hasznlatszmlls Keretrendszerek Tancsok Gyakorlatok

25.1. Az osztlyok fajti


A C++ osztly egy programozsi nyelvi szerkezet, tbbfle tervezsi igny kiszolglsra. A bonyolultabb tervezsi problmk megoldsa ltalban j osztlyok bevezetsvel jr (esetleg ms osztlyok elhagysval). Az j osztlyokkal olyan fogalmakat brzolunk, amelyeket az elz tervvzlatban mg nem hatroztunk meg pontosan. Az osztlyok sokfle szerepet jtszhatnak, ebbl pedig az addik, hogy a konkrt ignyekhez egyedi osztlyfajtkra lehet szksgnk. E fejezetben lerunk nhny alapvet osztlytpust, azok eredend erssgeivel s gyengivel egytt:

Forrs: http://www.doksi.hu

1032

Tervezs a C++ segtsgvel

25.2 25.2 25.4 25.5 25.6 25.7 25.8

Konkrt tpusok Absztrakt tpusok Csompontok Mveletek Felletek Lerk Keretrendszerek

Ezek az osztlyfajtk tervezsi fogalmak, nem nyelvi szerkezetek. A valsznleg elrhetetlen idel az lenne, ha rendelkezsre llna egyszer s nll osztlyfajtk egy olyan legkisebb halmaza, melybl az sszes megfelelen viselked s hasznlhat osztlyt megalkothatnnk. Fontos, hogy szrevegyk: a fenti osztlyfajtk mindegyiknek helye van a tervezsben s egyik sem eredenden jobb minden hasznlatra a tbbinl. A tervezsi s programozsi vitk sorn szmos flrerts addhat abbl, hogy vannak, akik kizrlag egy vagy kt fajta osztlyt prblnak hasznlni. Ez rendszerint az egyszersg nevben trtnik, de a kedvenc osztlyfajtk torz s termszetellenes hasznlathoz vezet. Jelen lers ezeknek az osztlyfajtknak a tiszta alakjaival foglalkozik, de termszetesen kevert formk is hasznlhatk. A keverkek azonban tervezsi dntsek eredmnyeknt kell, hogy szlessenek, a lehetsges megoldsok kirtkelsvel, nem pedig a dntshozatalt elkerlve, cltalan ksrletezssel. A dntsek elhalasztsa sokszor valjban a gondolkods elkerlst jelenti. A kezd tervezk rendszerint jl teszik, ha vakodnak a kevert megoldsoktl s egy ltez sszetev stlust kvetik, melynek tulajdonsgai az j komponens kvnt tulajdonsgaira emlkeztetnek. Csak tapasztalt programozk ksreljk meg egy ltalnos cl komponens vagy knyvtr megrst, s minden knyvtrtervezt arra kellene tlni, hogy nhny vig a sajt alkotst hasznlja, dokumentlja s tmogassa. (Lsd mg: 23.5.1.)

25.2. Konkrt tpusok


Az olyan osztlyok, mint a vector (16.3), a list (17.2.2), a Date (10.3) s a complex (11.3, 22.5) konkrtak abban az rtelemben, hogy mindegyikk egy-egy viszonylag egyszer fogalmat brzol, az sszes, e fogalom tmogatshoz nlklzhetetlen mvelettel egytt. Mindegyikknl fennll az egy az egyhez megfelels a fellet s a megvalsts kztt s egyiket sem szntk bzisosztlynak szrmaztatshoz. A konkrt tpusok ltalban nem illenek bele egymssal kapcsolatos osztlyok hierarchijba. Minden konkrt osztly nma-

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1033

gban megrthet, a lehet legkevesebb hivatkozssal ms osztlyokra. Ha egy konkrt tpust megfelelen runk le, az azt hasznl programok mretben s sebessgben sszemrhetk lesznek azokkal a programokkal, melyekben a programoz egynileg dolgozza ki a fogalom brzolst, majd annak egyedi cl (szrmaztatott) vltozataival dolgozik. Ezenkvl, ha a megvalsts jelentsen vltozik, rendszerint a fellet is mdosul, hogy tkrzze a vltozst. A konkrt tpusok mindezekben a beptett tpusokra emlkeztetnek. A beptett tpusok termszetesen mind konkrtak. A felhasznli konkrt tpusok, mint a komplex szmok, mtrixok, hibazenetek s szimbolikus hivatkozsok, gyakran az egyes alkalmazsi terletek alaptpusaiknt hasznltak. Az adott osztly felletnek termszete hatrozza meg, mi minsl jelents vltoztatsnak a megvalstsban; az elvontabb felletek ezen vltoztatsoknak tbb teret hagynak, de ronthatjk a futsi idej hatkonysgot. Ezenkvl egy j megvalsts nem fgg ms osztlyoktl a felttlenl szksgesnl ersebben, gy az osztly a programban lv hasonl osztlyokhoz val alkalmazkods szksgessgnek elkerlsvel fordtsi vagy futsi idej tlterhels nlkl hasznlhat. sszegezve, egy konkrt tpust ler osztly cljai a kvetkezk: 1. Szoros illeszkeds egy konkrt fogalomhoz s a megvalsts mdjhoz. 2. Az egynileg kidolgozott kdhoz mrhet gyorsasg s kis mret, a helyben kifejts, valamint a fogalom s megvalstsa sszes elnyt kihasznl mveletek hasznlatval. 3. A ms osztlyoktl val lehet legkisebb arny fggs. 4. rthetsg s nll hasznlhatsg. Az eredmny a felhasznli s a megvalst kd kzti szoros kts. Ha a megvalsts brmilyen mdon megvltozik, a felhasznli kdot jra kell fordtani, mivel a felhasznli kd szinte mindig tartalmaz helyben kifejtett (inline) fggvnyhvsokat vagy a konkrt tpushoz tartoz helyi (loklis) vltozkat. A konkrt tpus elnevezst az ltalnosan hasznlt absztrakt tpus elnevezs ellentteknt vlasztottuk. A konkrt s absztrakt tpusok kzti viszonyt a 25.3 pont trgyalja. A konkrt tpusok nem tudnak kzvetlenl kzssget kifejezni. A list s a vector mvelethalmaza pldul hasonl s nhny sablon fggvnyben egymst helyettesthetik. A list<int> s a vector<int>, vagy a list<Shape*> s a list<Circle*> kztt azonban nincs rokonsg (13.6.3), jllehet mi szrevesszk a hasonlsgokat.

Forrs: http://www.doksi.hu

1034

Tervezs a C++ segtsgvel

Ez azt is jelenti, hogy az egyes konkrt tpusokat hasonl mdon hasznl kdok klsejkben klnbzk lesznek. Egy List bejrsa a next() mvelettel pldul jelentsen klnbzik egy Vector indexelssel trtn bejrstl:
void my(List& sl) { for (T* p = sl.first(); p; p = sl.next()) { // egyik kd } // ... } void your(Vector& v) { for (int i = 0; i<v.size(); i++) { // msik kd } // ... }

// "termszetes" lista-bejrs

// "termszetes" vektor-bejrs

A bejrs stlusbeli klnbsge termszetes, abban az rtelemben, hogy a vedd a kvetkez elemet mvelet nlklzhetetlen a lista fogalmnl, de nem ilyen magtl rtetd egy vektornl, illetve hogy az indexels nlklzhetetlen a vektor fogalmnl, de a listnl nem az. Az, hogy a vlasztott megvalstsi mdhoz a termszetes mveletek rendelkezsre lljanak, ltalban ltfontossg, mind a hatkonysg szempontjbl, mind azrt, hogy a kd knnyen megrhat legyen. Magtl rtetd nehzsg, hogy az alapveten hasonl mveletekhez (pldul az elbbi kt ciklushoz) rott kd eltren nzhet ki, a hasonl mveletekhez klnbz konkrt tpusokat hasznl kdok pedig nem cserlhetk fel. Vals programoknl fejtrst ignyel, hogy megtalljuk a hasonlsgokat, s jelents jratervezst, hogy a megtallt hasonlsgok kihasznlsra mdot adjunk. A szabvnyos trolk s algoritmusok is gy teszik lehetv a konkrt tpusok hasonlsgainak kihasznlst, hatkonysguk s elegancijuk elvesztse nlkl (16.2). Egy fggvnynek ahhoz, hogy paramterknt elfogadjon egy konkrt tpust, pontosan az adott tpust kell megadnia paramtertpusknt. Nem llnak rendelkezsre rklsi kapcsolatok, melyeket arra hasznlhatnnk, hogy a paramterek deklarlst ltalnosabb tegyk. Kvetkezskppen a konkrt tpusok kzti hasonlsgok kihasznlsra tett ksrlet magval vonja a sablonok hasznlatt s az ltalnostott (generikus) programozst, amint arrl a 3.8 pontban mr emltst tettnk. Ha a standard knyvtrat hasznljuk, a bejrs gy fog kinzni:

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1035

template<class C> void ours(const C& c) { for (C::const_iterator p = c.begin(); p!=c.end(); ++p) { // standard knyvtrbeli bejrs // ... } }

Itt kihasznltuk a trolk alapvet hasonlsgt, s ezzel megnyitottuk az utat a tovbbi hasonlsgok kihasznlsa fel, ahogyan azt a szabvnyos algoritmusok is teszik (18. fejezet). A felhasznlnak a konkrt tpus megfelel hasznlathoz meg kell rtenie annak pontos rszleteit. (ltalban) nem lteznek olyan ltalnos tulajdonsgok, melyek egy knyvtrban az sszes konkrt tpusra rvnyesek lennnek, s amelyekre tmaszkodva a felhasznlnak nem kell az egyes osztlyok ismeretvel trdnie. Ez az ra a futsi idej tmrsgnek s hatkonysgnak. Nha megri, nha nem. Olyan eset is elfordul, hogy knnyebb megrteni s hasznlni egy konkrt osztlyt, mint egy ltalnosabb (absztrakt) osztlyt. A jl ismert adattpusokat (pl. tmbket, listkat) brzol osztlyoknl gyakran ez a helyzet. Vegyk szre azonban, hogy az idelis tovbbra is az, ha a megvalstsbl a lehet legnagyobb rszt elrejtjk, anlkl, hogy a teljestmnyt komolyabban rontannk. A helyben kifejtett fggvnyek ebben a krnyezetben nagy nyeresget jelenthetnek. Szinte soha nem j tlet, ha a tag vltozkat nyilvnoss tesszk vagy set s get fggvnyekrl gondoskodunk, melyekkel a felhasznl kzvetlenl kezelheti azokat (24.4.2). A konkrt tpusok maradjanak meg tpusoknak, s ne bitcsomagok legyenek, melyeket a knyelem kedvrt nhny fggvnnyel ltunk el.

25.2.1. A konkrt tpusok jrahasznostsa


A konkrt tpusok ritkn hasznlhatk alaptpusknt tovbbi szrmaztatshoz. Minden konkrt tpus clja egyetlen fogalom vilgos s hatkony brzolsa. Az olyan osztly, mely ezt a kvetelmnyt jl teljesti, ritkn alkalmas klnbz, de egymssal rokon osztlyok nyilvnos szrmaztats ltali ltrehozsra. Az ilyen osztlyok gyakrabban tehetk tagokk vagy privt bzisosztlyokk, mert gy anlkl hasznlhatk hatkonyan, hogy felletket s megvalstsukat keverni s rontani kellene j osztlyokval. Vegyk egy j osztly szrmaztatst a Date-bl:
class My_date : public Date { // ... };

Forrs: http://www.doksi.hu

1036

Tervezs a C++ segtsgvel

Szablyos-e a My_date-et gy hasznlni, mint az egyszer Date-et? Nos, ez attl fgg, mi a My_date, de tapasztalatom szerint ritkn lehet olyan konkrt tpust tallni, mely mdosts nlkl megfelel bzisosztlynak. A konkrt tpusok ugyangy mdosts nlkl jrahasznosthatk, mint az int-hez hasonl beptett tpusok (10.3.4):
class Date_and_time { private: Date d; Time t; public: // ... };

A felhasznlsnak (jrahasznostsnak) ez a mdja rendszerint egyszer s hatkony. Lehet, hogy hiba volt a Date-et nem gy tervezni, hogy szrmaztatssal knnyen lehessen mdostani? Nha azt mondjk, minden osztlynak mdosthatnak kell lennie fellrs s szrmaztatott osztlyok tagfggvnyeibl val hozzfrs ltal. Ezt a szemlletet kvetve a Date-bl az albbi vltozatot kszthetjk el:
class Date2 { public: // nyilvnos fellet, elssorban virtulis fggvnyekbl ll protected: // egyb megvalstsi rszletek (esetleg nmi brzolst is tartalmaz) private: // adatbrzols s egyb megvalstsi rszletek };

A fellr fggvnyek hatkony megrst megknnytend, az brzolst protected-knt deklarltuk. Ezzel elrtk clunkat: a Date2-t szrmaztatssal tetszlegesen hajlthatv tettk, vltozatlanul hagyva felhasznli fellett. Ennek azonban ra van: 1. Kevsb hatkony alapmveletek. A C++ virtulis fggvnyeinek meghvsa kiss lassabb, mint a szoksos fggvnyhvsok; a virtulis fggvnyek nem fordthatk helyben kifejtve olyan gyakran, mint a nem virtulis fggvnyek; radsul a virtulis fggvnyekkel rendelkez osztlyok egy gpi szval tbb helyet ignyelnek. 2. A szabad tr hasznlatnak szksgessge. A Date2 clja, hogy a belle szrmaztatott osztlyok objektumai felcserlhetek legyenek. Mivel e szrmaztatott

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1037

osztlyok mrete klnbz, kzenfekv, hogy a szabad trban foglaljunk szmukra helyet s mutatkkal vagy referencikkal frjnk hozzjuk. A valdi loklis vltozk hasznlata teht drmaian cskken. 3. Knyelmetlensg a felhasznlnak. A virtulis fggvnyek tbbalaksgt (polimorfizmus) kihasznland, a Date2-khz mutatk vagy referencik ltal kell hozzfrnnk. 4. Gyengbb betokozs. A virtulis mveletek fellrhatk s a vdett adatok a szrmaztatott osztlyokbl mdosthatk (12.4.1.1). Termszetesen ezek a kltsgek nem mindig jelentsek s az gy ltrehozott osztly gyakran pontosan gy viselkedik, mint ahogy szerettk volna (25.3, 25.4). Egy egyszer konkrt tpusnl (mint a Date2) azonban ezek nagy s szksgtelen kltsgek. A hajlthatbb tpus idelis brzolsra sokszor egy jl megtervezett konkrt tpus a legalkalmasabb:
class Date3 { public: // nyilvnos fellet, elssorban virtulis fggvnyekbl ll private: Date d; };

gy a konkrt tpusok (a beptett tpusokat is belertve) osztlyhierarchiba is illeszthetk, ha szksges. (Lsd mg 25.10[1].)

25.3. Absztrakt tpusok


Az osztlyokat, valamint az objektumokat ltrehoz s az azokat felhasznl kd kztti ktelk laztsnak legegyszerbb mdja egy absztrakt osztly bevezetse, mely a fogalom klnbz megvalstsainak halmazhoz kzs felletet biztost. Vegynk egy termszetes Set-et (halmazt):
template<class T> class Set { public: virtual void insert(T*) = 0; virtual void remove(T*) = 0;

Forrs: http://www.doksi.hu

1038

Tervezs a C++ segtsgvel

virtual int is_member(T*) = 0; virtual T* first() = 0; virtual T* next() = 0; }; virtual ~Set() { }

Ez meghatrozza a halmaz fellett s tartalmazza az elemek bejrsnak mdjt is. A konstruktor hinya s a virtulis destruktor jelenlte szokvnyos (12.4.2). Tbbfle megvalsts lehetsges (16.2.1):
template<class T> class List_set : public Set<T>, private list<T> { // ... }; template<class T> class Vector_set : public Set<T>, private vector<T> { // ... };

Az egyes megvalstsokhoz az absztrakt osztly adja a kzs felletet, vagyis a Set-tel anlkl dolgozhatunk, hogy tudnnk, melyik megvalstst hasznljuk:
void f(Set<Plane*>& s) { for (Plane** p = s.first(); p; p = s.next()) { // sajt kd } // ... } List_set<Plane*> sl; Vector_set<Plane*> v(100); void g() { f(sl); f(v); }

A konkrt tpusoknl megkveteltk a megvalst osztlyok jratervezst, hogy kifejezzk a kzssget, annak kiaknzsra pedig sablont hasznltunk. Itt a kzs felletet kell megterveznnk (esetnkben a Set-et), de semmi ms kzset nem kvnunk a megvalstsra hasznlt osztlyoktl, mint azt, hogy meg tudjk valstani a felletet.

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1039

Tovbb, a Set-et felhasznl programelemeknek nem kell ismernik a List_set s a Vector_set deklarciit, teht nem kell fggnik ezektl. Ezenkvl nem kell sem jrafordtanunk, sem brmilyen ms vltoztatst vgeznnk, ha a List_set vagy a Vector_set megvltozik, vagy ha bevezetjk a Set egy jabb megvalstst, mondjuk a Tree_set-et. Minden fggst a fggvnyek tartalmaznak, melyek a Set-bl szrmaztatott osztlyokat hasznljk. Vagyis felttelezve, hogy a szoksos mdon hasznljuk a fejllomnyokat az f(Set&) megrsakor csak a Set.h-t kell beptennk (#include), a List_set.h-t vagy a Vector_set.h-t nem. Csak ott van szksg egy implementcis fejllomnyra, ahol egy List_set, illetve egy Vector_set ltrehozsa trtnik. Az egyes megvalstsok tovbbi elszigetelst jelentheti a tnyleges osztlyoktl, ha egy absztrakt osztlyt vezetnk be, mely az objektum-ltrehozsi krseket kezeli (gyr, factory, 12.4.4). A felletnek ez az elvlasztsa a megvalststl azzal a kvetkezmnnyel jr, hogy eltnik a hozzfrs azon mveletekhez, melyek termszetesek egy adott megvalsts szmra, de nem elg ltalnosak ahhoz, hogy a fellet rszei legyenek. Pldul, mivel a Set nem rendelkezik a rendezs kpessgvel, a Set felletben nem tmogathatunk egy indexkezel opertort mg akkor sem, ha trtnetesen egy tmbt hasznl Set-et hozunk ltre. A kzi optimalizls hinya miatt ez nveli a futsi idt, ezenkvl ltalban nem lhetnk a helyben kifejts elnyeivel sem (kivve azokat az egyedi eseteket, amikor a fordt ismeri a valdi tpust), s a fellet minden lnyeges mvelete virtulis fggvnyhvs lesz. Mint a konkrt tpusok, az absztrakt tpusok hasznlata is nha megri; nha nem. sszefoglalva, az absztrakt tpusok cljai a kvetkezk: 1. Egy egyszer fogalom oly mdon val lersa, mely lehetv teszi, hogy a programban a fogalomnak tbb megvalstsa is ltezhessen. 2. Elfogadhat futsi id s takarkos helyfoglals biztostsa virtulis fggvnyek hasznlatval. 3. Az egyes megvalstsok lehet legkisebb fggse ms osztlyoktl. 4. Legyen rthet nmagban. Az absztrakt tpusok nem jobbak, mint a konkrt tpusok, csak msmilyenek. A felhasznlnak kell dntenie, melyiket hasznlja. A knyvtr ksztje elkerlheti a krdst azltal, hogy mind a kettt biztostja, a felhasznlra hagyva a vlasztst. Az a fontos, hogy vilgos legyen, az adott osztly melyik tpusba tartozik. Ha korltozzuk egy absztrakt tpus ltalnossgt, hogy sebessgben llja a versenyt egy konkrt tpussal, rendszerint kudarcot vallunk. Ez ugyanis veszlyezteti azt a kpessgt, hogy egyes megvalstsait egymssal helyettesthessk, a vltoztats utni jrafordts szksgessge nlkl. Hasonlkppen rendszerint kudarccal jr, ha konkrt tpusokat ltalnosabb akarunk tenni, hogy az absztrakt tpusok tulajdonsgaival sszemrhetk legyenek, mert gy veszlybe kerl az

Forrs: http://www.doksi.hu

1040

Tervezs a C++ segtsgvel

egyszer osztly hatkonysga s pontossga. A kt elkpzels egytt ltezhet valjban egytt kell, hogy ltezzen, mivel az absztrakt tpusok megvalstst konkrt tpusok adjk , de nem szabad ket sszekeverni egymssal. Az absztrakt tpusok gyakran kzvetlen megvalstsukon tl nem szolglnak tovbbi szrmaztatsok alapjul. j fellet azonban felpthet egy absztrakt osztlybl, ha belle egy mg tgabb absztrakt osztlyt szrmaztatunk. Ezt az j absztrakt osztlyt kell majd tovbbi szrmaztatson keresztl megvalstani egy nem absztrakt osztllyal (15.2.5). Mirt nem szrmaztattunk a Set-bl egy lpsben List s Vector osztlyokat, elhagyva a List_set s Vector_set osztlyok bevezetst? Ms szval, mirt legyenek konkrt tpusaink, ha absztrakt tpusaink is lehetnek? 1. Hatkonysg. Azt akarjuk, hogy konkrt tpusaink legyenek (mint a vector s a list), azon tlterhels nlkl, amit a megvalstsnak a fellettl val elvlasztsa okoz (az absztrakt tpusoknl ez trtnik). 2. jrahasznosts. Szksgnk van egy mdra, hogy a mshol tervezett tpusokat (mint a vector s a list) beilleszthessk egy j knyvtrba vagy alkalmazsba, azltal, hogy j felletet adunk nekik (nem pedig jrarjuk ket). 3. Tbb fellet. Ha egyetlen kzs alapot hasznlunk tbbfle osztlyhoz, az kvr felletekhez vezet (24.4.3). Gyakran jobb, ha az j clra hasznlni kvnt osztlynak j felletet adunk (mint egy vector-nak egy Set felletet), ahelyett, hogy a tbb clra val hasznlhatsg kedvrt az eredeti felletet mdostannk. Termszetesen ezek egymssal sszefgg krdsek. Az Ival_box pldban (12.4.2, 15.2.5) s a trolk tervezsvel kapcsolatban (16.2) rszletesebben trgyaltuk ezeket. Ha a Set bzisosztlyt hasznltuk volna, az egy csompont-osztlyokon nyugv alaptrolt eredmnyezett volna (25.4). A 25.7 pont egy rugalmasabb bejrt (itertort) r le, ahol a bejr a kezdeti rtkadsnl kthet az objektumokat biztost megvalstshoz s ez a kts futsi idben mdosthat.

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1041

25.4. Csompont-osztlyok
Az osztlyhierarchik ms szrmaztatsi szemllet alapjn plnek fel, mint az absztrakt osztlyoknl hasznlt, felletbl s megvalstsbl ll rendszer. Itt az osztlyokat alapnak tekintjk, melyre ms osztlyokat ptnk. Mg ha absztrakt osztly is, rendszerint van valamilyen brzolsa s ad valamilyen szolgltatsokat a belle szrmaztatott osztlyoknak. Ilyen csompont-osztlyok pldul a Polygon (12.3), a (kiindul) Ival_slider (12.4.1) s a Satellite (15.2). A hierarchin belli osztlyok jellemzen egy ltalnos fogalmat brzolnak, mg a bellk szrmaztatott osztlyok gy tekinthetk, mint az adott fogalom konkretizlt (egyedi cl, szakostott) vltozatai. Az egy hierarchia szerves rszeknt tervezett osztlyok a csompont-osztlyok (node class) a bzisosztly szolgltatsaira tmaszkodva biztostjk sajt szolgltatsaikat, vagyis a bzisosztly tagfggvnyeit hvjk meg. A csompont-osztlyok ltalban nem csupn a bzisosztlyuk ltal meghatrozott fellet egy megvalstst adjk (mint egy absztrakt tpusnak egy megvalst osztly), hanem maguk is hozztesznek j fggvnyeket, ezltal szlesebb felletet biztostanak. Vegyk a 24.3.2 forgalomszimulcis pldjban szerepl Car-t:
class Car : public Vehicle { public: Car(int passengers, Size_category size, int weight, int fc) : Vehicle(passengers,size,weight), fuel_capacity(fc) { /* ... */ } // a Vehicle lnyeges virtulis fggvnyeinek fellrsa void turn(Direction); // ... // Car-ra vonatkoz fggvnyek hozzadsa virtual void add_fuel(int amount); // az aut zemanyagot ignyel // ...

};

A fontos fggvnyek: a konstruktor, mely ltal a programoz a szimulci szempontjbl lnyeges alaptulajdonsgokat hatrozza meg s azok a (virtulis) fggvnyek, melyek a szimulcis eljrsoknak lehetv teszik, hogy egy Car-t anlkl kezeljenek, hogy tudnk annak pontos tpust. Egy Car az albbi mdon hozhat ltre s hasznlhat:

Forrs: http://www.doksi.hu

1042

Tervezs a C++ segtsgvel

void user() { // ... Car* p = new Car(3,economy,1500,60); drive(p,bs_home,MH); // belps a szimullt forgalmi helyzetbe // ... }

A csompont-osztlyoknak rendszerint szksgk van konstruktorokra s ez a konstruktor gyakran bonyolult. Ebben a csompont-osztlyok eltrnek az absztrakt osztlyoktl, melyeknek ritkn van konstruktoruk. A Car mveleteinek megvalstsai ltalban a Vehicle bzisosztlybl vett mveleteket hasznljk, a Car-okat hasznl elemek pedig a bzisosztlyok szolgltatsaira tmaszkodnak. A Vehicle pldul megadja a sllyal s mrettel foglalkoz alapfggvnyeket, gy a Car-nak nem kell gondoskodnia azokrl:
bool Bridge::can_cross(const Vehicle& r) { if (max_weight<r.weight()) return false; // ... }

Ez lehetv teszi a programoz szmra, hogy j osztlyokat (Car s Truck) hozzon ltre a Vehicle csompont-osztlybl, gy, hogy csak az attl klnbz tulajdonsgokat s mveleteket kell megadnia, illetve elksztenie. Ezt az eljrst gyakran gy emltik, mint klnbsg ltali programozst vagy bvt programozst. Sok csompont-osztlyhoz hasonlan a Car maga is alkalmas a tovbbi szrmaztatsra. Az Ambulance-nek pldul tovbbi adatokra s mveletekre van szksge a vszhelyzetek kezelshez:
class Ambulance : public Car, public Emergency { public: Ambulance(); // a Car lnyeges virtulis fggvnyeinek fellrsa void turn(Direction); // ... // az Emergency lnyeges virtulis fggvnyeinek fellrsa

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1043

virtual void dispatch_to(const Location&); // ... // Ambulance-ra vonatkoz fggvnyek hozzadsa virtual int patient_capacity(); // ... // hordgyak szma

};

sszegezzk a csompont-osztlyok jellemzit: 1. Mind megvalstst, mind a felhasznlknak adott szolgltatsokat tekintve bzisosztlyaira tmaszkodik. 2. Szlesebb felletet (vagyis tbb nyilvnos tagfggvnnyel rendelkez felletet) ad felhasznlinak, mint bzisosztlyai. 3. Elsdlegesen (de nem felttlenl kizrlagosan) a nyilvnos felletben lev virtulis fggvnyekre tmaszkodik. 4. Fgg az sszes (kzvetlen s kzvetett) bzisosztlytl. 5. Csak bzisosztlyaival sszefggsben rthet meg. 6. Tovbbi szrmaztats bzisosztlyaknt felhasznlhat. 7. Objektumok ltrehozsra hasznlhat. Nem minden csompont-osztly fog eleget tenni az 1, 2, 6 s 7 mindegyiknek, de a legtbb igen. A 6-nak eleget nem tev osztlyok a konkrt tpusokra emlkeztetnek, gy konkrt csompont-osztlyoknak nevezhetk. A konkrt csompont-osztlyok pldul absztrakt osztlyok megvalstshoz hasznlhatk (12.4.2), az ilyen osztlyok vltozi szmra pedig statikusan s a veremben is foglalhatunk helyet. Az ilyen osztlyokat nha levl osztlyoknak nevezzk. Ne feledjk azonban, hogy minden kd, amely egy osztlyra hivatkoz mutatn vagy referencin virtulis fggvnyek ltal vgez mveletet, szmtsba kell, hogy vegye egy tovbbi osztly szrmaztatsnak lehetsgt (vagy nyelvi tmogats nlkl el kell fogadnia, hogy nem trtnt tovbbi szrmaztats). Azok az osztlyok, melyek nem tesznek eleget a 7-es pontnak, az absztrakt tpusokra emlkeztetnek, gy absztrakt csompontosztlyoknak hvhatk. A hagyomnyok miatt sajnos sok csompont-osztlynak van legalbb nhny protected tagja, hogy a szrmaztatott osztlyok szmra kevsb korltozott felletrl gondoskodjon (12.4.1.1). A 4-es pontbl az kvetkezik, hogy a csompont-osztlyok fordtshoz a programoznak be kell ptenie (#include) azok sszes kzvetlen s kzvetett bzisosztly-deklarciit s az sszes olyan deklarcit, melyektl azok fggnek. Ez megint csak egy klnbsg a csompont-osztlyok s az absztrakt tpusok kztt, az utbbiakat hasznl programelemek ugyanis nem fggnek a megvalstsukra hasznlt osztlyoktl, gy nem kell azokat a fordtshoz bepteni.

Forrs: http://www.doksi.hu

1044

Tervezs a C++ segtsgvel

25.4.1. Felletek mdostsa


Minden csompont-osztly egy osztlyhierarchia tagja, egy hierarchin bell viszont nem minden osztlynak kell ugyanazt a felletet nyjtania. Nevezetesen, egy szrmaztatott osztly tbb tagfggvnnyel rendelkezhet s egy testvrosztly fggvnyhalmaza is teljesen ms lehet. A tervezs szempontjbl a dynamic_cast (15.4) gy tekinthet, mint az az eljrs, mellyel egy objektumot megkrdezhetnk, biztost-e egy adott felletet. Pldaknt vegynk egy egyszer objektum I/O (bemeneti/kimeneti) rendszert. A felhasznl egy adatfolyambl objektumokat akar olvasni, meg kvnja hatrozni, hogy a vrt tpusak-e, majd fel akarja hasznlni azokat:
void user() { // ... felttelezs szerint alakzatokat (Shape) tartalmaz fjl megnyitsa // ss ltal azonostott bemeneti adatfolyamknt ... Io_obj* p = get_obj(ss); // objektum olvassa az adatfolyamrl

if (Shape* sp = dynamic_cast<Shape*>(p)) { sp->draw(); // alakzat hasznlata // ... } else { // hopp: ez nem alakzat }

A user() fggvny az alakzatokat kizrlag az absztrakt Shape osztlyon keresztl kezeli s ezltal minden fajta alakzatot kpes hasznlni. A dynamic_cast hasznlata lnyeges, mert az objektum I/O rendszer szmos objektumfajtt kezel, a felhasznl pedig vletlenl olyan fjlt is megnyithatott, amely olyan osztlyok egybknt tkletesen j objektumait tartalmazza, melyekrl a felhasznl nem is hallott. A rendszer felttelezi, hogy minden olvasott vagy rt objektum olyan osztlyhoz tartozik, amely az Io_obj leszrmazottja. Az Io_obj osztlynak tbbalaknak (polimorfnak) kell lennie, hogy lehetv tegye szmunkra a dynamic_cast hasznlatt:
class Io_obj { public: virtual Io_obj* clone() const =0; virtual ~Io_obj() {} };

// tbbalak

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1045

A rendszer ltfontossg eleme a get_obj() fggvny, mely egy istream adatfolyambl adatokat olvas s osztlyobjektumokat hoz ltre azokra alapozva. Tegyk fel, hogy a bemeneti adatfolyamban az egyik objektumot kpvisel adatok eltagknt egy, az objektum osztlyt azonost karakterlncot tartalmaznak. A get_obj() fggvny feladata ezt elolvasni, majd egy olyan fggvnyt meghvni, amely kpes a megfelel osztly objektumot ltrehozni s kezdrtket adni neki:
typedef Io_obj* (*PF)(istream&); mutat map<string,PF> io_map; // Io_obj* visszatrsi tpus fggvnyre hivatkoz

// karakterlncok hozzrendelse a ltrehoz fggvnyekhez

bool get_word(istream& is, string& s); // sz beolvassa is-bl s-be Io_obj* get_obj(istream& s) { string str; bool b = get_word(s,str); if (b == false) throw No_class();

// a kezd sz beolvassa str-be // io formtumhiba

PF f = io_map[str]; // az "str" kikeresse, hogy kivlasszuk a fggvnyt if (f == 0) throw Unknown_class(); // nem talljuk "str"-t } return f(s); // objektum ltrehozsa az adatfolyambl

Az io_map nev map neveket tartalmaz karakterlncok s a nevekhez tartoz osztlyok objektumait ltrehozni kpes fggvnyek prjaibl ll. A Shape osztlyt a szoksos mdon hatrozhatjuk meg, azzal a kivtellel, hogy a user() ltal megkvetelten az Io_obj-bl szrmaztatjuk:
class Shape : public Io_obj { // ... };

Mg rdekesebb (s sok esetben valszerbb) lenne azonban egy meghatrozott Shape-et (2.6.2) mdosts nlkl felhasznlni:
class Io_circle : public Circle, public Io_obj { public: Io_circle* clone() const { return new Io_circle(*this); } // msol konstruktor hasznlata Io_circle(istream&); // kezdeti rtkads a bemeneti adatfolyambl

Forrs: http://www.doksi.hu

1046

Tervezs a C++ segtsgvel

};

static Io_obj* new_circle(istream& s) { return new Io_circle(s); } // ...

Ez annak pldja, hogyan lehet egy osztlyt egy absztrakt osztly hasznlatval beilleszteni egy hierarchiba, ami kevesebb elreltst ignyel, mint amennyi ahhoz kellene, hogy elszr csompont-osztlyknt hozzuk ltre (12.4.2, 25.3). Az Io_circle(istream&) konstruktor az objektumokat az iostream-bl kapott adatokkal tlti fel. A new_circle() fggvnyt az io_map-ba tesszk, hogy az osztlyt a bemeneti/kimeneti rendszer szmra ismertt tegyk:
io_map["Io_circle"]=&Io_circle::new_circle;

Ms alakzatokat hasonl mdon hozhatunk ltre:


class Io_triangle : public Triangle, public Io_obj { // ... };

Ha az objektum I/O rendszer elksztse tl fradsgos, segthet egy sablon:


template<class T> class Io : public T, public Io_obj { public: Io* clone() const { return new Io(*this); } // fellbrlja Io_obj::clone()-t Io(istream&); // kezdeti rtkads bemeneti adatfolyambl

};

static Io* new_io(istream& s) { return new Io(s); } // ...

Ha a fenti adott, meghatrozhatjuk az Io_circle-t:


typedef Io<Circle> Io_circle;

Termszetesen tovbbra is pontosan definilnunk kell az Io<Circle>::Io(istream&)-et, mivel annak rszleteiben ismernie kell a Circle-t. Az Io sablon plda arra, hogyan illeszthetnk be konkrt tpusokat egy osztlyhierarchiba egy ler (handle) segtsgvel, amely az adott hierarchia egy csompontja. A sablon sajt paramterbl szrmaztat, hogy lehetv tegye az talaktst az Io_obj-rl, de ez sajnos kizrja az Io-nak beptett tpusokra trtn alkalmazst:

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1047

typedef Io<Date> Io_date; typedef Io<int> Io_int;

// konkrt tpus beburkolsa // hiba: nem szrmaztathatunk beptett tpusbl

A problma gy kezelhet, ha a beptett tpusok rszre kln sablont biztostunk vagy ha egy beptett tpust kpvisel osztlyt hasznlunk (25.10[1]). Ez az egyszer objektum I/O rendszer nem teljesthet minden kvnsgot, de majdnem elfr egyetlen lapon s f eljrsait szmos clra felhasznlhatjuk, pldul meghvhatjuk velk a felhasznl ltal karakterlnccal megadott fggvnyeket vagy ismeretlen tpus objektumokat kezelhetnk futsi idej tpusazonostssal feldertett felleten keresztl.

25.5. Mveletek
A C++-ban a mveletek meghatrozsnak legegyszerbb s legkzenfekvbb mdja fggvnyek rsa. Ha azonban egy adott mveletet vgrehajts eltt ksleltetni kell, t kell vinni mshov, prostani kell ms mveletekkel vagy a mvelet sajt adatokat ignyel (25.10 [18, 19]), gyakran clszerbb azt osztly alakjban megadni, mely vgre tudja hajtani a kvnt eljrst s egyb szolgltatsokat is nyjthat. J pldk erre a szabvnyos algoritmusok ltal hasznlt fggvny-objektumok (18.4), valamint az iostream-mel hasznlt mdostk (21.4.6). Az elbbi esetben a tnyleges mveletet a fggvnyhv opertor hajtja vgre, mg az utbbinl a << vagy a >> opertor. A Form (21.4.6.3) s a Matrix (22.4.7) esetben a vgrehajts ksleltetsre egyedi (compositor) osztlyokat hasznlunk, amg a hatkony vgrehajtshoz elegend informci ssze nem gylik. A mveletosztlyok ltalnos formja egy (jellemzen valamilyen csinld (do_it) nev) virtulis fggvnyt tartalmaz egyszer osztly:
class Action { public: virtual int do_it(int) = 0; virtual ~Action() { } };

Ha ez adott, olyan kdot rhatunk mondjuk egy ment amely a mveleteket ksbbi vgrehajtsra elraktrozhatja, anlkl, hogy fggvnymutatkat hasznlna, brmit is tudna a meghvott objektumokrl vagy egyltaln ismern a meghvott mvelet nevt:

Forrs: http://www.doksi.hu

1048

Tervezs a C++ segtsgvel

class Write_file : public Action { File& f; public: int do_it(int) { return f.write().succeed(); } }; class Error_response : public Action { string message; public: int do_it(int); }; int Error_response::do_it(int) { Response_box db(message.c_str(), "Folytat","Mgse","Ismt"); switch (db.get_response()) { case 0: return 0; case 1: abort(); case 2: current_operation.redo(); return 1; }

Action* actions[] = { new Write_file(f), new Error_response("Megint elrontotta"), // ... };

Az Action-t hasznl kd teljesen elszigetelhet, nem kell, hogy brmit is tudjon az olyan szrmaztatott osztlyokrl, mint a Write_file s az Error_response. Ez igen erteljes mdszer, mellyel vatosan kell bnniuk azoknak, akik leginkbb a funkcionlis elemekre bontsban szereztek tapasztalatot. Ha tl sok osztly kezd az Action-re hasonltani, lehet, hogy a rendszer tfog terve tlzottan mveletkzpontv vlt. Megemltend, hogy az osztlyok jvbeni hasznlatra is trolhatnak mveleteket, illetve olyanokat is tartalmazhatnak, melyeket egy tvoli gp hajt majd vgre (25.10 [18]).

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1049

25.6. Felletosztlyok
Az egyik legfontosabb osztlyfajta a szerny s legtbbszr elhanyagolt felletosztly. Egy felletosztly nem sok dolgot csinl ha csinlna, nem lenne felletosztly , csupn helyi szksgletekhez igaztja egy szolgltats megjelenst. Mivel elvileg lehetetlen mindig, minden szksgletet egyformn jl kielgteni, a felletosztlyok nlklzhetetlenek, mert anlkl teszik lehetv a kzs hasznlatot, hogy minden felhasznlt egyetlen, kzs knyszerzubbonyba erszakolnnak. A felletek legtisztbb formjukban mg gpi kdot sem ignyelnek. Vegyk a 13.5-bl a Vector specializlt vltozatt:
template<class T> class Vector<T*> : private Vector<void*> { public: typedef Vector<void*> Base; Vector() : Base() {} Vector(int i) : Base(i) {} T*& operator[](int i) { return static_cast<T*&>(Base::operator[](i)); } }; // ...

Ez a (rszlegesen) specializlt vltozat a nem biztonsgos Vector<void*>-ot egy sokkal hasznlhatbb, tpusbiztos vektorosztly-csaldd teszi. A helyben kifejtett (inline) fggvnyek gyakran nlklzhetetlenek ahhoz, hogy a felletosztlyokat megengedhessk magunknak. A fentihez hasonl esetekben, ahol a helyben kifejtett kzvett fggvny csak tpusigaztst vgez, sem a futsi id, sem a trigny nem n. Termszetesen az absztrakt tpust brzol absztrakt bzisosztlyok, melyeket konkrt tpusok (25.2) valstanak meg, szintn a felletosztlyok egy formjt jelentik, mint ahogy a 25.7 leri is azok. Itt azonban azon osztlyokra sszpontostunk, melyeknek a fellet igaztsn kvl nincs ms feladatuk. Vegyk kt, tbbszrs rklst hasznl hierarchia sszefslsnek problmjt. Mit lehet tenni, ha nvtkzs lp fel, vagyis kt osztly ugyanazt a nevet hasznlja teljesen eltr mveleteket vgz virtulis fggvnyekhez? Vegynk pldul egy vadnyugati videojtkot, melyben a felhasznli beavatkozsokat egy ltalnos ablakosztly kezeli:

Forrs: http://www.doksi.hu

1050

Tervezs a C++ segtsgvel

class Window { // ... virtual void draw(); }; class Cowboy { // ... virtual void draw(); };

// kp megjelentse

// pisztoly elrntsa a pisztolytskbl

class Cowboy_window : public Cowboy, public Window { // ... };

A jtkban a cowboy mozgatst egy Cowboy_window kpviseli s ez kezeli a felhasznl (jtkos) s a cowboy figura kztti klcsnhatsokat is. A Window s a Cowboy tagokknt val bevezetse helyett tbbszrs rklst szeretnnk hasznlni, mivel szmos kiszolgl fggvnyt kell meghatroznunk mind a Window-k, mind a Cowboy-ok rszre, a Cowboy_window-kat pedig olyan fggvnyeknek szeretnnk tadni, melyek nem kvnnak a programoztl klnleges munkt. Ez azonban ahhoz a problmhoz vezet, hogy meg kell adni a Cowboy::draw() s Window::draw() fggvnyek Cowboy_window vltozatt. A Cowboy_window-ban csak egy draw() nev fggvny lehet. Ennek viszont mivel a Window-kat s Cowboy-okat a Cowboy_window-k ismerete nlkl kezeljk fell kell rnia mind a Cowboy, mind a Window draw() fggvnyt. Egyetlen vltozattal nem rhatjuk fell mind a kettt, mert a kzs nv ellenre a kt draw() fggvny szerepe ms. Vgezetl azt is szeretnnk, hogy a Cowboy_window egyedi, egyrtelm neveket biztostson az rklt Cowboy::draw() s Window:: draw() fggvnyek szmra. A problma megoldshoz szksgnk van egy-egy tovbbi osztlyra a Cowboy-hoz s a Window-hoz. Ezek az osztlyok vezetik be a kt j nevet a draw() fggvnyekre s biztostjk, hogy a Cowboy-ban s a Window-ban a draw() fggvnyhvsok az j nven hvjk a fggvnyeket:
class CCowboy : public Cowboy { public: virtual int cow_draw() = 0; void draw() { cow_draw(); } }; class WWindow : public Window { public: virtual int win_draw() = 0; void draw() { win_draw(); } }; // fellet a Cowboy-hoz: a draw()-t tnevezi // fellrja Cowboy::draw()-t // fellet a Window-hoz: a draw()-t tnevezi // fellrja Window::draw()-t

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1051

Most mr sszerakhatunk egy Cowboy_window-t a CCowboy s a WWindow felletosztlyokbl s a kvnt hatssal fellbrlhatjuk a cow_draw()-t s win_draw()-t:
class Cowboy_window : public CCowboy, public WWindow { // ... void cow_draw(); void win_draw(); };

Vegyk szre, hogy a problma csak amiatt volt komoly, hogy a kt draw() fggvnynek ugyanaz a paramtertpusa. Ha a paramtertpusok klnbznek, a szoksos tlterhelsfeloldsi szablyoknak ksznheten nem lesz problma, annak ellenre, hogy az egymssal kapcsolatban nem lv fggvnyeknek ugyanaz a nevk. Egy felletosztly minden hasznlatra elkpzelhetnk egy klnleges cl nyelvi bvtst, mely a kvnt igaztst hatkonyabban vagy elegnsabban tudn elvgezni. A felletosztlyok egyes hasznlatai azonban ritkk, s ha mindet egyedi nyelvi szerkezettel tmogatnnk, tlzottan bonyolultt tennnk programjainkat. Az osztlyhierarchik sszefslsbl ered nvtkzsek nem ltalnosak (ahhoz kpest, hogy milyen gyakran r osztlyt egy programoz), inkbb abbl erednek, hogy eltr krnyezetben ltrehozott hierarchikat olvasztunk ssze (pl. jtkokat s ablakrendszereket). Az ilyen eltr hierarchikat nem knny sszefslni s a nvtkzsek feloldsa gyakran csak csepp a tengerben a programoz szmra. Problmt jelenthet az eltr hibakezels, kezdeti rtkads s trkezelsi md is. A nvtkzsek feloldst itt azrt trgyaljuk, mert a kzvett/tovbbt szerepet betlt felletosztlyok bevezetse ms terleteken is alkalmazhat; nem csak nevek megvltoztatsra, hanem paramterek s visszatrsi rtkek tpusnak mdostsra, futsi idej ellenrzs bevezetsre stb. is. Minthogy a CCowboy::draw() s WWindow::draw() fggvnyek virtulisak, nem optimalizlhatk egyszer helyben kifejtssel. Lehetsges viszont, hogy a fordt felismeri, hogy ezek egyszer tovbbt fggvnyek, s a rajtuk tmen hvsi lncok alapjn kpes optimlis kdot kszteni.

25.6.1. Felletek igaztsa


A felletfggvnyek egyik f felhasznlsa egy fellet igaztsa gy, hogy az jobban illeszkedjk a felhasznl elvrsaihoz; vagyis egyetlen felletbe helyezzk azt a kdot, amely klnben a felhasznli kdon bell szt lenne szrva. A szabvnyos vector pldul nulla alap, azaz az els elem indexe 0. Azoknak a felhasznlknak, akik a 0-tl size-1-ig terjed tartomnytl eltrt akarnak, igaztst kell vgeznik:

Forrs: http://www.doksi.hu

1052

Tervezs a C++ segtsgvel

void f() { vector v<int>(10);

// [0:9] tartomny

// gy tesznk, mintha v a [1:10] tartomnyban lenne for (int i = 1; i<=10; i++) { v[i-1] = 7; // ne felejtsk az indexet igaztani // ... }

Mg jobb, ha a vector-nak tetszleges hatrokat biztostunk:


class Vector : public vector<int> { int lb; public: Vector(int low, int high) : vector<int>(high-low+1) { lb=low; } int& operator[](int i) { return vector<int>::operator[](i-lb); } int low() { return lb; } int high() { return lb+size()-1; }

};

A Vector az albbi mdon hasznlhat:


void g() { Vector v(1,10); for (int i = 1; i<=10; i++) { v[i] = 7; // ... }

// [1:10] tartomny

Ez az elz pldhoz kpest nem okoz tbbletterhelst. Vilgos, hogy a Vector vltozatot knnyebb olvasni s kisebb a hibzs lehetsge is. A felletosztlyok rendszerint meglehetsen kicsik s kevs feladatot vgeznek, de mindentt felbukkannak, ahol klnbz hagyomnyok szerint megrt programoknak kell egyttmkdnik, mivel ilyenkor a klnbz szablyok kztt kzvettsre van szksg.

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1053

A felletosztlyokat gyakran hasznljk pldul arra, hogy nem C++ kdnak C++ felletet adjanak, s az alkalmazs kdjt elszigeteljk a knyvtraktl (nyitva hagyva egy knyvtr msikkal val helyettestsnek a lehetsgt). A felletosztlyok msik fontos felhasznlsi terlete az ellenrztt vagy korltozott felletek biztostsa. Nem szokatlan pldul, ha olyan egsz tpus vltozink vannak, melyek rtke csak egy adott tartomnyon bell mozoghat. Ez (futsi idben) egy egyszer sablonnal knyszerthet ki:
template<int low, int high> class Range { int val; public: class Error { }; // kivtelosztly Range(int i) { Assert<Error>(low<=i&&i<high); val = i; } Range operator=(int i) { return *this=Range(i); } operator int() { return val; } // ... // lsd 24.3.7.2

};

void f(Range<2,17>); void g(Range<-10,10>); void h(int x) { Range<0,2001> i = x; int i1 = i; f(3); f(17); g(-7); g(100);

// Range::Error kivtelt vlthat ki

// Range::Error kivtelt vlt ki // Range::Error kivtelt vlt ki

A Range sablon knnyen bvthet gy, hogy tetszleges skalr tpus tartomnyok kezelsre legyen kpes (25.10[7]). Azokat a felletosztlyokat, amelyek a ms osztlyokhoz val hozzfrst ellenrzik vagy azok fellett igaztjk, nha beburkol (csomagol, wrapper) osztlyoknak nevezzk.

Forrs: http://www.doksi.hu

1054

Tervezs a C++ segtsgvel

25.7. Ler osztlyok


Az absztrakt tpusok hatkony elvlasztst biztostanak a felletek s megvalstsaik kztt, de az absztrakt tpus ltal adott fellet s annak egy konkrt tpus ltal nyjtott megvalstsa kztti kapcsolat ahogyan a 25.3-ban hasznltuk tarts. Nem lehet pldul egy absztrakt bejrt az egyik forrsrl (mondjuk egy halmazrl) tktni egy msikra (mondjuk egy adatfolyamra), ha az eredeti forrs kimerlt. Tovbb, hacsak nem mutatkon vagy referencikon keresztl kezelnk egy absztrakt osztlyt kpvisel objektumot, elvesztjk a virtulis fggvnyek elnyeit. A felhasznli kd fggni fog a megvalst osztlyoktl, mivel az absztrakt tpusok szmra nem foglalhat hely statikusan vagy a veremben (belertve az rtk szerinti paramtertvtelt is), anlkl, hogy tudnnk a tpus mrett. A mutatk s hivatkozsok hasznlata azzal jr, hogy a trkezels terhe a felhasznl kdra hrul. Az absztrakt osztlyos megkzelts msik korltja az, hogy az osztlyobjektumok rgztett mretek. Az osztlyokat azonban fogalmak brzolsra hasznljuk, melyek megvalstshoz klnbz mennyisg trterlet kell. E krdsek kezelsnek kedvelt mdja kt rszre osztani azt, amit egyetlen objektumknt hasznlunk. Az egyik lesz a felhasznli felletet ad ler (handle), a msik pedig az brzols, amely az objektum llapotnak egszt vagy annak java rszt trolja. A ler s az brzols kztti kapcsolatot ltalban egy, a lerban lev mutat biztostja. A lerban az brzolsra hivatkoz mutatn kvl ltalban mg van egy-kt adat, de nem sok. Ebbl kvetkezik, hogy a ler szerkezete rendszerint stabil (mg akkor is, ha az brzols megvltozik), illetve hogy a lerk meglehetsen kicsik s viszonylag szabadon mozgathatk, gy a felhasznlnak nem kell mutatkat s referencikat hasznlnia.

Ler

brzols

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1055

A 11.12 String-je a ler egyszer pldja. A ler felletet, hozzfrs-szablyozst s trkezelst biztost az brzols rszre. Ebben az esetben mind a ler, mind az brzols konkrt tpusok, br az brzol osztly tbbnyire absztrakt osztly szokott lenni. Vegyk a 25.3-bl a Set absztrakt tpust. Hogyan gondoskodhatunk szmra egy lerrl s milyen elnykkel, illetve htrnyokkal jrhat ez? Egy adott halmazosztlyhoz egyszeren megadhatunk egy lert, a -> opertor tlterhelsvel:
template<class T> class Set_handle { Set<T>* rep; public: Set<T>* operator->() { return rep; } }; Set_handle(Set<T>* pp) : rep(pp) { }

Ez nem befolysolja jelentsen a Set-ek hasznlatnak mdjt; egyszeren Set_handle-eket adunk t Set&-ek vagy Set*-ok helyett:
void f(Set_handle<int> s) { for (int* p = s->first(); p; p = s->next()) { // ... } } void user() { Set_handle<int> sl(new List_set<int>); Set_handle<int> v(new Vector_set<int>(100)); f(sl); f(v);

Gyakran tbbet kvnunk egy lertl, mint hogy a hozzfrsrl gondoskodjon. Pldul, ha a Set osztlyt s a Set_handle osztlyt egytt terveztk, a hivatkozsokat knnyen megszmllhatjuk, ha minden Set-ben elhelyeznk egy hasznlatszmllt. Persze a lert ltalban nem akarjuk egytt tervezni azzal, aminek a lerja, gy nll objektumban kell trolnunk minden adatot, amit a lernak biztostania kell. Ms szval, szksgnk lenne nem tolakod (non-intensive) lerkra is a tolakodk mellett. me egy ler, mely felszmol egy objektumot, amikor annak utols lerja is megsemmisl:

Forrs: http://www.doksi.hu

1056

Tervezs a C++ segtsgvel

template<class X> class Handle { X* rep; int* pcount; public: X* operator->() { return rep; } Handle(X* pp) : rep(pp), pcount(new int(1)) { } Handle(const Handle& r) : rep(r.rep), pcount(r.pcount) { (*pcount)++; } Handle& operator=(const Handle& r) { if (rep == r.rep) return *this; if (--(*pcount) == 0) { delete rep; delete pcount; } rep = r.rep; pcount = r.pcount; (*pcount)++; return *this; } ~Handle() { if (--(*pcount) == 0) { delete rep; delete pcount; } } }; // ...

Egy ilyen ler szabadon tadhat:


void f1(Handle<Set>); Handle<Set> f2() { Handle<Set> h(new List_set<int>); // ... return h; } void g() { Handle<Set> hh = f2(); f1(hh); // ... }

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1057

Itt az f2()-ben ltrehozott halmaz trldik a g()-bl val kilpskor hacsak f1() fenn nem tartja annak egy msolatt. (A programoznak errl nem kell tudnia.) Termszetesen ennek a knyelemnek ra van, de a hasznlatszmll trolsnak s fenntartsnak kltsge a legtbb alkalmazsnl elfogadhat. Nha hasznos az brzolsra hivatkoz mutatt a lerbl kinyerni s kzvetlenl felhasznlni. Erre akkor lehet szksg, ha egy objektumot egy olyan fggvnynek kell tadni, amely nem ismeri a lerkat. Ez a megolds jl mkdik, feltve, hogy a hvott fggvny nem semmisti meg a neki tadott objektumot vagy egy arra hivatkoz mutatt nem trol a hvhoz val visszatrs utni hasznlatra. Egy olyan mvelet szintn hasznos lehet, amely a lert egy j brzolshoz kapcsolja:
template<class X> class Handle { // ... X* get_rep() { return rep; } void bind(X* pp) { if (pp != rep) { if (--*pcount == 0) { delete rep; *pcount = 1; } else pcount = new int(1); rep = pp; } }

// pcount jrahasznostsa // j pcount

};

Vegyk szre, hogy Handle-bl j osztlyokat szrmaztatni nem klnsebben hasznos, ez ugyanis egy konkrt tpus, virtulis fggvnyek nlkl. Az alapelv, hogy egy bzisosztly ltal meghatrozott teljes osztlycsaldhoz egyetlen ler osztlyunk legyen. Ebbl a bzisosztlybl szrmaztatni viszont mr hasznos lehet. Ez a csompont-osztlyokra ppgy rvnyes, mint az absztrakt tpusokra. Fenti formjban a Handle nem foglalkozik rklssel. Ahhoz, hogy egy olyan osztlyunk legyen, mely gy mkdik, mint egy valdi hasznlatszmll, a Handle-t egytt kell hasznlni a 13.6.3.1 Ptr-jvel (lsd 25.10[2]).

Forrs: http://www.doksi.hu

1058

Tervezs a C++ segtsgvel

Az olyan lert, melynek fellete kzel azonos azon osztlyval, melynek lerja, gyakran nevezzk proxy-nak. Ez klnsen azokra a lerkra vonatkozik, melyek tvoli gpen lv objektumokra hivatkoznak.

25.7.1. A lerk mveletei


A -> opertor tlterhelse lehetv teszi, hogy egy ler minden hozzfrskor megkapja a vezrlst, s valamilyen mveletet vgezzen egy objektumon. A lern keresztl hozzfrhet objektum felhasznlsainak szmrl pldul statisztikt kszthetnk:
template <class T> class Xhandle { T* rep; int no_of_accesses; public: T* operator->() { no_of_accesses++; return rep; } }; // ...

Azon lerk, melyeknl a hozzfrs eltt s utn is valamilyen mveletet kell vgezni, kidolgozottabb kdot ignyelnek. Tegyk fel pldul, hogy egy olyan halmazt szeretnnk, amely zrolhat, amg beszrs vagy eltvolts folyik. Az brzol osztly fellett lnyegben meg kell ismtelni a ler osztlyban:
template<class T> class Set_controller { Set<T>* rep; Lock lock; // ... public: void insert(T* p) { Lock_ptr x(lock); rep->insert(p); } // lsd 14.4.1 void remove(T* p) { Lock_ptr x(lock); rep->remove(p); } int is_member(T* p) { return rep->is_member(p); } T get_first() { T* p = rep->first(); return p ? *p : T(); } T get_next() { T* p = rep->next(); return p ? *p : T(); } T first() { Lock_ptr x(lock); T tmp = *rep->first(); return tmp; } T next() { Lock_ptr x(lock); T tmp = *rep->next(); return tmp; } }; // ...

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1059

Ezekrl a tovbbt fggvnyekrl gondoskodni fradsgos (gy hibt is vthetnk kzben), jllehet nehzsget nem jelent s a futsi idt sem nveli. Vegyk szre, hogy a Set-nek csak nmelyik fggvnye kvn zrolst. Tapasztalatom szerint ltalnos, hogy egy el- s uttevkenysgeket ignyl osztly a mveleteket csak nhny tagfggvnynl kvnja meg. A minden mveletnl val zrols ahogy egyes rendszerfigyelknl lenni szokott felesleges zrolsokhoz vezet s szreveheten lassthatja a prhuzamos vgrehajtst. A lern vgzett mveletek alapos kidolgozsnak elnye a -> opertor tlterhelsvel szemben az, hogy a Set_controller osztlybl szrmaztathatunk. Sajnos a lerk nhny elnys tulajdonsgt feladjuk, ha a szrmaztatott osztlyhoz adattagokat tesznk, mert a kzsen hasznlt kd mennyisge az egyes lerkban lev kd mennyisghez viszonytva cskken.

25.8. Keretrendszerek
A 25.225.7-ben lert osztlyfajtkbl ptett komponensek azltal tmogatjk a kdtervezst s -jrahasznostst, hogy ptkockkat s kombincis lehetsgeket biztostanak. A programozk s az alkalmazskszt eszkzk ptik fel azt a vzat, amelybe ezek az ptkockk beleillenek. A tervezs s jrahasznosts tmogatsnak egy msik, ltalban nehezebb mdja egy olyan, kzs vzat ad kd megrsa, melybe az alkalmazskszt ptkockkknt az adott alkalmazsra jellemz kdokat illeszti be. Ez az, amit ltalban keretrendszernek (application framework) hvunk. Az ilyen vzat biztost osztlyok fellete gyakran olyan kvr, hogy hagyomnyos rtelemben aligha nevezhetk tpusoknak; inkbb teljes alkalmazsnak tnnek, br nem vgeznek semmilyen tevkenysget; azokat az alkalmazsprogramoz biztostja. Pldakppen vegynk egy szrt, vagyis egy olyan programot, amely egy bemeneti adatfolyambl olvas, majd annak alapjn (esetleg) elvgez nhny mveletet, (esetleg) egy kimeneti adatfolyamot hoz ltre, s (esetleg) ad egy vgeredmnyt. Els tletnk bizonyra az, hogy a programhoz olyan keretrendszert ksztsnk, amely olyan mvelethalmazt ad meg, melyet egy alkalmazsprogramoz biztost:

Forrs: http://www.doksi.hu

1060

Tervezs a C++ segtsgvel

class Filter { public: class Retry { public: virtual const char* message() { return 0; } }; virtual void start() { } virtual int read() = 0; virtual void write() { } virtual void compute() { } virtual int result() = 0; virtual int retry(Retry& m) { cerr << m.message() << '\n'; return 2; } }; virtual ~Filter() { }

Azon fggvnyeket, melyeket a szrmaztatott osztlynak kell biztostania, tisztn virtulis (pure virtual) fggvnyekknt deklarltuk; a tbbit egyszeren gy definiltuk, mint amik nem vgeznek mveletet. A keretrendszer gondoskodik egy fciklusrl s egy kezdetleges hibakezel eljrsrl is:
int main_loop(Filter* p) { for(;;) { try { p->start(); while (p->read()) { p->compute(); p->write(); } return p->result(); } catch (Filter::Retry& m) { if (int i = p->retry(m)) return i; } catch (...) { cerr << "Vgzetes szrhiba\n"; return 1; } } }

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1061

A programot vgl gy rhatjuk meg:


class My_filter : public Filter { istream& is; ostream& os; int nchar; public: int read() { char c; is.get(c); return is.good(); } void compute() { nchar++; } int result() { os << nchar << " elolvasott karakter\n"; return 0; } }; My_filter(istream& ii, ostream& oo) : is(ii), os(oo), nchar(0) { }

s gy indthatjuk el:
int main() { My_filter f(cin,cout); return main_loop(&f); }

Termszetesen ahhoz, hogy igazn hasznt vegyk, a keretrendszernek tbb szerkezetet s jval tbb szolgltatst kellene nyjtania, mint ebben az egyszer pldban. A keretrendszer ltalban csompont-osztlyokbl ll hierarchia. Ha egy mlyen egymsba gyazott elemekbl ll hierarchiban az alkalmazsprogramozval ratjuk meg a levl osztlyokat, lehetv vlik a kzs elemek tbb program ltal val hasznlata s a hierarchia ltal nyjtott szolgltatsok jrahasznostsa. A keretrendszert egy knyvtr is tmogathatja, olyan osztlyokkal, melyek az alkalmazsprogramoz szmra a mveletosztlyok meghatrozsnl hasznosnak bizonyulhatnak.

25.9. Tancsok
[1] Az egyes osztlyok hasznlatra vonatkozan hozzunk megfontolt dntseket. 25.1. [2] vakodjunk a vlaszts knyszertl, melyet az osztlyok eltr fajti okoznak. 25.1. [3] Egyszer, fggetlen fogalmak brzolsra hasznljunk konkrt tpusokat. 25.2. [4] Hasznljunk konkrt tpusokat azon fogalmak brzolsra, melyeknl nlklzhetetlen az optimlishoz kzeli hatkonysg. 25.2.

Forrs: http://www.doksi.hu

1062

Tervezs a C++ segtsgvel

[5] Konkrt osztlybl ne szrmaztassunk. 25.2. [6] Hasznljunk absztrakt osztlyokat olyan felletek brzolsra, ahol az objektumok brzolsa vltozhat. 25.3. [7] Hasznljunk absztrakt osztlyokat olyan felletek brzolsra, ahol az objektumok klnbz brzolsainak egytt kell lteznik. 25.3. [8] A ltez tpusok j felleteinek brzolsra hasznljunk absztrakt osztlyokat. 25.3. [9] Ha hasonl fogalmak kzsen hasznljk a megvalsts egyes rszeit, hasznljunk csompont-osztlyokat. 25.4. [10] A megvalsts fokozatos kidolgozshoz hasznljunk csompont-osztlyokat. 25.4. [11] Az objektumok felletnek kinyersre hasznljunk futsi idej tpusazonostst. 25.4.1. [12] Az llapothoz kapcsold mveletek brzolsra hasznljunk osztlyokat. 25.5. [13] Azon mveletek brzolsra, melyeket trolni, tvinni, vagy ksleltetni kell, hasznljunk osztlyokat. 25.5. [14] Ha egy osztlyt j felhasznlshoz kell igaztani (az osztly mdostsa nlkl), hasznljunk felletosztlyokat. 25.6. [15] Ellenrzs hozzadsra hasznljunk felletosztlyokat. 25.6.1. [16] Hasznljunk lerkat, hogy elkerljk a mutatk s referencik kzvetlen hasznlatt. 25.7. [17] A kzsen hasznlt brzolsok kezelsre hasznljunk lerkat. 25.7. [18] Ha az alkalmazsi terlet lehetv teszi, hogy a vezrlsi szerkezetet elre meghatrozzuk, hasznljunk keretrendszert. 25.8.

25.10. Gyakorlatok
1. (*1) A 25.4.1 Io sablonja nem mkdik beptett tpusokra. Mdostsuk gy, hogy mkdjn. 2. (*1.5) A 25.7 Handle sablonja nem tkrzi azon osztlyok rklsi kapcsolatait, amelyeknek lerja. Mdostsuk gy, hogy tkrzze (vagyis lehessen egy Handle<Circle>-lel egy Handle<Shape>-nek rtket adni, de nem fordtva). 3. (*2.5) Ha adott egy String osztly, azt brzolsknt hasznlva s mveleteit virtulis fggvnyekknt megadva hozzunk ltre egy msik karakterlnc-osztlyt. Hasonltsuk ssze a kt osztly teljestmnyt. Prbljunk tallni egy rtelmes osztlyt, amely a legjobban a virtulis fggvnyekkel rendelkez karakterlncosztlybl trtn nyilvnos szrmaztatssal valsthat meg.

Forrs: http://www.doksi.hu

25. Az osztlyok szerepe

1063

4. (*4) Tanulmnyozzunk kt szles krben hasznlt knyvtrat. Osztlyozzuk a knyvtri osztlyokat konkrt tpusokknt, absztrakt tpusokknt, csompontosztlyokknt, ler osztlyokknt, s felletosztlyokknt. Hasznlnak-e absztrakt s konkrt csompont-osztlyokat? Van-e a knyvtrakban lev osztlyokra megfelelbb osztlyozs? Hasznlnak-e kvr felleteket? Milyen trkezelsi mdot hasznlnak? Milyen lehetsgek vannak ha vannak futsi idej tpusinformcira? 5. (*2) A Filter vz (25.8) segtsgvel rjunk olyan programot, mely egy bemeneti adatfolyambl eltvoltja a szomszdos ismtelt szavakat, majd az eredmnyt tmsolja a kimenetre. 6. (*2) A Filter keretrendszer segtsgvel rjunk olyan programot, mely egy bemeneti adatfolyamban megszmllja a szavak gyakorisgt s kimenetknt gyakorisgi sorrendben felsorolja a (sz, szm) prokat. 7. (*1.5) rjunk egy Range sablont, mely sablonparamterekknt veszi t a tartomnyt s az elemtpust. 8. (*1) rjunk egy Range sablont, mely a tartomnyt konstruktor-paramterekknt veszi t. 9. (*2) rjunk egy egyszer karakterlnc-osztlyt, mely nem vgez hibaellenrzst. rjunk egy msik osztlyt, mely ellenrzi az elbbihez val hozzfrst. Vitassuk meg az alapszolgltatsok s a hibaellenrzs elvlasztsnak elnyeit s htrnyait. 10. (*2.5) Ksztsk el a 25.4.1 objektum I/O rendszert nhny tpusra, kztk legalbb az egszekre, a karakterlncokra s egy tetszlegesen kivlasztott osztlyhierarchira. 11. (*2.5) Hatrozzuk meg a Storable osztlyt, mint absztrakt bzisosztlyt a write_out() s read_in() virtulis fggvnyekkel. Az egyszersg kedvrt ttelezzk fel, hogy egy perzisztens trolhely meghatrozshoz egy karakterlnc elegend. Hasznljuk fel a Storage osztlyt egy olyan szolgltatsban, mely a Storable-bl szrmaztatott osztlyok objektumait rja lemezre s ugyanilyen objektumokat olvas lemezrl. Ellenrizzk nhny tetszs szerint vlasztott osztllyal. 12. (*4) Hozzuk ltre a Persistent alaposztlyt a save() s no_save() mveletekkel, melyek egy destruktor ltal ellenrzik, hogy egy objektum bekerlt-e az lland trba. A save()-en s no_save()-en kvl mg milyen hasznlhat mveleteket nyjthatna a Persistent? Teszteljk a Persistent osztlyt nhny tetszs szerint vlasztott osztllyal. Csompont-osztly, konkrt tpus, vagy absztrakt tpus-e Persistent? Mirt? 13. (*3) rjunk egy Stack osztlyt, melynek megvalstsa futsi idben mdosthat. Tipp: Egy jabb indirekci minden problmt megold.

Forrs: http://www.doksi.hu

1064

Tervezs a C++ segtsgvel

14. (*3.5) Ksztsk el az Oper osztlyt, amely egy Id tpus (string vagy C stlus karakterlnc) azonostt s egy mveletet (fggvnymutatt vagy fggvnyobjektumot) tartalmaz. Hatrozzuk meg a Cat_object osztlyt, mely Oper-ek listjt s egy void*-ot tartalmaz. Lssuk el a Cat_object-et egy add_oper(Oper) mvelettel, mely egy Oper-t ad a listhoz; egy remove_oper(Id)-del, mely egy Id-del azonostott Oper-t eltvolt a listbl; valamint egy operator() (Id, arg)-gal, mely meghvja az Id-del azonostott Oper-t. Ksztsnk egy Cat-eket trol vermet egy Cat_object segtsgvel. rjunk egy kis programot ezen osztlyok hasznlatra. 15. (*3) Ksztsnk egy Object sablont a Cat_object osztly alapjn. Hasznljuk fel az Object-et egy String-verem megvalstshoz. rjunk egy kis programot a sablon hasznlatra. 16. (*2.5) Hatrozzuk meg az Object osztly Class nev vltozatt, mely biztostja, hogy az azonos mveletekkel rendelkez objektumok kzs mveletsort hasznljanak. rjunk egy kis programot a sablon hasznlatra. 17. (*2) Ksztsnk egy olyan Stack sablont, mely egy, az Object sablon ltal megvalstott verem rszre egy hagyomnyos, tpusbiztos felletrl gondoskodik. Hasonltsuk ssze ezt a vermet az elz gyakorlatokban tallt veremosztlyokkal. rjunk egy kis programot a sablon hasznlatra. 18. (*3) rjunk egy osztlyt olyan mveletek brzolsra, melyeket vgrehajtsra egy msik gpre kell tvinni. Teszteljk egy msik gpnek tnylegesen elkldtt parancsokkal vagy parancsoknak egy fjlba rsval, melyeket ezutn a fjlbl kiolvasva hajtunk vgre. 19. (*2) rjunk egy osztlyt fggvnyobjektumok alakjban brzolt mveletek egyttes hasznlatra. Ha adott f s g fggvnyobjektum, a Compose(f,g) hozzon ltre egy olyan objektumot, mely egy g-hez illeszked x paramterrel meghvhat, s f(g(x))-et ad vissza, feltve, hogy a g() ltal visszaadott rtk egy, az f() ltal elfogadhat paramtertpus.

Forrs: http://www.doksi.hu

Fggelkek s trgymutat

A fggelkek a C++ nyelvtanval; a C s a C++ kztt, valamint a szabvnyos s a szabvnyosts eltti C++-vltozatok kztt felmerl kompatibilitsi krdsekkel; illetve a nyelv nhny egyb szolgltatsval foglalkoznak. Az igen rszletes trgymutat a knyv lnyeges rsze.

Fejezetek A B C D E I Nyelvtan Kompatibilits Technikai rszletek Helyi sajtossgok Kivtelbiztossg a standard knyvtrban Trgymutat

Forrs: http://www.doksi.hu

Forrs: http://www.doksi.hu

A
Nyelvtan
Nincs nagyobb veszly, ami egy tanrra leselkedik, mint hogy szavakat tant a dolgok helyett. (Marc Block) Bevezets Kulcsszavak Nyelvi egysgek Programok Kifejezsek Utastsok Deklarcik Deklartorok Osztlyok Szrmaztatott osztlyok Klnleges tagfggvnyek Tlterhels Sablonok Kivtelkezels Az elfeldolgoz direktvi

A.1. Bevezets
A C++ szintaxisnak (formai kvetelmnyeinek) itt tallhat sszefoglalsa a megrts megknnytst clozza. Nem a nyelv pontos lersa a cl; az albb lertaknl a C++ tbb rvnyes nyelvtani szerkezetet is elfogad. Az egyszer kifejezseknek a deklarciktl val megklnbztetsre a tbbrtelmsg-feloldsi szablyokat (A.5, A.7), a formailag helyes, de rtelmetlen szerkezetek kiszrsre pedig a hozzfrsi, tbbrtelmsgi s tpusszablyokat egyttesen kell alkalmaznunk.

Forrs: http://www.doksi.hu

1068

Fggelkek s trgymutat

A C s C++ szabvny a legkisebb klnbsgeket is formai klnbsgekkel s nem megszortsokkal fejezi ki. Ez nagyfok pontossgot ad, de nem mindig javtja a kd olvashatsgt.

A.2. Kulcsszavak
A typedef (4.9.7), nvtr (8.2), osztly (10. fejezet), felsorol tpus (4.8), s template (13. fejezet) deklarcik j krnyezetfgg kulcsszavakat vezetnek be a programba.
typedef-nv: azonost nvtr-nv: eredeti-nvtr-nv nvtr-lnv eredeti-nvtr-nv: azonost nvtr-lnv: azonost osztlynv: azonost sablon-azonost felsorolsnv: azonost sablonnv: azonost

Jegyezzk meg, hogy egy osztlyt megnevez typedef-nv egyben osztlynv is. Ha nem adjuk meg kifejezetten egy azonostrl, hogy egy tpus neve, a fordt felttelezi, hogy nem tpust nevez meg (lsd C.13.5). A C++- kulcsszavai a kvetkezk:

Forrs: http://www.doksi.hu

A. Nyelvtan

1069

C++ kulcsszavak and bool compl do export goto namespace or_eq return struct try using xor and_eq break const double extern if new private short switch typedef virtual xor_eq asm case const_cast dynamic_cast false inline not protected signed template typeid void auto catch continue else float int not_eq public sizeof this typename volatile bitand char default enum for long operator register static throw union wchar_t bitor class delete explicit friend mutable or reinterpret_cast static_cast true unsigned while

A.3. Nyelvi egysgek


A szabvnyos C s C++ nyelvtanok a nyelvi egysgeket nyelvtani szerkezetekknt mutatjk be. Ez nveli a pontossgot, de a nyelvtan mrett is, s nem mindig javtja az olvashatsgot:
hex-quad: hexadecimlis-szmjegy hexadecimlis-szmjegy hexadecimlis-szmjegy hexadecimlis-szmjegy ltalnos-karakternv: \u hex-quad \U hex-quad hex-quad elfeldolgoz-szimblum: fejllomny-nv azonost pp-szm karakterliterl karakterlnc-literl elfeldolgoz-utasts-vagy-mveleti-jel minden nem reshely karakter, ami nem tartozik a fentiek kz szimblum: azonost kulcssz literl

Forrs: http://www.doksi.hu

1070

Fggelkek s trgymutat

opertor mveleti-jel fejllomny-nv: <h-char-sorozat> "q-char-sorozat" h-char-sorozat: h-char h-char-sorozat h-char h-char: a forrs-karakterkszlet brmely tagja, kivve az jsor s > karaktereket q-char-sorozat: q-char q-char-sorozat q-char q-char: a forrs-karakterkszlet brmely tagja, kivve az jsor s " karaktereket pp-szm: szmjegy . szmjegy pp-szm szmjegy pp-szm nem-szmjegy pp-szm e eljel pp-szm E eljel pp-szm . azonost: nem-szmjegy azonost nem-szmjegy azonost szmjegy nem-szmjegy: a kvetkezk egyike ltalnos-karakternv _ a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z szmjegy: a kvetkezk egyike 0 1 2 3 4 5 6 7 8 9

Forrs: http://www.doksi.hu

A. Nyelvtan

1071

elfeldolgoz-utasts-vagy-mveleti-jel: a kvetkezk egyike { } [ ] # ## ( ) <: %: ; : ? :: . .* + & | ~ ! = < > += -= &= |= <<= >>= << >> == != <= -, -> ->* ... new delete and bitor compl not or not_eq xor literl: egszliterl karakterliterl lebegpontos-literl karakterlnc-literl logikai-literl egszliterl: decimlis-literl egsz-uttagnem ktelez oktlis-literl egsz-uttagnem ktelez hexadecimlis-literl egsz-uttagnem ktelez decimlis-literl: nem-nulla-szmjegy decimlis-literl szmjegy oktlis-literl: 0 oktlis-literl oktlis-szmjegy hexadecimlis-literl: 0x hexadecimlis-szmjegy 0X hexadecimlis-szmjegy hexadecimlis-literl hexadecimlis-szmjegy nem-nulla-szmjegy: a kvetkezk egyike 1 2 3 4 5 6 7 8 9 oktlis-szmjegy: a kvetkezk egyike 0 1 2 3 4 5 6 7 hexadecimlis-szmjegy: a 0 1 2 3 4 a b c d e A B C D E kvetkezk egyike 5 6 7 8 9 f F

:> <% * / *= /= >= && and_eq or_eq

%> %:%: % ^ %= ^= || ++ bitand xor_eq

egsz-uttag: eljel-nlkli-uttag long-uttagnem ktelez long-uttag eljel-nlkli-uttagnem ktelez

Forrs: http://www.doksi.hu

1072

Fggelkek s trgymutat

eljel-nlkli-uttag: a kvetkezk egyike u U long-uttag: a kvetkezk egyike l L karakterliterl: 'c-char-sorozat' L'c-char-sorozat' c-char-sorozat: c-char c-char-sorozat c-char c-char: a forrs-karakterkszlet brmely tagja, kivve az egyszeres idzjelet, a fordtott perjelet, s az jsor karaktert escape-sorozat ltalnos-karakternv escape-sorozat: egyszer-escape-sorozat oktlis-escape-sorozat hexadecimlis-escape-sorozat egyszer-escape-sorozat: a kvetkezk egyike \' \" \? \\ \a \b \f \n \r \t \v

oktlis-escape-sorozat: \ oktlis-szmjegy \ oktlis-szmjegy oktlis-szmjegy \ oktlis-szmjegy oktlis-szmjegy oktlis-szmjegy hexadecimlis-escape-sorozat: \x hexadecimlis-szmjegy hexadecimlis-escape-sorozat hexadecimlis-szmjegy lebegpontos-literl: trt-konstans kitev-rsznem ktelez lebegpontos-uttagnem ktelez szmjegy-sorozat kitev-rsz lebegpontos-uttagnem ktelez trt-konstans: szmjegy-sorozatnem ktelez . szmjegy-sorozat szmjegy-sorozat . kitev-rsz: e eljelnem ktelez szmjegy-sorozat E eljelnem ktelez szmjegy-sorozat

Forrs: http://www.doksi.hu

A. Nyelvtan

1073

eljel: a kvetkezk egyike + szmjegy-sorozat: szmjegy szmjegy-sorozat szmjegy lebegpontos-uttag: a kvetkezk egyike f l F L karakterlnc-literl: "s-char-sorozatnem ktelez" L"s-char-sorozatnem ktelez" s-char-sorozat: s-char s-char-sorozat s-char s-char: a forrs-karakterkszlet brmely tagja, kivve a ketts idzjelet, a fordtott perjelet, s az jsort escape-sorozat ltalnos-karakternv logikai-literl: false true

A.4. Programok
A programok sszeszerkeszts ltal sszelltott fordtsi egysgek (translation unit) gyjtemnyei (9.4). A fordtsi egysgek vagy mskpp forrsfjlok, deklarcik sorozatbl llnak:
fordtsi-egysg: deklarci-sorozatnem ktelez

Forrs: http://www.doksi.hu

1074

Fggelkek s trgymutat

A.5. Kifejezsek
A kifejezseket a 6. fejezet rja le s a 6.2 pont sszegzi. A kifejezs-lista (expression-list) defincija azonos a kifejezsvel (expression). A fggvnyparamtereket elvlaszt vessznek a vessz opertortl (comma, sequencing operator, 6.2.2) val megklnbztetsre kt szably szolgl.
elsdleges-kifejezs: literl this :: azonost :: opertorfggvny-azonost :: minstett-azonost ( kifejezs ) azonost-kifejezs azonost-kifejezs: nem-minstett-azonost minstett-azonost nem-minstett-azonost: azonost opertorfggvny-azonost talaktfggvny-azonost ~ osztlynv sablon-azonost minstett-azonost: begyazott-nv-meghatrozs templatenem ktelez nem-minstett-azonost begyazott-nv-meghatrozs: osztly-vagy-nvtr-nv :: begyazott-nv-meghatrozsnem ktelez osztly-vagy-nvtr-nv :: template begyazott-nv-meghatrozs osztly-vagy-nvtr-nv: osztlynv nvtr-nv uttag-kifejezs: elsdleges-kifejezs uttag-kifejezs [ kifejezs ] uttag-kifejezs ( kifejezs-listanem ktelez ) egyszer-tpus-meghatrozs ( kifejezs-listanem ktelez ) typename ::nem ktelez begyazott-nv-meghatrozs azonost ( kifejezs-listanem ktelez )

Forrs: http://www.doksi.hu

A. Nyelvtan

1075

typename ::nem ktelez begyazott-nv-meghatrozs templatenem ktelez sablon-azonost ( kifejezs-listanem ktelez ) uttag-kifejezs . templatenem ktelez ::nem ktelez azonost-kifejezs uttag-kifejezs -> templatenem ktelez ::nem ktelez azonost-kifejezs uttag-kifejezs . l-destruktor-nv uttag-kifejezs -> l-destruktor-nv uttag-kifejezs ++ uttag-kifejezs -dynamic-cast < tpusazonost > ( kifejezs ) static-cast < tpusazonost > ( kifejezs ) reinterpret-cast < tpusazonost > ( kifejezs ) const-cast < tpusazonost > ( kifejezs ) typeid ( kifejezs ) typeid ( tpusazonost ) kifejezs-lista: rtkad-kifejezs kifejezs-lista , rtkad-kifejezs l-destruktor-nv: ::nem ktelez begyazott-nv-meghatrozsnem ktelez tpusnv :: ~ tpusnv ::nem ktelez begyazott-nv-meghatrozs template sablon-azonost :: ~ tpusnv ::nem ktelez begyazott-nv-meghatrozsnem ktelez ~ tpusnv egyoperandus-kifejezs: uttag-kifejezs ++ cast-kifejezs -- cast-kifejezs egyoperandus-opertor cast-kifejezs sizeof egyoperandus-kifejezs sizeof ( tpusazonost ) new-kifejezs delete-kifejezs egyoperandus-opertor: a kvetkezk egyike * & + - ! ~ new-kifejezs: ::nem ktelez new elhelyez-utastsnem ktelez new-tpusazonost new-kezdrtk-adnem ktelez ::nem ktelez new elhelyez-utastsnem ktelez ( tpusazonost ) new-kezdrtk-adnem ktelez elhelyez-utasts: ( kifejezs-lista ) new-tpusazonost: tpus-meghatroz-sorozat new-deklartornem ktelez

Forrs: http://www.doksi.hu

1076

Fggelkek s trgymutat

new-deklartor: ptr-opertor new-deklartornem ktelez kzvetlen-new-deklartor kzvetlen-new-deklartor: [ kifejezs ] kzvetlen-new-deklartor [ konstans-kifejezs ] new-kezdrtk-ad: ( kifejezs-listanem ktelez ) delete-kifejezs: ::nem ktelez delete cast-kifejezs ::nem ktelez delete [ ] cast-kifejezs cast-kifejezs: egyoperandus-kifejezs ( tpusazonost ) cast-kifejezs pm-kifejezs: cast-kifejezs pm-kifejezs .* cast-kifejezs pm-kifejezs ->* cast-kifejezs szorz-kifejezs: pm-kifejezs szorz-kifejezs * pm-kifejezs szorz-kifejezs / pm-kifejezs szorz-kifejezs % pm-kifejezs sszead-kifejezs: szorz-kifejezs sszead-kifejezs + szorz-kifejezs sszead-kifejezs szorz-kifejezs eltol-kifejezs: sszead-kifejezs eltol-kifejezs << sszead-kifejezs eltol-kifejezs >> sszead-kifejezs viszonyt-kifejezs: eltol-kifejezs viszonyt-kifejezs < eltol-kifejezs viszonyt-kifejezs > eltol-kifejezs viszonyt-kifejezs <= eltol-kifejezs viszonyt-kifejezs >= eltol-kifejezs

Forrs: http://www.doksi.hu

A. Nyelvtan

1077

egyenrtksg-kifejezs: viszonyt-kifejezs egyenrtksg-kifejezs == viszonyt-kifejezs egyenrtksg-kifejezs != viszonyt-kifejezs s-kifejezs: egyenrtksg-kifejezs s-kifejezs & egyenrtksg-kifejezs kizr-vagy-kifejezs: s-kifejezs kizr-vagy-kifejezs ^ s-kifejezs megenged-vagy-kifejezs: kizr-vagy-kifejezs megenged-vagy-kifejezs | kizr-vagy-kifejezs logikai-s-kifejezs: megenged-vagy-kifejezs logikai-s-kifejezs && megenged-vagy-kifejezs logikai-vagy-kifejezs: logikai-s-kifejezs logikai-vagy-kifejezs || logikai-s-kifejezs feltteles-kifejezs: logikai-vagy-kifejezs logikai-vagy-kifejezs ? kifejezs : rtkad-kifejezs rtkad-kifejezs: feltteles-kifejezs logikai-vagy-kifejezs rtkad-opertor rtkad-kifejezs throw-kifejezs rtkad-opertor: a kvetkezk egyike = *= /= %= += -= kifejezs: rtkad-kifejezs kifejezs , rtkad-kifejezs konstans-kifejezs: feltteles-kifejezs >>= <<= &= ^= |=

Forrs: http://www.doksi.hu

1078

Fggelkek s trgymutat

A fggvny stlus tpustalaktsok (cast) s a deklarcik hasonlsgbl tbbrtelmsgek addhatnak. Pldul:


int x; void f() { char(x); // x talaktsa char tpusra // vagy egy x nev char deklarcija? }

A fordt minden ilyen tbbrtelm szerkezetet deklarciknt rtelmez, vagyis a szably: ha lehet deklarciknt rtelmezni, akkor deklarci. Pldul:
T(a)->m; T(a)++; T(*e)(int(3)); T(f)[4]; T(a); T(a)=m; T(*b)(); T(x),y,z=7; // kifejezs-utasts // kifejezs-utasts // deklarci // deklarci // deklarci // deklarci // deklarci // deklarci

A tbbrtelmsg ilyen feloldsa tisztn szintaktikus. A fordt a nvrl csak annyi informcit hasznl fel, hogy az egy ismert tpus- vagy sablonnv-e. Ha nem az, akkor ezektl klnbz jelentst ttelez fel rla. A template nem-minstett-azonost szerkezet azt jelenti, hogy a nem-minstettazonost egy sablon neve, mgpedig egy olyan krnyezetben, ahonnan ezt nem lehetne levezetni (C.13.6)

Forrs: http://www.doksi.hu

A. Nyelvtan

1079

A.6. Utastsok
Lsd 6.3-at.
utasts: cmkzett-utasts kifejezs-utasts sszetett-utasts kivlaszt-utasts ciklus-utasts ugr-utasts deklarci-utasts try-blokk cmkzett-utasts: azonost : utasts case konstans-kifejezs : utasts default : utasts kifejezs-utasts: kifejezsnem ktelez ; sszetett-utasts: { utasts-sorozatnem ktelez } utasts-sorozat: utasts utasts-sorozat utasts kivlaszt-utasts: if ( felttel ) utasts if ( felttel ) utasts else utasts switch ( felttel ) utasts felttel: kifejezs tpus-meghatrozs-sorozat deklartor = rtkad-kifejezs ciklus-utasts: while ( felttel ) utasts do utasts while ( kifejezs ) ; for ( for-init-utasts felttelnem ktelez ; kifejezsnem ktelez ) utasts for-init-utasts: kifejezs-utasts egyszer-deklarci

Forrs: http://www.doksi.hu

1080

Fggelkek s trgymutat

ugr-utasts: break ; continue ; return kifejezsnem ktelez ; goto azonost ; deklarci-utasts: blokk-deklarci

A.7. Deklarcik
A deklarcik szerkezett a 4. fejezet rja le, a felsorol tpusokat (enumeration) a 4.8, a mutatkat (pointer) s tmbket (array) az 5. fejezet, a fggvnyeket (function) a 7. fejezet, a nvtereket (namespace) a 8.2, az sszeszerkesztsi direktvkat (linkage directive) a 9.2.4, a trolsi mdokat (storage class) pedig a 10.4.
deklarci-sorozat: deklarci deklarci-sorozat deklarci deklarci: blokk-deklarci fggvnydefinci sablondeklarci explicit-pldnyosts explicit-specializci sszeszerkesztsi-md nvtr-definci blokk-deklarci: egyszer-deklarci asm-definci nvtr-lnv-definci using-deklarci using-utasts egyszer-deklarci: deklarci-meghatrozs-sorozatnem ktelez kezdrtk-deklartor-listanem ktelez ; deklarci-meghatrozs: trolsi-md-meghatrozs tpus-meghatrozs

Forrs: http://www.doksi.hu

A. Nyelvtan

1081

fggvny-meghatrozs friend typedef deklarci-meghatrozs-sorozat: deklarci-meghatrozs-sorozatnem ktelez deklarci-meghatrozs trolsi-md-meghatrozs: auto register static extern mutable fggvny-meghatrozs: inline virtual explicit typedef-nv: azonost tpus-meghatrozs: egyszer-tpus-meghatrozs osztly-meghatrozs felsorols-meghatrozs sszetett-tpus-meghatrozs cv-minst egyszer-tpus-meghatrozs: ::nem ktelez begyazott-nv-meghatrozsnem ktelez tpusnv ::nem ktelez begyazott-nv-meghatrozs templatenem ktelez sablon-azonost char wchar_t bool short int long signed unsigned float double void tpusnv: osztlynv felsorolsnv typedef-nv

Forrs: http://www.doksi.hu

1082

Fggelkek s trgymutat

sszetett-tpus-meghatrozs: osztlykulcs ::nem ktelez begyazott-nv-meghatrozsnem ktelez azonost enum ::nem ktelez begyazott-nv-meghatrozsnem ktelez azonost typename ::nem ktelez begyazott-nv-meghatrozs azonost typename ::nem ktelez begyazott-nv-meghatrozs templatenem ktelez sablon-azonost felsorolsnv: azonost felsorols-meghatrozs: enum azonostnem ktelez { felsorols-listanem ktelez } felsorols-lista: felsorol-definci felsorols-lista , felsorol-definci felsorol-definci: felsorol felsorol = konstans-kifejezs felsorol: azonost nvtr-nv: eredeti-nvtr-nv nvtr-lnv eredeti-nvtr-nv: azonost nvtr-definci: nevestett-nvtr-definci nevestetlen-nvtr-definci nevestett-nvtr-definci: eredeti-nvtr-definci bvtett-nvtr-definci eredeti-nvtr-definci: namespace azonost { nvtr-trzs } bvtett-nvtr-definci: namespace eredeti-nvtr-nv { nvtr-trzs } nevestetlen-nvtr-definci: namespace { nvtr-trzs }

Forrs: http://www.doksi.hu

A. Nyelvtan

1083

nvtr-trzs: deklarci-sorozatnem ktelez nvtr-lnv: azonost nvtr-lnv-definci: namespace azonost = minstett-nvtr-meghatrozs ; minstett-nvtr-meghatrozs: ::nem ktelez begyazott-nv-meghatrozsnem ktelez nvtr-nv using-deklarci: using typenamenem ktelez ::nem ktelez begyazott-nv-meghatrozs nemminstett-azonost ; using :: nem-minstett-azonost ; using-utasts: using namespace ::nem ktelez begyazott-nv-meghatrozsnem ktelez nvtr-nv ; asm-definci: asm ( karakterlnc-literl ) ; sszeszerkesztsi-md: extern karakterlnc-literl { deklarci-sorozatnem ktelez } extern karakterlnc-literl deklarci

A nyelvtan megengedi a deklarcik tetszleges egymsba gyazst, de bizonyos megszortsok rvnyesek. Pldul nem szabad fggvnyeket egymsba gyazni, azaz egy fggvnyen bell egy msik fggvnyt kifejteni. A deklarcikat kezd meghatrozsok (minstk, specifier) listja nem lehet res (azaz nincs implicit int, B.2) s a meghatrozsok leghosszabb lehetsges sorozatbl ll. Pldul:
typedef int I; void f(unsigned I) { /* ... */ }

Itt f() paramtere egy unsigned int. Az asm() szerkezet az assembly kd beszrsra szolgl. Jelentst az adott nyelvi vltozat hatrozza meg, de clja az, hogy a megadott helyen az adott szvegnek megfelel assembly kd kerljn be a fordt ltal ltrehozott kdba.

Forrs: http://www.doksi.hu

1084

Fggelkek s trgymutat

Ha egy vltozt register-knt vezetnk be, azzal azt jelezzk a fordtprogram szmra, hogy a gyakori hozzfrsekre kell optimalizlnia a kdot. Az jabb fordtprogramok legtbbje szmra ezt felesleges megadni.

A.7.1. Deklartorok
Lsd a 4.9.1 pontot, az 5. fejezetet (mutatk s tmbk), a 7.7 pontot (fggvnymutatk) s a 15.5 pontot (tagokra hivatkoz mutatk).
kezdrtk-deklartor-lista: kezdrtk-deklartor kezdrtk-deklartor-lista , kezdrtk-deklartor kezdrtk-deklartor: deklartor kezdrtk-adnem ktelez deklartor: kzvetlen-deklartor ptr-opertor deklartor kzvetlen-deklartor: deklartor-azonost kzvetlen-deklartor ( paramter-deklarci-zradk ) cv-minst-sorozatnem ktelez kivtel-meghatrozsnem ktelez kzvetlen-deklartor [ konstans-kifejezsnem ktelez ] ( deklartor ) ptr-opertor: * cv-minst-sorozatnem ktelez & ::nem ktelez begyazott-nv-meghatrozs * cv-minst-sorozatnem ktelez cv-minst-sorozat: cv-minst cv-minst-sorozatnem ktelez cv-minst: const volatile deklartor-azonost: ::nem ktelez azonost-kifejezs ::nem ktelez begyazott-nv-meghatrozsnem ktelez tpusnv

Forrs: http://www.doksi.hu

A. Nyelvtan

1085

tpusazonost: tpus-meghatrozs-sorozat elvont-deklartornem ktelez tpus-meghatrozs-sorozat: tpus-meghatrozs tpus-meghatrozs-sorozatnem ktelez elvont-deklartor: ptr-opertor elvont-deklartornem ktelez kzvetlen-elvont-deklartor kzvetlen-elvont-deklartor: kzvetlen-elvont-deklartornem ktelez ( paramter-deklarci-zradk ) cv-minstsorozatnem ktelez kivtel-meghatrozsnem ktelez kzvetlen-elvont-deklartornem ktelez [ konstans-kifejezsnem ktelez ] ( elvont-deklartor ) paramter-deklarci-zradk: paramter-deklarci-listanem ktelez ... paramter-deklarci-lista , ...

nem ktelez

paramter-deklarci-lista: paramter-deklarci paramter-deklarci-lista , paramter-deklarci paramter-deklarci: deklarci-meghatrozs-sorozat deklartor deklarci-meghatrozs-sorozat deklartor = rtkad-kifejezs deklarci-meghatrozs-sorozat elvont-deklartornem ktelez deklarci-meghatrozs-sorozat elvont-deklartornem ktelez = rtkad-kifejezs fggvnydefinci: deklarci-meghatrozs-sorozatnem ktelez deklartor ctor-kezdrtk-adnem ktelez fggvnytrzs deklarci-meghatrozs-sorozatnem ktelez deklartor fggvny-try-blokk fggvnytrzs: sszetett-utasts kezdrtk-ad: = kezdrtk-ad-zradk ( kifejezs-lista ) kezdrtk-ad-zradk: rtkad-kifejezs { kezdrtk-lista , {}

nem ktelez

Forrs: http://www.doksi.hu

1086

Fggelkek s trgymutat

kezdrtk-lista: kezdrtk-ad-zradk kezdrtk-lista , kezdrtk-ad-zradk

A volatile meghatrozs/minsts azt jelzi a fordtprogram szmra, hogy az objektum a nyelv ltal nem meghatrozott mdon vltoztatja az rtkt, gy az agresszv optimalizls kerlend. Egy valsidej rt pldul gy deklarlhatunk:
extern const volatile clock;

A clock kt egymst kvet leolvassa klnbz eredmnyeket adhat.

A.8. Osztlyok
Lsd a 10. fejezetet.
osztlynv: azonost sablon-azonost osztly-meghatrozs: osztlyfej { tag-meghatrozsnem ktelez } osztlyfej: osztlykulcs azonostnem ktelez alap-zradknem ktelez osztlykulcs begyazott-nv-meghatrozs azonost alap-zradknem ktelez osztlykulcs begyazott-nv-meghatrozs template sablon-azonost alap-zradknem ktelez osztlykulcs: class struct union tag-meghatrozs: tag-deklarci tag-meghatrozsnem ktelez hozzfrs-meghatrozs : tag-meghatrozsnem ktelez

Forrs: http://www.doksi.hu

A. Nyelvtan

1087

tag-deklarci: deklarci-meghatrozs-sorozatnem ktelez tag-deklartor-listanem ktelez ; fggvnydefinci ;nem ktelez ::nem ktelez begyazott-nv-meghatrozs templatenem ktelez nem-minstett-azonost ; using-deklarci sablondeklarci tag-deklartor-lista: tag-deklartor tag-deklartor-lista , tag-deklartor tag-deklartor: deklartor res-meghatrozsnem ktelez deklartor konstans-kezdrtk-adnem ktelez azonostnem ktelez : konstans-kifejezs res-meghatrozs: = 0 konstans-kezdrtk-ad: = konstans-kifejezs

A C-vel val sszeegyeztethetsget megrzend egy azonos nev osztly s egy nem-osztly ugyanabban a hatkrben is deklarlhat (5.7). Pldul:
struct stat { /* ... */ }; int stat(char* nv, struct stat* buf);

Ebben az esetben a sima nv (stat) a nem-osztly neve. Az osztlyra egy osztlykulcs eltaggal (class, struct vagy union) kell hivatkozni. A konstans kifejezseket a C.5 pont rja le.

A.8.1. Szrmaztatott osztlyok


Lsd a 12. s 15. fejezetet.
alap-zradk: : alap-meghatrozs-lista alap-meghatrozs-lista: alap-meghatrozs alap-meghatrozs-lista , alap-meghatrozs

Forrs: http://www.doksi.hu

1088

Fggelkek s trgymutat

alap-meghatrozs: ::nem ktelez begyazott-nv-meghatrozsnem ktelez osztlynv virtual hozzfrs-meghatrozsnem ktelez ::nem ktelez begyazott-nvmeghatrozsnem ktelez osztlynv hozzfrs-meghatrozs virtualnem ktelez ::nem ktelez begyazott-nvmeghatrozsnem ktelez osztlynv hozzfrs-meghatrozs: private protected public

A.8.2. Klnleges tagfggvnyek


Lsd a 11.4 (talakt opertorok), 10.4.6 (osztlytagok kezdeti rtkadsa) s 12.2.2 (alaposztlyok kezdeti rtkadsa) pontokat.
talaktfggvny-azonost: operator talakts-tpusazonost talakts-tpusazonost: tpus-meghatrozs-sorozat talakts-deklartornem ktelez talakts-deklartor: ptr-opertor talakts-deklartornem ktelez ctor-kezdrtk-ad: : mem-kezdrtk-lista mem-kezdrtk-lista: mem-kezdrtk-ad mem-kezdrtk-ad , mem-kezdrtk-lista mem-kezdrtk-ad: mem-kezdrtk-ad-azonost ( kifejezs-listanem ktelez ) mem-kezdrtk-ad-azonost: ::nem ktelez begyazott-nv-meghatrozsnem ktelez osztlynv azonost

Forrs: http://www.doksi.hu

A. Nyelvtan

1089

A.8.3. Tlterhels
Lsd a 11. fejezetet.
opertorfggvny-azonost: operator opertor opertor: a kvetkezk egyike new delete new[] + * / % ^ += -= *= /= %= ^= != <= >= && || ++ delete[] | ~ |= << , ->*

& &= --

! >> ->

= >>= ()

< <<= []

> ==

A.9. Sablonok
A sablonokkal a 13. fejezet s a C.13. pont foglalkozik rszletesen.
sablondeklarci: exportnem ktelez template < sablonparamter-lista > deklarci sablonparamter-lista: sablonparamter sablonparamter-lista , sablonparamter sablonparamter: tpus-paramter paramter-deklarci tpus-paramter: class azonostnem ktelez class azonostnem ktelez = tpusazonost typename azonostnem ktelez typename azonostnem ktelez = tpusazonost template < sablonparamter-lista > class azonostnem ktelez template < sablonparamter-lista > class azonostnem ktelez = sablonnv sablon-azonost: sablonnv < sablonargumentum-listanem ktelez > sablonnv: azonost

Forrs: http://www.doksi.hu

1090

Fggelkek s trgymutat

sablonargumentum-lista: sablonargumentum sablonargumentum-lista , sablonargumentum sablonargumentum: rtkad-kifejezs tpusazonost sablonnv explicit-pldnyosts: template deklarci explicit-specializci: template < > deklarci

Az explicit sablonargumentum-meghatrozs egy rdekes tbbrtelmsgre ad lehetsget. Vegyk a kvetkez pldt:


void h() { f<1>(0); }

// tbbrtelm: ((f)<1) > (0) vagy (f<1>)(0) ? // feloldsa: f<1> meghvsa a 0 paramterrel

A felolds egyszer s hatkony: ha f egy sablon neve, akkor f< egy minstett sablonnv kezdete s az azt kvet nyelvi egysgek eszerint rtelmezendek; ha nem ez a helyzet, a < jel a kisebb mint mveletet jelenti. Ugyangy az els pr nlkli > jel lezrja a sablonparamterek listjt. Ha a nagyobb mint jelre van szksgnk, zrjelezst kell alkalmaznunk:
f< a>b >(0); f< (a>b) >(0); // szintaktikus hiba // rendben

Hasonl tbbrtelmsg lphet fel, ha a zr > jelek tl kzel kerlnek:


list<vector<int>> lv1; // szintaktikus hiba: nem vrt >> (jobbra lptets) list< vector<int> > lv2; // helyes: vektorok listja

Figyeljnk a szkzre a kt > jel kztt (a >> a jobbra lptet opertor!), mert nagyon knny elnzni.

Forrs: http://www.doksi.hu

A. Nyelvtan

1091

A.10. Kivtelkezels
Lsd a 8.3 pontot s a 14. fejezetet.
try-blokk: try sszetett-utasts kezel-sorozat fggvny-try-blokk: try ctor-kezdrtk-adnem ktelez fggvnytrzs kezel-sorozat kezel-sorozat: kezel kezel-sorozatnem ktelez kezel: catch ( kivtel-deklarci ) sszetett-utasts kivtel-deklarci: tpus-meghatrozs-sorozat deklartor tpus-meghatrozs-sorozat elvont-deklartor tpus-meghatrozs-sorozat ... throw-kifejezs: throw rtkad-kifejezsnem ktelez kivtel-meghatrozs: throw ( tpusazonost-listanem ktelez ) tpusazonost-lista: tpusazonost tpusazonost-lista , tpusazonost

A.11. Az elfeldolgoz direktvi


Az elfeldolgoz (preprocessor, pp) egy viszonylag egyszer makr-feldolgoz rendszer, amely elsdlegesen nyelvi egysgeken s nem egyes karaktereken dolgozik. A makrk meghatrozsnak s hasznlatnak (7.8) kpessgn kvl az elfeldolgoz a szvegfjloknak s szabvnyos fejllomnyoknak (9.2.1) a forrsba ptsre is lehetsget ad s makrkon alapul feltteles fordtst is tud vgezni (9.3.3). Pldul:

Forrs: http://www.doksi.hu

1092

Fggelkek s trgymutat

#if OPT==4 #include "fejllomny4.h" #elif 0<OPT #include "fejllomny.h" #else #include<cstdlib> #endif

Az sszes elfeldolgozi utasts (direktva) a # jellel kezddik, amelynek az els nem reshely karakternek kell lennie a sorban.
elfeldolgoz-fjl: csoportnem ktelez csoport: csoport-rsz csoport csoport-rsz csoport-rsz: pp-szimblumoknem ktelez jsor if-rsz vezrlsor if-rsz: if-csoport elif-csoportoknem ktelez else-csoportnem ktelez endif-sor if-csoport: # if konstans-kifejezs jsor csoportnem ktelez # ifdef azonost jsor csoportnem ktelez # ifndef azonost jsor csoportnem ktelez elif-csoportok: elif-csoport elif-csoportok elif-csoport elif-csoport: # elif konstans-kifejezs jsor csoportnem ktelez else-csoport: # else jsor csoportnem ktelez endif-sor: # endif jsor

Forrs: http://www.doksi.hu

A. Nyelvtan

1093

vezrlsor: # include pp-szimblumok jsor # define azonost helyettest-lista jsor # define azonost balzrjel azonost-listanem ktelez ) helyettest-lista jsor # # # # # undef azonost jsor line pp-szimblumok jsor error pp-szimblumoknem ktelez jsor pragma pp-szimblumoknem ktelez jsor jsor

balzrjel: a bal oldali zrjel karakter megelz reshely nlkl helyettest-lista: pp-szimblumoknem ktelez pp-szimblumok: elfeldolgoz-szimblum pp-szimblumok elfeldolgoz-szimblum jsor: jsor karakter azonost-lista: azonost azonost-lista , azonost

Forrs: http://www.doksi.hu

B
Kompatibilits
Te msz a te utadon, a te szoksaid szerint, s n is kvetem a sajt elveimet. (C. Napier) C/C++ kompatibilits szrevtlen klnbsgek a C s a C++ kztt C program, ami nem C++ program Elavult szolgltatsok C++ program, ami nem C program Rgebbi C++-vltozatok hasznlata Fejllomnyok A standard knyvtr Nvterek Helyfoglalsi hibk Sablonok A for utasts kezdrtk-ad rsze Tancsok Gyakorlatok

B.1. Bevezets
Ebben a fggelkben azokat a klnbsgeket vizsgljuk meg, amelyek a C s a C++, illetve a szabvnyos C++ s a rgebbi C++-vltozatok kztt llnak fenn. Clunk az, hogy egyrszt lerjuk azokat a klnbsgeket, amelyek a programozknak problmt okozhatnak, msrszt mdszereket mutassunk ezen problmk kezelsre. A legtbb kompatibilitsi problmval akkor kerlnk szembe, amikor egy C programot a C++-bl akarunk hasznlni, vagy egy nem szabvnyos C++ rendszerben ltrehozott programot egy msik krnyezetbe akarunk tvinni, esetleg j lehetsgeket akarunk egy rgebbi C++-vltozatban hasznl-

Forrs: http://www.doksi.hu

1096

Fggelkek s trgymutat

ni. A cl nem az, hogy rszletesen bemutassuk az sszes kompatibilitsi problmt, ami valaha is elfordulhat, hanem az, hogy a leggyakoribb problmkra szabvnyos megoldst adjunk. Amikor kompatibilitsi problmkrl beszlnk, a legfontosabb krds az, hogy programunk a klnbz nyelvi vltozatok milyen szles krben kpes mkdni. A C++ nyelv megismershez rdemes a legteljesebb s legknyelmesebb rendszert hasznlnunk, rendszerfejlesztskor azonban ennl konzervatvabb stratgit kell kvetnnk, hiszen olyan programot szeretnnk, amely a lehet legtbb krnyezetben mkdkpes. Rgebben ez nagyon j kifogs volt arra, hogy a C++ tl fejlettnek minstett lehetsgeit elkerljk. Az egyes vltozatok azonban kzeledtek egymshoz, gy a klnbz platformok kztti hordozhatsg kvetelmnye ritkn ignyel olyan komoly erfesztseket a programoztl, mint nhny vvel ezeltt.

B.2. C/C++ kompatibilits


Kisebb kivtelektl eltekintve a C++ a C nyelv tovbbfejlesztsnek tekinthet. A legtbb klnbsg a C++ hangslyozottabb tpusellenrzsbl kvetkezik. A jl megrt C programok ltalban C++ programok is. A C s C++ kztti sszes klnbsget a fordtnak is jeleznie kell.

B.2.1. szrevtlen klnbsgek


Nhny kivteltl eltekintve azok a programok, melyek C s C++ nyelven is rtelmezhetk, ugyanazt jelentik mind a kt nyelvben. Szerencsre azok a klnbsgek, melyek mgis elfordulnak, ltalban csak rnyalatnyiak: A C-ben a karakterkonstansok s a felsorol tpusok mrete sizeof(int). A C++-ban sizeof(a) egyenrtk sizeof(char)-ral, a felsorol tpusok megvalstshoz pedig az egyes C++-vltozatok olyan mretet hasznlhatnak, ami az adott krnyezetben a legclszerbb (4.8). A C++ lehetv teszi a // jellel bevezetett megjegyzsek hasznlatt; ez a C-ben nem ll rendelkezsnkre (br nagyon sok C-vltozat tartalmazza bvtsknt). Ezt az eltrst olyan programok ksztshez hasznlhatjuk fel, melyek a kt nyelvben klnbzkppen viselkednek.

Forrs: http://www.doksi.hu

B. Kompatibilits

1097

Pldul:
int f(int a, int b) { return a //* elg valszntlen */ b ; /* pontosvessz a szintaktikus hiba elkerlsre */ }

Az ISO C ma mr a C++-hoz hasonlan megengedi a // hasznlatt. Egy bels hatkrben bevezetett adatszerkezet-nv elrejtheti egy kls hatkrben deklarlt objektum, fggvny, felsorols, vagy tpus nevt:
int x[99]; void f() { }

struct x { int a; }; sizeof(x); /* a tmb mrete C-ben, a struct mrete C++-ban */

B.2.2. C program, ami nem C++ program


A leggyakrabban problmt okoz klnbsgek ltalban nem tl lesek. A legtbbet ezek kzl a fordtk is knnyen szreveszik. Ebben a pontban olyan C kdokat mutatunk be, amelyek nem C++ kdok. Ezek legtbbjt mr az jabb C-vltozatok is rossz stlusnak, st elavultnak minstik. A C-ben a legtbb fggvnyt deklarlsuk eltt is meghvhatjuk:
main() /* rossz stlus C-ben, hibs C++-ban */ { double sq2 = sqrt(2); /* deklarlatlan fggvny meghvsa */ printf("the square root of 2 is %g\n",sq2); /* deklarlatlan fggvny meghvsa */ }

A fggvny-deklarcik (fggvny-prototpusok) teljes s kvetkezetes hasznlata minden C-megvalstsban ajnlott. Ha ezt a tancsot megfogadjuk (s ezt a fordtk ltalban valamilyen kapcsolval biztostani is tudjk), C programjaink illeszkedni fognak a C++ szablyaihoz. Ha deklarlatlan fggvnyeket hvunk meg, nagyon pontosan ismernnk kell C rendszernk fggvnyhvssal kapcsolatos szablyait, hogy eldnthessk, okoztunk-e hibt vagy hordozhatsgi problmt. Az elbbi main() program pldul legalbb kt hibt tartalmaz C programknt is.

Forrs: http://www.doksi.hu

1098

Fggelkek s trgymutat

A C-ben azok a fggvnyek, melyeket paramtertpus megadsa nlkl vezetnk be, tetszleges szm s tpus paramterrel meghvhatk. Az ilyen fggvnyhasznlat a Standard C-ben is elavult, ennek ellenre sok helyen tallkozhatunk vele:
void f(); void g() { f(2); } /* a paramtertpusokat elhagyjuk */

/* rossz stlus C-ben, hibs C++-ban */

A C-ben megengedett az a fggvny-meghatrozsi forma, melyben a paramterek tpust a paramterek listjnak megadsa utn rgztjk:
void f(a,p,c) char *p; char c; { /* ... */ } /* helyes C-ben, hibs C++-ban */

Ezeket a defincikat t kell rnunk:


void f(int a, char* p, char c) { /* ... */ }

A C-ben s a C++ szabvnyosts eltti vltozataiban az int alaprtelmezett tpus volt. Pldul:
const a = 7; /* C-ben "int" tpust felttelez; C++-ban hibs */

Az ISO C, a C++-hoz hasonlan, megtiltotta az implicit int hasznlatt. A C megengedi, hogy a visszatrsi tpusok s a paramtertpusok deklarciiban structokat adjunk meg:
struct S { int x,y; } f(); void g(struct S { int x,y; } y); /* helyes C-ben, hibs C++-ban */ /* helyes C-ben, hibs C++-ban */

A C++ tpus-meghatrozsokra vonatkoz szablyai az ilyen deklarcikat feleslegess teszik s nem is engedik meg. A C-ben a felsorol tpus vltozknak rtkl adhatunk egszeket is:
enum Direction { up, down }; Direction d = 1; /* hiba: int rtkads Direction-nek; C-ben helyes */

Forrs: http://www.doksi.hu

B. Kompatibilits

1099

A C++ sokkal tbb kulcsszt ismer, mint a C. Ha ezek valamelyikt azonostknt hasznljuk egy C programban, knytelenek lesznk azt lecserlni, hogy C++-ban is rtelmezhet programot kapjunk.

C++ kulcsszavak, melyek a C-ben nem kulcsszavak and catch explicit namespace or_eq template typename and_eq class export new private this using asm compl false not protected throw virtual bitand const_cast friend not_eq public true wchar_t bitor delete inline operator reinterpret_cast try xor bool dynamic_cast mutable or static_cast typeid xor_eq

A C-ben nhny C++ kulcssz makrknt szerepel a szabvnyos fejllomnyokban:

C++ kulcsszavak, melyek C makrk and not_eq and_eq or bitand or_eq bitor wchar_t compl xor not xor_eq

Ez azt is jelenti, hogy a C-ben ezeket tesztelhetjk az #ifdef segtsgvel, meghatrozsukat fellbrlhatjuk stb. A C-ben a globlis adatobjektumokat tbbszr is deklarlhatjuk egy fordtsi egysgen bell, anlkl, hogy az extern kulcsszt hasznlnnk. Amg a deklarcik kzl csak egy ad kezdrtket, a fordt az objektumot egyszer meghatrozottnak (egyszer definiltnak) tekinti.

Forrs: http://www.doksi.hu

1100

Fggelkek s trgymutat

Pldul:
int i; int i; /* egyetlen 'i' egszet hatroz meg vagy vezet be; C++-ban hibs */

A C++-ban minden elemet csak egyszer hatrozhatunk meg (9.2.3). A C++-ban egy osztlynak nem lehet ugyanaz a neve, mint egy typedef elemnek, amely ugyanabban a hatkrben valamilyen ms tpusra hivatkozik (5.7). A C-ben a void* hasznlhat brmilyen mutat tpus vltoz egyszer vagy kezdeti rtkadsnak jobb oldaln. A C++-ban ezt nem tehetjk meg (5.6.):
void f(int n) { int* p = malloc(n*sizeof(int)); opertort */ }

/* C++-ban hibs; hasznljuk inkbb a 'new'

A C megengedi, hogy ugrsokkal kikerljnk egy kezdeti rtkadst, a C++-ban ez sem lehetsges. A C-ben a globlis konstansokat a fordt automatikusan extern elemekknt kezeli, mg a C++-ban nem. Ktelez vagy kezdrtket adni, vagy az extern kulcsszt hasznlni (5.4.). A C-ben a begyazott szerkezetek nevei ugyanabba a hatkrbe kerlnek, mint a felettk ll szerkezet:
struct S { struct T { /* ... */ }; // ... }; struct T x; /* helyes C-ben, jelentse 'S::T x;'; C++-ban hibs */

A C-ben egy tmbnek olyan elemmel is adhatunk kezdrtket, amelynek tbb eleme van, mint amennyire a deklarlt tmbnek szksge van:
char v[5] = "Oscar"; /* helyes C-ben, a lezr 0-t nem hasznlja; C++-ban hibs */

Forrs: http://www.doksi.hu

B. Kompatibilits

1101

B.2.3. Elavult szolgltatsok


Ha a szabvnyost bizottsg egy szolgltatst elavultnak nyilvnt, akkor ezzel azt fejezi ki, hogy a szolgltatst el kell kerlni. A bizottsgnak azonban nincs joga egy korbban gyakran hasznlhat lehetsget teljesen megszntetni, akkor sem, ha az esetleg felesleges vagy kifejezetten veszlyes. Teht az elavultsg kimondsa csak egy (nyomatkos) ajnls a programozknak arra, hogy ne hasznljk a lehetsget. A static kulcssz, melynek alapjelentse: statikusan lefoglalt, ltalban azt jelzi, hogy egy fggvny vagy egy objektum helyinek (loklisnak) szmt a fordtsi egysgre nzve:
// fjl1: static int glob; // fjl2: static int glob;

Ennek a programnak valjban kt glob nev, egsz tpus vltozja lesz. Mindkettt kizrlag azok a fggvnyek fogjk hasznlni, amelyekkel megegyez fordtsi egysgben szerepel. A static kulcssz hasznlata a loklis a fordtsi egysgre nzve rtelemben elavult a C++ban. Helyettk a nvtelen nvterek hasznlata javasolt (8.2.5.1). A karakterlnc-literlok automatikus talaktsa (nem konstans) char* tpusra szintn elavult. Hasznljunk inkbb nvvel rendelkez karaktertmbket, gy elkerlhetjk, hogy karakterlnc-literlokat kelljen rtkl adnunk char* vltozknak (5.2.2). A C stlus tpustalaktst az j talaktsoknak szintn elavultt kellett volna tennik; sajnos mg sokan hasznljk, pedig a felhasznlnak programjaiban rdemes komolyan megfogadnia a C stlus talaktsok tilalmt. Ha explicit tpustalaktsra van szksgnk, a static_cast, a reinterpret_cast, illetve a const_cast kulcsszval vagy ezek egyttes hasznlatval ugyanazt az eredmnyt rhetjk el, mint a C stlus talaktssal. Az j talaktsokat azrt is rdemes hasznlnunk, mert ezek szlesebb krben hasznlhatk s pontosabban megfogalmazottak (6.2.7).

Forrs: http://www.doksi.hu

1102

Fggelkek s trgymutat

B.2.4. C++ program, ami nem C program


Ebben a pontban azokat a szolgltatsokat soroljuk fel, melyek a C++-ban megtallhatk, de a C-ben nem. A lehetsgeket szerepk szerint soroljuk fel. Termszetesen szmtalan osztlyozs lehetsges, mert a legtbb szolgltats tbb clt is szolgl, teht az itt bemutatott nem az egyetlen j csoportosts: Lehetsgek, melyek elssorban knyelmes jellsrendszert biztostanak: 1. A // megjegyzsek (2.3); a C-ben is szerepelni fognak. 2. Korltozott karakterkszletek hasznlatnak lehetsge (C.3.1). 3. Bvtett karakterkszletek hasznlatnak lehetsge (C.3.3), a C-ben is megjelenik. 4. A static trban lev objektumok nem-konstans kezdrtket is kaphatnak (9.4.1). 5. A const a konstans kifejezsekben (5.4, C.5). 6. A deklarcik utastsokknt szerepelnek. 7. Deklarcik szerepelhetnek a for utasts kezdrtk-ad elemben s az if felttelben is (6.3.3, 6.3.2.1). 8. Az adatszerkezetek nevei eltt nem kell szerepelnie a struct kulcssznak. Lehetsgek, melyek elssorban a tpusrendszer erstst szolgljk: 1. A fggvnyparamterek tpusellenrzse (7.1); ksbb a C-ben is megjelent (B.2.2). 2. Tpusbiztos sszeszerkeszts (9.2, 9.2.3). 3. A szabad tr kezelse a new s a delete opertor segtsgvel (6.2.6, 10.4.5, 15.6). 4. A const (5.4, 5.4.1); ksbb a C-be is bekerlt. 5. A logikai bool adattpus (4.2). 6. j tpustalaktsi forma (6.2.7). Felhasznli tpusokat segt lehetsgek: 1. Osztlyok (10. fejezet). 2. Tagfggvnyek (10.2.1) s tagosztlyok (11.12). 3. Konstruktorok s destruktorok (10.2.3, 10.4.1). 4. Szrmaztatott osztlyok (12. fejezet, 15. fejezet). 5. Virtulis fggvnyek s elvont osztlyok (12.2.6, 12.3). 6. A public/protected/private hozzfrs-szablyozs (10.2.2, 15.3, C.11). 7. A bartok (friend) lehetsgei (11.5). 8. Mutatk tagokra (15.5, C.12). 9. static tagok (10.2.4).

Forrs: http://www.doksi.hu

B. Kompatibilits

1103

10. mutable tagok (10.2.7.2). 11. Opertor-tlterhels (11. fejezet). 12. Hivatkozsok (5.5). Lehetsgek, melyek elssorban a program rendszerezsre szolglnak (az osztlyokon tl): 1. Sablonok (13. fejezet, C.13). 2. Helyben kifejtett (inline) fggvnyek (7.1.1). 3. Alaprtelmezett paramterek (7.5). 4. Fggvnynv-tlterhels (7.4). 5. Nvterek (8.2). 6. Explicit hatkr-meghatrozs (a :: opertor, 4.9.4). 7. Kivtelkezels (8.3, 14. fejezet). 8. Futsi idej tpus-meghatrozs (15.4). A C++-ban bevezetett j kulcsszavak (B.2.2) a legtbb olyan szolgltatst bemutatjk, melyek kifejezetten a C++-ban jelentek meg. Van azonban nhny nyelvi elem pldul a fggvny-tlterhels vagy a consts alkalmazsa konstans kifejezsekben , melyeket nem j kulcssz segtsgvel valst meg a nyelv. Az itt felsorolt nyelvi lehetsgek mellett a C++ knyvtr is (16.1.2) nagy rszben csak a C++-ra jellemz. A __cplusplus makr segtsgvel mindig megllapthatjuk, hogy programunkat ppen C vagy C++ fordtval dolgozzuk-e fel (9.2.4).

B.3. Rgebbi C++-vltozatok hasznlata


A C++ nyelvet 1983 ta folyamatosan hasznljk (1.4). Azta szmtalan vltozat s nll fejlesztkrnyezet kszlt belle. A szabvnyosts alapvet clja, hogy a programozk s felhasznlk rszre egy egysges C++ lljon rendelkezsre. Amg azonban a szabvny teljes krben el nem terjed a C++-rendszerek kszti krben, mindig figyelembe kell vennnk azt a tnyt, hogy nem minden megvalsts nyjtja az sszes szolgltatst, ami ebben a knyvben szerepel. Sajnos nem ritka, hogy a programozk els komoly benyomsaikat a C++-rl egy ngy-t ves vltozat alapjn szerzik meg. Ennek oka az, hogy ezek szles krben s olcsn elrhetk. Ha azonban a legkisebb lehetsge is van, egy magra valamit is ad szakember

Forrs: http://www.doksi.hu

1104

Fggelkek s trgymutat

ilyen antik rendszernek a kzelbe sem megy. Egy kezdnek a rgebbi vltozatok szmtalan rejtett problmt okozhatnak. A nyelvi lehetsgek s a knyvtrban megvalstott szolgltatsok hinya olyan problmk kezelst teszi szksgess, melyek az jabb vltozatokban mr nem jelentkeznek. A kevs lehetsget biztost rgebbi vltozatok a kezd programoz programozsi stlusnak is sokat rtanak, radsul helytelen kpnk alakul ki arrl, mi is valjban a C++. Vlemnyem szerint a C++ els megismersekor nem az a legmegfelelbb rszhalmaz, amely az alacsonyszint szolgltatsokat tartalmazza (teht semmikppen sem a C s a C++ kzs rsze). Azt ajnlom, elszr a standard knyvtrat s a sablonokat ismerjk meg, mert ezekkel egyszeren megoldhatunk komolyabb feladatokat is s j zeltt kapunk a valdi C++ szolgltatsaibl. A C++ els kereskedelmi kiadsa 1985-ben jelent meg; e knyv els kiadsa alapjn ksztettk. Akkor a C++ mg nem biztostott tbbszrs rkldst, sablonokat, futsi idej tpusinformcikat, kivteleket s nvtereket sem. Mai szemmel semmi rtelmt nem ltom annak, hogy olyan rendszerrel kezdjnk el dolgozni, amely ezen szolgltatsok legalbb egy rszt nem biztostja. A tbbszrs rklds, a sablonok s a kivtelek 1989-ben kerltek be a C++-ba. A sablonok s kivtelek els megvalstsa mg nagyon kiforratlan s szegnyes volt. Ha ezek hasznlatakor problmkba tkznk, srgsen szerezznk be egy jabb nyelvi vltozatot. ltalban igaz, hogy olyan vltozatot rdemes hasznlnunk, amely minden lehetsges helyen igazodik a szabvnyhoz, hogy elkerlhessk a megvalstsbl ered klnbsgeket, illetve az adott nyelvi vltozatnak a szabvny ltal nem meghatrozott tulajdonsgait. Tervezzk programjainkat gy, mintha a teljes nyelv a rendelkezsnkre llna, majd valstsuk meg nllan a hinyz rszletek szolgltatsait. gy jobban rendszerezett s knnyebben tovbbfejleszthet programot kapunk, mint ha a C++ mindenhol megtallhat, kzs magjhoz terveznnk a programot. Arra is mindig figyeljnk, hogy megvalsts-fgg szolgltatsokat csak akkor hasznljunk, ha elkerlhetetlen.

B.3.1. Fejllomnyok
Eredetileg minden fejllomny kiterjesztse .h volt, gy a C++ egyes vltozataiban is megjelentek az olyan fejllomnyok, mint a <map.h> vagy az <iostream.h>. A kompatibilits rdekben ezek ltalban ma is hasznlhatk. Amikor a szabvnyost bizottsgnak j fejllomnyokat kellett bevezetnie a szabvnyos knyvtrak trt vltozatai, illetve az jonnan meghatrozott knyvtri szolgltatsok kezelshez, a fejllomnyok elnevezse problmkba tkztt. A rgi .h kiterjeszts hasznla-

Forrs: http://www.doksi.hu

B. Kompatibilits

1105

ta sszeegyeztethetsgi problmkat okozott volna. A megoldst a .h kiterjeszts elhagysa jelentette az j szabvnyos fejllomny-nevekben. Az uttag egybknt is felesleges volt, mert a < > enlkl is jelezte, hogy szabvnyos fejllomnyt neveztnk meg. Teht a standard knyvtr kiterjeszts nlkli fejllomnyokat biztost (pldul <map> vagy <iostream>). Ezen llomnyok deklarcii az std nvtrben szerepelnek. A rgebbi fejllomnyok a globlis nvtrben kapnak helyet s ezek nevben szerepel a .h kiterjeszts. Pldul:
#include<iostream> int main() { std::cout << "Hell, vilg!\n"; }

Ha ezt a programrszletet nem sikerl lefordtanunk, prblkozzunk a hagyomnyosabb vltozattal:


#include<iostream.h> int main() { cout << "Hell, vilg!\n"; }

A legtbb hordozhatsgi problmt a nem teljesen sszeegyeztethet fejllomnyok okozzk. A szabvnyos fejllomnyok ebbl a szempontbl ritkbban jelentenek gondot. Nagyobb programoknl gyakran elfordul, hogy sok fejllomnyt hasznlunk, s ezek nem mindegyike szerepel az sszes rendszerben vagy sok deklarci nem ugyanabban a fejllomnyban jelenik meg. Az is elfordul, hogy bizonyos deklarcik szabvnyosnak tnnek (mert szabvnyos nev fejllomnyokban szerepelnek), de valjban semmilyen szabvnyban nem szerepelnek. Sajnos nincs teljesen kielgt mdszer a fejllomnyok ltal okozott hordozhatsgi problmk kezelsre. Egy gyakran alkalmazott megolds, hogy elkerljk a fejllomnyoktl val kzvetlen fggst s a fennmarad fggsgeket kln fogalmazzuk meg. gy a hordozhatsgot a kzvetett hivatkozsokkal s az elklntett fggsgkezelssel javtjuk. Pldul, ha egy szksges deklarci a klnbz rendszerekben klnbz fejllomnyokon keresztl rhet el, egy alkalmazsfgg fejllomnyt hasznlhatunk, amely az

Forrs: http://www.doksi.hu

1106

Fggelkek s trgymutat

#include segtsgvel magba foglalja az egyes rendszerek megfelel llomnyait. Ugyangy, ha valamilyen szolgltatst a klnbz rendszerekben mskppen rhetnk el, a szolgltatshoz alkalmazsfgg osztlyokat s fggvnyeket kszthetnk.

B.3.2. A standard knyvtr


Termszetesen a C++ szabvny eltti vltozataibl hinyozhatnak a standard knyvtr bizonyos rszei. A legtbb rendszerben szerepelnek az adatfolyamok, a complex adattpus (ltalban nem sablonknt), a klnbz string osztlyok s a C standard knyvtra. A map, a list, a valarray stb. azonban gyakran hinyozik ezekbl a megvalstsokbl. Ilyenkor prbljuk az elrhet knyvtrakat gy hasznlni, hogy ksbb lehetsgnk legyen az talaktsra, ha szabvnyos krnyezetbe kerlnk. ltalban jobban jrunk, ha a nem szabvnyos string, list vagy map osztlyt hasznljuk, ahelyett, hogy a standard knyvtr osztlyainak hinya miatt visszatrnnk a C stlus programozshoz. Egy msik lehetsg, hogy a standard knyvtr STL rsznek (16., 17., 18. s 19. fejezet) j megvalstsai tlthetk le ingyenesen az Internetrl. A standard knyvtr rgebbi vltozatai mg nem voltak teljesek. Pldul sokszor jelentek meg trolk gy, hogy memriafoglalk hasznlatra mg nem volt lehetsg, vagy ppen ellenkezleg, mindig ktelez volt megadni a memriafoglalt. Hasonl problmk fordultak el az eljrsmd-paramterek esetben is, pldul az sszehasonltsi feltteleknl:
list<int> li; // rendben, de nhny megvalsts megkveteli a memriafoglalt list<int,allocator<int> > li2; // rendben, de nhny megvalsts nem ismeri // a memriafoglalt map<string,Record> m1; // rendben, de nhny megvalsts megkvetel egy // 'kisebb mint' mveletet map<string,Record,less<string> > m2;

Mindig azt a vltozatot kell hasznlnunk, amelyet az adott fejlesztkrnyezet elfogad. Szerencss esetben rendszernk az sszes formt ismeri. A rgebbi C++-vltozatokban gyakran istrstream s ostrstream osztly szerepelt, a <strstream.h> nev fejllomny rszeknt, mg a szabvny az istringstream s ostringstream osztlyokat adja meg, melyeknek helye az <sstream> fejllomny. A strstream adatfolyamok kzvetlenl karaktertmbkn vgeztek mveleteket (lsd 21.10 [26]).

Forrs: http://www.doksi.hu

B. Kompatibilits

1107

A szabvny eltti C++-vltozatokban az adatfolyamok nem voltak paramteresek. A basic_ eltag sablonok jak a szabvnyban; a basic_ios osztlyt rgebben ios osztlynak hvtk. Kiss meglep mdon az iostate rgi neve viszont io_state volt.

B.3.3. Nvterek
Ha rendszernk nem tmogatja a nvterek hasznlatt, a program logikai szerkezett kifejezhetjk forrsfjlok segtsgvel is (9.fejezet). A fejllomnyok ugyangy jl hasznlhatk sajt vagy C kddal kzsen hasznlt felleteink lersra. Ha a nvterek nem llnak rendelkezsre, a nvtelen nvterek hinyt a static kulcssz hasznlatval ellenslyozhatjuk. Szintn sokat segthet, ha a globlis nevekben egy eltaggal jelezzk, hogy milyen logikai egysghez tartoznak:
// nvtr eltti nyelvi vltozatokban: class bs_string { /* ... */ }; typedef int bs_bool; class joe_string; enum joe_bool { joe_false, joe_true }; // Bjarne sajt string tpusa // Bjarne sajt bool tpusa // Joe sajt string tpusa // Joe sajt bool tpusa

Az eltagok kivlasztsakor azonban legynk elvigyzatosak, mert a ltez C s C++ knyvtrak esetleg ugyanilyen eltagokat hasznlnak.

B.3.4. Helyfoglalsi hibk


Mieltt a kivtelkezels megjelent a C++-ban, a new opertor 0 rtket adott vissza, ha a helyfoglals nem sikerlt. A szabvnyos C++ new opertora ma mr egy bad_alloc kivtellel jelzi a hibt. ltalban rdemes programjainkat a szabvnynak megfelelen trni. Itt ez annyit jelent, hogy nem a 0 visszatrsi rtket, hanem a bad_alloc kivtelt figyeljk. ltalban mindkt esetben nagyon nehz a problmt egy hibazenetnl hatkonyabban kezelni. Ha gy rezzk, nem rdemes a 0 rtk vizsglatt a bad_alloc kivtel figyelsre talaktani, ltalban elrhetjk, hogy a program gy mkdjn, mintha nem llnnak rendelkezsnkre a kivtelek. Ha rendszernkben nincs _new_handler teleptve, a nothrow memriafoglal segtsgvel kivtelek helyett a 0 visszatrsi rtkkel vizsglhatjuk a helyfoglalsi hibk elfordulst:

Forrs: http://www.doksi.hu

1108

Fggelkek s trgymutat

X* p1 = new X; X* p2 = new(nothrow) X;

// bad_alloc kivtelt vlt ki, ha nincs memria // visszatrsi rtke 0, ha nincs memria

B.3.5. Sablonok
A szabvny szmos j szolgltatst vezetett be a sablonok krben s a rgebbi lehetsgek szablyait is tisztbban fogalmazta meg. Ha rendszernk nem tmogatja a rszlegesen egyedi cl vltozatok (specializcik) megadst, akkor azokhoz a sablonokhoz, amelyeket egybknt egyszer szakostssal hoznnk ltre, nll nevet kell rendelnnk:
template<class T> class plist : private list<void*> { // ... }; // list<T*> kellett volna

Ha az adott nyelvi vltozat nem tmogatja a sablon tagok hasznlatt, nhny mdszerrl knytelenek lesznk lemondani. A sablon tagok segtsgvel pldul olyan rugalmasan hatrozhatjuk meg elemek ltrehozst, illetve talaktst, amire ezek nlkl nincs lehetsgnk. (13.6.2.) Nha kisegt megoldst jelent az, hogy egy nll (nem tag) fggvnyt adunk meg, amely ltrehozza a megfelel objektumot:
template<class T> class X { // ... template<class A> X(const A& a); };

Ha nem hasznlhatunk sablon tagokat, knytelenek vagyunk konkrt tpusokra korltozni tevkenysgnket:
template<class T> class X { // ... X(const A1& a); X(const A2& a); // ... };

A korbbi C++-rendszerek tbbsge a sablon osztlyok pldnyostsakor a sablon sszes fggvnyt lemsolja s ltrehozza kdjukat. Ez ahhoz vezethet, hogy a nem hasznlt tagfggvnyek hibt okoznak (C.13.9.1.). A megoldst az jelenti, ha a krdses tagfggvnyek meghatrozst az osztlyon kvl helyezzk el.

Forrs: http://www.doksi.hu

B. Kompatibilits

1109

A
template<class T> class Container { // ... public: void sort() { /* hasznlja < mveletet */ } }; class Glob { /* a Glob-ban nincs < mvelet */ }; Container<Glob> cg; // nhny szabvny eltti vltozatban // Container<Glob>::sort() szerepel

// osztlyon belli meghatrozs

helyett pldul a kvetkez megoldst adhatjuk:


template<class T> class Container { // ... public: void sort(); }; template<class T> void Container<T>::sort() { /* a < mvelet hasznlata */ } // osztlyon kvli definici class Glob { /* a Glob-ban nincs < mvelet */ }; Container<Glob> cg; // nincs baj, amg a cg.sort()-ot meg nem hvjuk

A C++ rgebbi megvalstsai nem teszik lehetv az osztlyban ksbb bevezetett tagok kezelst:
template<class T> class Vector { public: T& operator[](size_t i) { return v[i]; } // v ksbb bevezetend // ... private: T* v; // hopp: nem tallja! size_t sz; };

Ilyenkor vagy gy rendezzk t a tagokat, hogy elkerljk az ilyen problmkat, vagy a fggvny meghatrozst az osztlydeklarci utn helyezzk.

Forrs: http://www.doksi.hu

1110

Fggelkek s trgymutat

Nhny, szabvny eltti C++-vltozat a sablonok esetben nem fogadja el az alaprtelmezett paramterek hasznlatt (13.4.1.). Ez esetben minden sablonparamtert kln meg kell adnunk:
template<class Key, class T, class LT = less<T> > class map { // ... }; map<string,int> m; map< string,int,less<string> > m2; // hopp: nem lehet alaprtelmezett sablonparamter // megolds: explicit megads

B.3.6. A for utasts kezdrtk-ad rsze


Vizsgljuk meg az albbi pldt:
void f(vector<char>& v, int m) { for (int i= 0; i<v.size() && i<=m; ++i) cout << v[i]; if (i == m) { // ... } // hiba: hivatkozs 'i'-re a 'for' utasts utn

Az ilyen programok rgebben mkdtek, mert az eredeti C++-ban a vltoz hatkre a for utastst tartalmaz hatkr vgig terjedt. Ha ilyen programokkal tallkozunk, a vltozt rdemes egyszeren a for ciklus eltt bevezetnnk:
void f2(vector<char>& v, int m) { int i= 0; // 'i' kell a ciklus utn for (; i<v.size() && i<=m; ++i) cout << v[i]; if (i == m) { // ... }

Forrs: http://www.doksi.hu

B. Kompatibilits

1111

B.4. Tancsok
[1] A C++ megtanulshoz hasznljuk a szabvnyos C++ legjabb s legteljesebb vltozatt, amihez csak hozz tudunk frni. B.3. [2] A C s a C++ kzs rsze egyltaln nem az a rsze a C++-nak, amit elszr meg kell tanulnunk. 1.6, B.3. [3] Amikor komoly alkalmazst ksztnk, gondoljunk r, hogy nem minden C++vltozat tartalmazza a szabvny sszes lehetsgt. Mieltt egy fbb szolgltatst hasznlunk egy komoly programban, rdemes kitapasztalnunk azt egy-kt kisebb program megrsval. Ezzel ellenrizhetjk, hogy rendszernk mennyire illeszkedik a szabvnyhoz s mennyire hatkony a megvalsts. Pldakppen lsd 8.5[6-7], 16.5[10], B.5[7] [4] Kerljk az elavult szolgltatsokat, pldul a static kulcssz globlis hasznlatt, vagy a C stlus tpustalaktst. [5] A szabvny megtiltotta az implicit int hasznlatt, teht mindig pontosan adjuk meg fggvnyeink, vltozink, konstansaink stb. tpust. B.2.2. [6] Amikor egy C programot C++ programm alaktunk, elszr a fggvnydeklarcikat (prototpusokat), s a szabvnyos fejllomnyok kvetkezetes hasznlatt ellenrizzk. B.2.2. [7] Amikor egy C programot C++ programm alaktunk, nevezzk t azokat a vltozkat, melyek C++ kulcsszavak. B.2.2. [8] Amikor egy C programot C++ programm alaktunk, a malloc() eredmnyt mindig megfelel tpusra kell alaktanunk. Mg hasznosabb, ha a malloc() sszes hvst a new opertorral helyettestjk. B.2.2. [9] Ha a malloc() s a free() fggvnyeket a new s a delete opertorra cserljk, vizsgljuk meg, hogy a realloc() fggvny hasznlata helyett nem tudjuk-e a vector, a push_back() s a reserve() szolgltatsait ignybe venni. 3.8, 16.3.5. [10] Amikor egy C programot C++ programm alaktunk, gondoljunk r, hogy nincs automatikus talakts egszekrl felsorol tpusokra, teht meghatrozott talaktst kell alkalmaznunk, ha ilyen talaktsra van szksg. 4.8. [11] Az std nvtrben meghatrozott szolgltatsok kiterjeszts nlkli fejllomnyokban szerepelnek. (Az std::cout deklarcija pldul az <iostream> fejllomny rsze.) A rgebbi vltozatokban a standard knyvtr nevei is a globlis nvtrbe kerltek, a fejllomnyok pedig .h kiterjesztssel rendelkeztek. (A ::cout deklarcija pldul az <iostream.h> fejllomnyban szerepelt.) 9.2.2, B.3.1. [12] Ha rgebbi C++ programok a new visszatrsi rtkt a 0 rtkkel hasonltjk ssze, akkor ezt a bad_alloc kivtel ellenrzsre kell cserlnnk, vagy a new(nothrow) utastst kell helyette hasznlnunk. B.3.4.

Forrs: http://www.doksi.hu

1112

Fggelkek s trgymutat

[13] Ha fejlesztkrnyezetnk nem tmogatja az alaprtelmezett sablonparamterek hasznlatt, meg kell adnunk minden paramtert. A typedef segtsgvel a sablonparamterek ismtelgetse elkerlhet (ahhoz hasonlan, ahogy a string tpus-meghatrozs megkml minket a basic_string< char, char_traits<char>, allocator<char> > lerstl). B.3.5. [14] Az std::string osztly hasznlathoz a <string> fejllomnyra van szksgnk. (A <string.h> llomnyban a rgi, C stlus karakterlnc-fggvnyek szerepelnek.) 9.2.2, B.3.1. [15] Minden <X.h> szabvnyos C fejllomnynak (amely a globlis nvtrbe vezet be neveket) megfelel egy <cX> fejllomny, amely az elemeket az std nvtrbe helyezi. B.3.1. [16] Nagyon sok rendszerben tallhat egy String.h fejllomny, amely egy karakterlnc-tpust r le. Mindig gondoljunk r, hogy ezek eltrhetnek a szabvnyos string osztlytl. [17] Ha lehetsgnk van r, a szabvnyos eszkzket hasznljuk a nem szabvnyos lehetsgek helyett. 20.1, B.3, C.2. [18] Ha C fggvnyeket vezetnk be, hasznljuk az extern C formt.

B.5. Gyakorlatok
1. (*2.5) Vegynk egy C programot s alaktsuk t C++-ra. Soroljuk fel a programban hasznlt, a C++-ban nem hasznlhat elemeket, s vizsgljuk meg, rvnyesek-e az ANSI C szabvny szerint. Els lpsben a programot csak ANSI C formtumra alaktsuk (prototpusokkal stb.), csak ezutn C++-ra. Becsljk meg, mennyi idt vesz ignybe egy 100 000 soros C program talaktsa C++-ra. 2. (*2.5) rjunk programot, amely segt talaktani egy C programot C++-ra. Vgezze el azon vltozk tnevezst, melyek a C++-ban kulcsszavak, a malloc() hvsokat helyettestse a new opertorral stb. Ajnls: ne akarjunk tkletes programot rni. 3. (*2) Egy C stlus C++ programban (pldul amit mostanban alaktottak t egy C programbl) a malloc() fggvny hvsait cserljk a new opertor meghvsra. tlet: B.4[8-9] 4. (*2.5) Egy C stlus C++ programban (pldul amit mostanban alaktottak t egy C programbl) a makrk, globlis vltozk, kezdrtk nlkli vltozk s tpustalaktsok szmt cskkentsk a minimumra.

Forrs: http://www.doksi.hu

B. Kompatibilits

1113

5. (*3) Vegynk egy C++ programot, amit egy egyszer eljrssal alaktottunk t egy C programbl, s brljuk azt, mint C++ programot az adatrejts, az elvont brzols, az olvashatsg, a bvthetsg s a rszek esetleges jrahasznosthatsga szerint. Vgezznk valamilyen nagyobb talaktst ezen brlatok alapjn. 6. (*2) Vegynk egy kicsi (mondjuk 500 soros) C++ programot s alaktsuk azt C programm. Hasonltsuk ssze a kt programot a mret s az elkpzelhet tovbbfejlesztsek szempontjbl. 7. (*3) rjunk nhny kicsi tesztprogramot, mellyel megllapthatjuk, hogy egy C++-vltozat rendelkezik-e a legjabb szabvnyos lehetsgekkel. Pldul mi a for utasts kezdrtk-ad rszben bevezetett vltoz hatkre? (B.3.6.) Hasznlhatk-e alaprtelmezett sablonparamterek? (B.3.5.) Hasznlhatk-e sablon tagok? (8.2.6.) Ajnls: B.2.4. 8. (*2.5) Vegynk egy C++ programot, amely egy <X.h> fejllomnyt hasznl s alaktsuk t gy, hogy az <X> s <cX> fejllomnyokat hasznlja. Cskkentsk a lehet legkevesebbre a using utastsok hasznlatt.

Forrs: http://www.doksi.hu

C
Technikai rszletek
Valahol a llek s az Univerzum legmlyn mindennek megvan a maga oka" (Slartibartfast) Mit gr a szabvny? Karakterkszletek Egsz literlok Konstans kifejezsek Kiterjesztsek s talaktsok Tbbdimenzis tmbk Mezk s unik Memriakezels Szemtgyjts Nvterek Hozzfrs-szablyozs Mutatk adattagokra Sablonok static tagok friend-ek Sablonok, mint sablonparamterek Sablonparamterek levezetse A typename s a template minstk Pldnyosts Nvkts Sablonok s nvterek Explicit pldnyosts Tancsok

C.1. Bevezets s ttekints


Ebben a fejezetben olyan technikai rszleteket s pldkat mutatok be, amelyeket nem lehetett elegnsan beilleszteni abba a szerkezetbe, amit a C++ nyelv fbb lehetsgeinek bemutatsra hasznltam. Az itt kzlt rszletek fontosak lehetnek programjaink megrsakor is, de elengedhetetlenek, ha ezek felhasznlsval kszlt programot kell megrtennk. Technikai rszletekrl beszlek, mert nem szabad, hogy a tanulk figyelmt elvonja az el-

Forrs: http://www.doksi.hu

1116

Fggelkek s trgymutat

sdleges feladatrl, a C++ nyelv helyes hasznlatnak megismersrl, vagy hogy a programozkat eltrtse a legfontosabb cltl, gondolataik olyan tiszta s kzvetlen megfogalmazstl, amennyire csak a C++ lehetv teszi.

C.2. A szabvny
Az ltalnos hiedelemmel ellenttben a felttlen ragaszkods a C++ nyelvhez s a szabvnyos knyvtrakhoz nmagban vve nem jelent sem tkletes programot, sem hordozhat kdot. A szabvny nem mondja meg, hogy egy programrszlet j vagy rossz, mindssze azt rulja el, hogy a programoz mit vrhat el egy megvalststl s mit nem. Van, aki a szabvny betartsval is borzalmas programokat r, mg a legtbb j program hasznl olyan lehetsgeket is, melyeket a szabvny nem tartalmaz. A szabvny nagyon sok fontos dolgot megvalsts-fggnek (implementation-defined) minst. Ez azt jelenti, hogy minden nyelvi vltozatnak egy konkrt, pontosan meghatrozott megoldst kell adnia az adott krdsre s ezt a megoldst pontosan dokumentlnia is kell:
unsigned char c1 = 64; unsigned char c2 = 1256; // megfelel meghatrozs: egy karakter legalbb 8 bit s // mindig kpes 64-et trolni // megvalsts-fgg: csonkol, ha a char csak 8 bites

A c1 kezdeti rtkadsa megfelelen meghatrozott, mert a char tpusnak legalbb 8 bitesnek kell lennie, a c2- viszont megvalsts-fgg, mert a char tpus brzolshoz hasznlt bitek pontos szma is az. Ha a char csak 8 bites, az 1256 rtk 232-re csonkul (C.6.2.1). A legtbb megvalsts-fgg szolgltats a programok futtatshoz hasznlt hardverhez igazodik. Amikor komoly programokat runk, gyakran szksg van arra, hogy megvalsts-fgg lehetsgeket hasznljunk. Ez az ra annak, ha szmtalan klnbz rendszeren hatkonyan futtathat programokat akarunk rni. Sokat egyszerstett volna a nyelven, ha a karaktereket 8 bitesknt, az egszeket pedig 32 bitesknt hatrozzuk meg. Nem ritkk azonban a 16 vagy 32 bites karakterkszletek, sem az olyan egsz rtkek, melyek nem brzolhatk 32 biten. Ma mr lteznek 32 gigabjtot meghalad kapacits merevlemezek is, gy a lemezcmek brzolshoz rdemes lehet 48 vagy 64 bites egsz rtkeket hasznlni.

Forrs: http://www.doksi.hu

C. Technikai rszletek

1117

A hordozhatsg lehet leghatkonyabb megvalstshoz rdemes pontosan megfogalmaznunk, hogy milyen megvalsts-fgg nyelvi elemeket hasznltunk a programunkban, s rdemes vilgosan elklntennk a program azon rszeit, ahol ezeket a szolgltatsokat hasznljuk. Egy jellemz plda erre a megoldsra, hogy az sszes olyan elemet, amely valamilyen mdon fgg a hardvertl, egy fejllomnyban, konstansok s tpus-meghatrozsok formjban rgztjk. Ezt az eljrst tmogatja a standard knyvtr a numeric_limits osztllyal (22.2). A nem meghatrozott (definilatlan) viselkeds kellemetlenebb dolog. Egy nyelvi szerkezetet akkor nevez nem meghatrozottnak a szabvny, ha semmilyen rtelmes mkdst nem vrhatunk el a megvalststl. A szoksos megvalstsi mdszerek ltalban a nem meghatrozott lehetsgeket hasznl programok nagyon rossz viselkedst eredmnyezik:
const int size = 4*1024; char page[size]; void f() { page[size+size] = 7; }

// nem meghatrozhat

Egy ilyen programrszlet teljesen kzenfekv kvetkezmnye, hogy a memriban olyan adatokat runk fell, melyeket nem lenne szabad, vagy hardverhibk, kivtelek lpnek fel. A megvalststl nem vrhatjuk el, hogy ilyen kzenfekv szablytalansg esetben is sszeren mkdjn. Ha a programot komolyan optimalizljuk, a nem meghatrozott szerkezetek hasznlatnak eredmnye teljesen kiszmthatatlann vlik. Ha ltezik kzenfekv s knnyen megvalsthat megoldsa egy problmnak, azt inkbb megvalsts-fggnek minsti a szabvny s nem definilatlannak. rdemes jelents idt s erfesztst sznnunk annak biztostsra, hogy programunk semmi olyat ne hasznljon, ami a szabvny ltal nem meghatrozott helyzetekhez vezethet. Sok esetben kln eszkzk segtik ezt a munkt.

Forrs: http://www.doksi.hu

1118

Fggelkek s trgymutat

C.3. Karakterkszletek
E knyv pldaprogramjai az ASCII-nek (ANSI3.4-1968) nevezett, 7 bites, nemzetkzi, ISO 646-1983 karakterkszlet amerikai angol vltozatnak felhasznlsval kszltek. Ez hromfle problmt jelenthet azoknak, akik ms karakterkszletet hasznl C++krnyezetben rjk programjaikat: 1. Az ASCII tartalmaz olyan elvlaszt karaktereket s szimblumokat is pldul a ], a { vagy a ! , melyek egyes karakterkszletekben nem tallhatk meg. 2. Azokhoz a karakterekhez, melyeknek nincs hagyomnyos brzolsa, valamilyen jellsmdot kell vlasztanunk (pldul az jsorhoz, vagy a 17-es kd karakterhez). 3. Az ASCII nem tartalmaz bizonyos karaktereket pldul a , a vagy az , melyeket az angoltl eltr nyelvekben viszonylag gyakran hasznlnak.

C.3.1. Korltozott karakterkszletek


A klnleges ASCII karakterek [, ], {, }, | s \ az ISO szerint betnek minstett karakterpozcit foglalnak el. A legtbb eurpai ISO-646 karakterkszletben ezeken a pozcikon az angol bcben nem szerepl betk szerepelnek. A dn nemzeti karakterkszlet pldul ezeket a pozcikat az , , , , , magnhangzk brzolsra hasznlja s ezek nlkl nem tl sok dn szveg rhat le. A trigrf (hrom jelbl ll) karakterek szolglnak arra, hogy tetszleges nemzeti karaktereket rhassunk le, hordozhat formban, a legkisebb szabvnyos karakterkszlet felhasznlsval. Ez a forma hasznos lehet, ha programunkat tnyleg t kell vinnnk ms rendszerre, de a programok olvashatsgt ronthatja. Termszetesen a hossz tv megolds erre a problmra a C++ programozk szmra az, hogy beszereznek egy olyan rendszert, ahol sajt nemzeti karaktereiket s a C++ jeleit is hasznlhatjk. Sajnos ez a megolds nem mindenhol megvalsthat, s egy j rendszer beszerzse idegesten lass megolds lehet. A C++ az albbi jellsekkel teszi lehetv, hogy a programozk hinyos karakterkszlettel is tudjanak programot rni:

Forrs: http://www.doksi.hu

C. Technikai rszletek

1119

Kulcsszavak and and_eq bitand bitor compl not or or_eq xor xor_eq not_eq && &= & | ~ ! || |= ^ ^= !=

Digrf <% %> <: :> %: %:%: { } [ ] # ## ??= ??( ??< ??/ ??) ??> ??' ??! ??-

Trigrf # [ { \ ] } ^ | ~

Azok a programok melyek ezeket a kulcsszavakat s digrf karaktereket hasznljk, sokkal olvashatbbak, mint a velk egyenrtk, de trigrf karakterekkel kszlt programok. Ha azonban az olyan karakterek, mint a { nem rhetk el rendszernkben, knytelenek vagyunk a trigrf karaktereket hasznlni a karakterlncokban s karakter-konstansokban a hinyz jelek helyett. A '{' helyett pldul a '??<' karakterkonstanst rhatjuk. Egyes programozk hagyomnyos opertor-megfelelik helyett szvesebben hasznljk az and s hasonl kulcsszavakat.

Forrs: http://www.doksi.hu

1120

Fggelkek s trgymutat

C.3.2. Vezrlkarakterek
A \ jel segtsgvel nhny olyan karaktert rhetnk el, melyeknek szabvnyos neve is van:

Nv jsor/sortrs vzszintes tabultor fggleges tabultor visszatrls kocsivissza lapdobs cseng fordtott perjel krdjel aposztrf idzjel oktlis szm hexadecimlis szm

ASCII jells NL (LF) HT VT BS CR FF BEL \ ? ooo hhh

C++ jells \n \t \v \b \r \f \a \\ \? \ \ \ooo \xhhh

Megjelensi formjuk ellenre ezek is egyetlen karaktert jelentenek. Lehetsgnk van arra is, hogy egy karaktert egy-, kt- vagy hromjegy, oktlis (a \ utn nyolcas szmrendszerbeli), vagy hexadecimlis (a \x utn 16-os szmrendszerbeli) szmknt adjunk meg. A hexadecimlis jegyek szma nem korltozott. Az oktlis vagy hexadecimlis szmjegyek sorozatnak vgt az els olyan karakter jelzi, amely nem rtelmezhet oktlis, illetve hexadecimlis jegyknt: Oktlis \6 \60 \137 Hexadecimlis \x6 \x30 \x05f Decimlis 6 48 95 ASCII ACK 0 _

Forrs: http://www.doksi.hu

C. Technikai rszletek

1121

Ez a jells lehetv teszi, hogy az adott rendszer tetszleges karaktert lerjuk vagy karakterlncokban felhasznljuk azokat (lsd 5.2.2). Viszont ha a karakterek jellsre szmokat hasznlunk, programunk nem lesz tvihet ms karakterkszletet hasznl rendszerre. Lehetsg van arra is, hogy egy karakterliterlban egynl tbb karaktert adjunk meg (pldul ab). Ez a megolds azonban elavult s megvalsts-fgg, gy rdemes elkerlnnk. Ha egy karakterlncban oktlis konstansokat hasznlunk a karakterek jellsre, rdemes mindig hrom jegyet megadnunk. A jellsrendszer elg knyelmetlen, gy knnyen eltveszthetjk, hogy a konstans utni karakter szmjegy-e vagy sem. A hexadecimlis konstansok esetben hasznljunk kt szmjegyet:
char char char char v1[] v2[] v3[] v4[] = = = = "a\xah\129"; "a\xah\127"; "a\xad\127"; "a\xad\0127"; // // // // 6 5 4 5 karakter: karakter: karakter: karakter: 'a' 'a' 'a' 'a' '\xa' 'h' '\12' '9' '\0' '\xa' 'h' '\127' '\0' '\xad' '\127' '\0' '\xad' '\012' '7' '\0'

C.3.3. Nagy karakterkszletek


C++ programot rhatunk olyan karakterkszletek felhasznlsval is, melyek sokkal bvebbek, mint a 127 karakterbl ll ASCII kszlet. Ha az adott nyelvi vltozat tmogatja a nagyobb karakterkszleteket, az azonostk, megjegyzsek, karakter konstansok s karakterlncok is tartalmazhatnak olyan karaktereket, mint az , a vagy a . Ahhoz azonban, hogy az gy kszlt program hordozhat legyen, a fordtnak ezeket a klnleges karaktereket valahogy olyan karakterekre kell kdolnia, melyek minden C++-vltozatban elrhetk. Ezt az talaktst a C++ a knyvben is hasznlt alap karakterkszletre a rendszer ltalban mg azeltt vgrehajtja, hogy a fordt brmilyen ms tevkenysgbe kezdene, teht a programok jelentst ez nem rinti. A nagy karakterkszletekrl a C++ ltal tmogatott kisebb karakterkszletre val szabvnyos talaktst ngy vagy nyolc jegybl ll hexadecimlis szmok valstjk meg:

ltalnos karakternv: \U X X X X X X X X \u X X X X Itt X egyetlen, hexadecimlis szmjegyet jell (pldul \u1e2b). A rvidebb \uXXXX jells egyenrtk a \U0000XXXX-szel. Ha a megadott szmban a jegyek szma nem ngy s nem is nyolc, a fordt hibt jelez.

Forrs: http://www.doksi.hu

1122

Fggelkek s trgymutat

A programoz ezeket a karakterkdolsokat kzvetlenl hasznlhatja, de valjban arra talltk ki, hogy a programoz ltal lthat karaktereket a megvalsts belsleg egy kisebb karakterkszlettel trolhassa. Ha a bvtett karakterkszletek azonostkban val hasznlathoz egy adott krnyezet egyedi szolgltatsaira tmaszkodunk, programunk hordozhatsgt ersen lerontjuk. Ha nem ismerjk azt a termszetes nyelvet, amelyet az azonostk, illetve megjegyzsek megfogalmazshoz hasznltak, a program nagyon nehezen olvashat lesz, ezrt a nemzetkzileg hasznlt programokban rdemes megmaradnunk az angol nyelvnl s az ASCII karakterkszletnl.

C.3.4. Eljeles s eljel nlkli karakterek


Megvalsts-fgg az is, hogy az egyszer char eljeles vagy eljel nlkli tpus-e, ami nhny kellemetlen meglepetst s vratlan helyzeteket eredmnyezhet:
char c = 255; int i = c; // a 255 "csupa egyes", hexadecimlis jellssel 0xFF

Mi lesz itt az i rtke? Sajnos nem meghatrozhat. A vlasz az ltalam ismert sszes nyelvi vltozatban attl fgg, hogy a char rtk bitmintja milyen rtket eredmnyez, ha egszknt rtelmezzk. Egy SGI Challenge gpen a char eljel nlkli, gy az eredmny 255. Egy Sun SPARC vagy egy IBM PC gpen viszont a char eljeles, aminek kvetkeztben az i rtke -1 lesz. Ilyenkor a fordtnak illik figyelmeztetnie, hogy a 255 literl karakterr alaktsval a -1 rtket kapjuk. A C++ nem ad ltalnos megoldst ezen problma kikszblsre. Az egyik lehetsg, hogy teljesen elkerljk az egyszer char tpus hasznlatt s mindig valamelyik egyedi cl vltozatot hasznljuk. Sajnos a standard knyvtr bizonyos fggvnyei (pldul a strcmp()) egyszer char tpus paramtert vrnak (20.4.1). Egy char rtknek mindenkppen signed char vagy unsigned char tpusnak kell lennie, de mind a hrom karaktertpus klnbz, gy pldul a klnbz char tpusokra hivatkoz mutatk sem keverhetk ssze:
void f(char c, signed char sc, unsigned char uc) { char* pc = &uc; // hiba: nincs mutat-talakts signed char* psc = pc; // hiba: nincs mutat-talakts unsigned char* puc = pc; // hiba: nincs mutat-talakts psc = puc; // hiba: nincs mutat-talakts }

Forrs: http://www.doksi.hu

C. Technikai rszletek

1123

A klnbz char tpusok vltozi szabadon rtkl adhatk egymsnak, de amikor egy eljeles char vltozba tl nagy rtket akarunk rni, az eredmny nem meghatrozhat lesz:
void f(char c, signed char sc, unsigned char uc) { c = 255; // megvalsts-fgg, ha a sima karakterek eljelesek s 8 bitesek c = sc; c = uc; sc = uc; uc = sc; sc = c; uc = c; // rendben // megvalsts-fgg, ha a sima karakterek eljelesek s 'uc' rtke tl nagy // megvalsts-fgg, ha 'uc' rtke tl nagy // rendben: talakts eljel nlklire // megvalsts-fgg, ha a sima karakterek eljel nlkliek s 'c' rtke tl nagy // rendben: talakts eljel nlklire

Ha mindenhol az egyszer char tpust hasznljuk, ezek a problmk nem jelentkeznek.

C.4. Az egsz literlok tpusa


Az egsz literlok tpusa ltalban alakjuktl, rtkktl s uttagjuktl is fgg: Ha decimlis rtkrl van sz s a vgre nem runk semmilyen uttagot, tpusa az els olyan lesz a kvetkezk kzl, amelyben brzolhat: int, long int, unsigned long int. Ha oktlis vagy hexadecimlis formban, uttag nlkl adjuk meg az rtket, annak tpusa az els olyan lesz a kvetkezk kzl, amelyben brzolhat: int, unsigned int, long int, unsigned long int. Ha az u vagy az U uttagot hasznljuk, a tpus a kvetkez lista els olyan tpusa lesz, amelyben az rtk brzolhat: unsigned int, unsigned long int. Ha l vagy L uttagot adunk meg, az rtk tpusa a kvetkez lista els olyan tpusa lesz, amelyben az rtk brzolhat: long int, unsigned long int. Ha az uttag ul, lu, uL, Lu, Ul, lU, UL vagy LU, az rtk tpusa unsigned long int lesz. A 100000 pldul egy olyan gpen, amelyen az int 32 bites, int tpus rtk, de long int egy olyan gpen, ahol az int 16, a long int pedig 32 bites. Ugyangy a 0XA000 tpusa int,

Forrs: http://www.doksi.hu

1124

Fggelkek s trgymutat

ha az int 32 bites, de unsigned int, ha 16 bites. Ezeket a megvalsts-fggsgeket az uttagok hasznlatval kerlhetjk el: a 100000L minden gpen long int tpus, a 0XA000U pedig minden gpen unsigned int.

C.5. Konstans kifejezsek


Az olyan helyeken, mint a tmbhatrok (5.2.), a case cmkk (6.3.2.) vagy a felsorol elemek kezdrtk-adi (4.8.), a C++ konstans kifejezseket hasznl. A konstans kifejezs rtke egy szm vagy egy felsorolsi konstans. Ilyen kifejezseket literlokbl (4.3.1, 4.4.1, 4.5.1), felsorol elemekbl (4.8) s konstans kifejezsekkel meghatrozott const rtkekbl pthetnk fel. Sablonokban egsz tpus sablonparamtereket is hasznlhatunk (C.13.3). Lebegpontos literlok (4.5.1) csak akkor hasznlhatk, ha azokat meghatrozott mdon egsz tpusra alaktjuk. Fggvnyeket, osztlypldnyokat, mutatkat s hivatkozsokat csak a sizeof opertor (6.2) paramtereknt hasznlhatunk. A konstans kifejezs egy olyan egyszer kifejezs, melyet a fordt ki tud rtkelni, mieltt a programot sszeszerkesztennk s futtatnnk.

C.6. Automatikus tpustalakts


Az egsz s a lebegpontos tpusok (4.1.1) szabadon keverhetk az rtkadsokban s a kifejezsekben. Ha lehetsg van r, a fordt gy alaktja t az rtkeket, hogy ne vesztsnk informcit. Sajnos az informcivesztssel jr talaktsok is automatikusan vgrehajtsra kerlnek. Ebben a rszben az talaktsi szablyokrl, az talaktsok problmirl s ezek kvetkezmnyeirl lesz sz.

C.6.1. Kiterjesztsek
Azokat az automatikus talaktsokat, melyek megrzik az rtkeket, kzsen kiterjesztseknek (promotion) nevezzk. Mieltt egy aritmetikai mveletet vgrehajtunk, egy egsz tpus kiterjeszts az int tpusnl kisebb rtkeket int rtkre alaktja. Figyeljnk r, hogy

Forrs: http://www.doksi.hu

C. Technikai rszletek

1125

ezek a kiterjesztsek nem long tpusra alaktanak (hacsak valamelyik operandus nem wchar_t vagy olyan felsorol rtk, amely mr eleve nagyobb, mint egy int). Ez a kiterjeszts eredeti cljt tkrzi a C nyelvben: az operandusokat termszetes mretre akarjuk alaktani az aritmetikai mveletek elvgzse eltt. Az egsz tpus kiterjesztsek a kvetkezk: A char, signed char, unsigned char, short int s unsigned short int rtkeket int tpusra alaktja a rendszer, ha az int az adott tpus sszes rtkt kpes brzolni; ellenkez esetben a cltpus unsigned int lesz. A wchar_t tpust (4.3) s a felsorol tpusokat (4.8) a rendszer a kvetkez lista els olyan tpusra alaktja, amely brzolni tudja a megfelel tpus sszes rtkt: int, unsigned int, long, unsigned long. A bitmezk (C.8.1) int tpusak lesznek, ha az kpes a bitmez sszes rtkt brzolni. Ha az int nem, de az unsigned int kpes erre, akkor az utbbi lesz a cltpus. Ha ez sem valsthat meg, akkor nem kvetkezik be egsz tpus kiterjeszts. Logikai rtkek int tpusra alaktsa: a false rtkbl 0, a true rtkbl 1 lesz. A kiterjesztsek a szoksos aritmetikai talaktsok rszt kpezik. (C.6.3)

C.6.2. talaktsok
Az alaptpusok szmos mdon alakthatk t: vlemnyem szerint tl sok is az engedlyezett talakts:
void f(double d) { char c = d; karakterre }

// vigyzat: ktszeres pontossg lebegpontos rtk talaktsa

Amikor programot runk, mindig figyelnnk kell arra, hogy elkerljk a nem meghatrozott szolgltatsokat s azokat az talaktsokat, melyek sz nlkl tntetnek el informcikat. A fordtk a legtbb veszlyes talaktsra figyelmeztethetnek s szerencsre ltalban meg is teszik.

Forrs: http://www.doksi.hu

1126

Fggelkek s trgymutat

C.6.2.1. Egsz talaktsok Egy egsz rtk brmely ms egsz tpusra talakthat, de a felsorol rtkeket is egsz tpusokra alakthatjuk. Ha a cltpus eljel nlkli, az eredmny egyszeren annyi bit lesz a forrsbl, amennyi a cltpusban elfr. (Ha kell, a magas helyirtkk biteket a rendszer eldobja.) Pontosabban fogalmazva, az eredmny a legkisebb olyan egsz, amelynek osztsa a 2 n-edik hatvnyval azonos rtket ad, mint a forrsrtk hasonl osztsa, ahol n az eljel nlkli tpus brzolshoz hasznlt bitek szma. Pldul:
unsigned char uc = 1023; // binris 1111111111: uc binris 11111111 lesz; ami 255

Ha a cltpus eljeles s az talaktand rtk brzolhat vele, az rtk nem vltozik meg. Ha a cltpus nem tudja brzolni az rtket, az eredmny az adott nyelvi vltozattl fgg lesz:
signed char sc = 1023; // megvalsts-fgg

A lehetsges eredmnyek: 127 s -1 (C.3.4). A logikai s felsorol rtkek automatikusan a megfelel egsz tpusra alakthatk (4.2, 4.8).

C.6.2.2. Lebegpontos talaktsok Lebegpontos rtkeket ms lebegpontos tpusokra alakthatunk t. Ha a forrsrtk pontosan brzolhat a cltpusban, az eredmny az eredeti szmrtk lesz. Ha a forrsrtk a cltpus kt egymst kvet brzolhat rtke kztt ll, eredmnyknt ezen kt rtk valamelyikt kapjuk. A tbbi esetben az eredmny nem meghatrozhat:
float f = FLT_MAX; double d = f; float f2 = d; double d3 = DBL_MAX; float f3 = d3; // a legnagyobb lebegpontos rtk // rendben: d == f // rendben: f2 == f // a legnagyobb double rtk // nem meghatrozott, ha FLT_MAX<DBL_MAX

Forrs: http://www.doksi.hu

C. Technikai rszletek

1127

C.6.2.3. Mutat- s hivatkozs-talaktsok Az objektumtpusra hivatkoz mutatk mind void* (5.6) tpusra alakthatk; a leszrmazott osztlyra hivatkoz mutatk s hivatkozsok brmelyike talakthat egy elrhet s egyrtelm alaposztlyra hivatkoz mutat, illetve hivatkozs tpusra (12.2). Fontos viszont, hogy egy fggvnyre vagy egy tagra hivatkoz mutat automatikusan nem alakthat void* tpusra. A 0 rtk konstans kifejezsek (C.5) brmilyen mutat s tagra hivatkoz mutat (5.1.1) tpusra automatikusan talakthatk:
int* p =

! ! !

! ! !

! ! ! ! ! ! !!!!!!

! ! ! !!!!!

! ! ! !!!!1;

Egy T* rtk automatikusan const T* tpusra alakthat (5.4.1) s ugyangy egy T& rtk is talakthat const T& tpusra.

C.6.2.4. Tagra hivatkoz mutatk talaktsa A tagokra hivatkoz mutatk s hivatkozsok gy alakthatk t automatikusan, ahogy a 15.5.1 pontban lertuk.

C.6.2.5. Logikai rtkek talaktsa A mutatk, egszek s lebegpontos rtkek automatikusan bool tpusv alakthatk (4.2). A nem nulla rtkek eredmnye true, a nulla eredmnye false lesz:
void f(int* p, int i) { bool is_not_zero = p; bool b2 = i; }

// igaz, ha p!=0 // igaz, ha i!=0

Forrs: http://www.doksi.hu

1128

Fggelkek s trgymutat

C.6.2.6. Lebegpontosegsz talaktsok Amikor egy lebegpontos rtket egssz alaktunk, a trtrsz elveszik, vagyis a lebegpontosrl egszre val talakts csonkolst eredmnyez. Az int(1.6) rtke pldul 1 lesz. Ha a csonkolssal keletkez rtk sem brzolhat a cltpusban, az eredmny nem meghatrozhatnak minsl:
int i = 2.7; char b = 2000.7; // i rtke 2 lesz // nem meghatrozott 8 bites char tpusra: a 2000 nem brzolhat // 8 bites karakterben

Az egszrl lebegpontosra val talakts matematikailag annyira pontos, amennyire a hardver megengedi. Ha egy egsz rtk egy lebegpontos tpus rtkeknt nem brzolhat, az eredmny nem lesz egszen pontos:
int i = float(1234567890);

Itt az i rtke 1234567936 lesz az olyan gpeken, amely az int s a float brzolsra is 32 bitet hasznlnak. Termszetesen clszer elkerlni az olyan automatikus talaktsokat, ahol informcit veszthetnk. A fordtk kpesek szlelni nhny veszlyes talaktst, pldul a lebegpontosrl egszre vagy a long int tpusrl char tpusra valt, ennek ellenre az ltalnos, fordtsi idben trtn ellenrzs nem mindig kellen megbzhat, ezrt a programoznak elvigyzatosnak kell lennie. Amennyiben az elvigyzatossg nem elegend, a programoznak ellenrzseket kell beiktatnia a hibk elkerlsre:
class check_failed { }; char checked(int i) { char c = i; if (i != c) throw check_failed(); return c; } void my_code(int i) { char c = checked(i); // ... }

// figyelem: nem hordozhat (C.6.2.1)

Ha gy szeretnnk csonkolst vgezni, hogy a program ms rendszerre is vltoztats nlkl tvihet legyen, vegyk figyelembe a numeric_limits-ben (22.2) megadottakat.

Forrs: http://www.doksi.hu

C. Technikai rszletek

1129

C.6.3. Szoksos aritmetikai talaktsok


Ezek az talaktsok akkor kvetkeznek be, ha egy ktoperandus (binris) mvelet kt operandust kzs tpusra kell alaktani. Ez a kzs tpus hatrozza meg az eredmny tpust is. 1. Ha az egyik operandus long double tpus, a rendszer a msikat is long double tpusv alaktja. Ha egyik sem long double, de valamelyik double, a msik is double tpusv alakul. Ha egyik sem double, de valamelyik float, a msik is float rtkre lesz talaktva. Ha a fentiek egyike sem teljesl, a rendszer mindkt operandusra egsz kiterjesztst (C.6.1) alkalmaz. 2. Ezutn, ha valamelyik operandus unsigned long, akkor a msik is unsigned long lesz. Ha a fenti nem teljesl, de az egyik operandus long int, a msik pedig unsigned int, s a long int tpus kpes brzolni az sszes unsigned int rtket, akkor az unsigned int rtket a fordt long int tpusra alaktja. Ha a long int nem elg nagy, mindkt rtket unsigned long int tpusra kell alaktani. Ha a fenti nem teljesl, de valamelyik operandus long, a msik is long tpusra alakul. Ha valamelyik operandus unsigned, a msik is unsigned rtkre alakul. Ha a fentiek egyike sem teljesl, mindkt rtk int lesz.

C.7. Tbbdimenzis tmbk


Nem ritka, hogy vektorok vektorra van szksgnk, st vektorok vektorainak vektorra. A krds az, hogy a C++-ban hogyan brzolhatjuk ezeket a tbbdimenzis tmbket. Ebben a pontban elszr megmutatjuk, hogyan hasznljuk a standard knyvtr vector osztlyt erre a clra, majd megvizsgljuk, hogyan kezelhetk a tbbdimenzis tmbk a C-ben s a C++-ban akkor, ha csak a beptett lehetsgek llnak rendelkezsnkre.

Forrs: http://www.doksi.hu

1130

Fggelkek s trgymutat

C.7.1. Vektorok
A szabvnyos vector (16.3) egy nagyon ltalnos megoldst knl:
vector< vector<int> > m(3, vector<int>(5));

Ezzel az utastssal egy 3 olyan vektort tartalmaz vektort hozunk ltre, melyek mindegyike 5 darab egsz rtk trolsra kpes. Mind a 15 egsz elem a 0 alaprtelmezett rtket kapja, melyet a kvetkez mdon mdosthatunk:
void init_m() { for (int i = 0; i<m.size(); i++) { for (int j = 0; j<m[i].size(); j++) m[i][j] = 10*i+j; } }

Grafikusan ezt gy brzolhatjuk:

m:

3 00 01 02 03 04 m[0]: m[1]: m[2]:

5 5 5

10 11 12 13 14

20 21 22 23 24

A vector objektumot egy, az elemekre hivatkoz mutatval s az elemek szmval brzoltuk. Az elemeket ltalban egy szoksos tmbben troljuk. Szemlltetskppen mindegyik egsz elembe a koordintinak megfelel rtket rtuk. Az egyes elemeket ktszeres indexelssel rhetjk el. Az m[i][j] kifejezs pldul az i-edik vektor j-edik elemt vlasztja ki. Az m elemeit a kvetkezkppen rathatjuk ki:

Forrs: http://www.doksi.hu

C. Technikai rszletek

1131

void print_m() { for (int i = 0; i<m.size(); i++) { for (int j = 0; j<m[i].size(); j++) cout << m[i][j] << '\t'; cout << '\n'; } }

Ennek eredmnye a kvetkez lesz:


01 10 20 2 11 21 3 12 22 4 13 23 14 24

Figyeljk meg, hogy az m vektorok vektora, nem egyszer tbbdimenzis tmb, ezrt a gyakorlatban lehetsgnk van arra, hogy egyesvel mdostsuk a bels vektorok mrett:
void reshape_m(int ns) { for (int i = 0; i<m.size(); i++) m[i].resize(ns); }

A vector< vector<int> > szerkezetben szerepl vector<int> vektorok mretnek nem felttlenl kell azonosnak lennie.

C.7.2. Tmbk
A beptett tmbk jelentik a C++-ban a legfbb hibaforrst, klnsen ha tbbdimenzis tmbk ksztsre hasznljuk fel azokat. Kezdk szmra ezek okozzk a legtbb keveredst is, ezrt amikor lehet, hasznljuk helyettk a vector, list, valarray, string stb. osztlyokat. A tbbdimenzis tmbket tmbk tmbjeknt brzolhatjuk. Egy 3x5-s mret tmbt az albbi mdon vezethetnk be:
int ma[3][5]; // 3 tmb, mindegyikben 5 int elem

Forrs: http://www.doksi.hu

1132

Fggelkek s trgymutat

Az ma adatszerkezetet a kvetkezkppen tlthetjk fel:


void init_ma() { for (int i = 0; i<3; i++) { for (int j = 0; j<5; j++) ma[i][j] = 10*i+j; } }

brval:

ma:

00 01 02 03 04 10 11 12 13 14 20 21 22 23 24

Az ma tmb egyszeren 15 egsz rtket trol, melyeket gy rnk el, mintha 3 darab 5 elem tmbrl lenne sz. Teht a memriban nincs olyan objektum, amely magt az ma mtrixot jelenti, csak az nll elemeket troljuk. A 3 s 5 dimenzirtkek csak a forrsban jelennek meg. Amikor ilyen programot runk, neknk kell valahogy emlkeznnk az rtkekre. Az ma tmbt pldul a kvetkez kdrszlet segtsgvel rathatjuk ki:
void print_ma() { for (int i = 0; i<3; i++) { for (int j = 0; j<5; j++) cout << ma[i][j] << '\t'; cout << '\n'; } }

Az a vesszs jells, amit nhny nyelv a tbbdimenzis tmbk kezelshez biztost, a C++-ban nem hasznlhat, mert a vessz (,) a mveletsorozat-opertor (6.2.2). Szerencsre a fordt a legtbb hibra figyelmeztethet:
int bad[3,5]; int good[3][5]; int ouch = good[1,4]; int nice = good[1][4]; // hiba: a vessz nem megengedett konstans kifejezsben // 3 tmb, mindegyikben 5 int elem // hiba: int kezdrtke nem lehet int* tpus (good[1,4] // jelentse good[4], ami int* tpus) // helyes

Forrs: http://www.doksi.hu

C. Technikai rszletek

1133

C.7.3. Tbbdimenzis tmbk tadsa


Kpzeljnk el egy fggvnyt, mellyel egy tbbdimenzis tmbt akarunk feldolgozni. Ha a dimenzikat fordtskor ismerjk, nincs problma:
void print_m35(int m[3][5]) { for (int i = 0; i<3; i++) { for (int j = 0; j<5; j++) cout << m[i][j] << '\t'; cout << '\n'; } }

A tbbdimenzis tmbknt megjelen mtrixokat egyszeren mutatknt adjuk t (nem kszl rla msolat, 5.3). Az els dimenzi rdektelen az egyes elemek helynek meghatrozsakor, csak azt rgzti, hogy az adott tpusbl (esetnkben az int[5]-bl) hny darab ll egyms utn (esetnkben 3). Pldul, nzzk meg az ma elbbi brzolst. Megfigyelhetjk, hogy ha csak annyit tudunk, hogy a msodik dimenzi 5, akkor is brmely ma[i][5] elem helyt meg tudjuk llaptani. Ezrt az els dimenzit tadhatjuk kln paramterknt:
void print_mi5(int m[][5], int dim1) { for (int i = 0; i<dim1; i++) { for (int j = 0; j<5; j++) cout << m[i][j] << '\t'; cout << '\n'; } }

A problms eset az, ha mindkt dimenzit paramterknt akarjuk tadni. A nyilvnval megolds nem mkdik:
void print_mij(int m[][], int dim1, int dim2) { // nem gy viselkedik, // ahogy legtbben gondolnnk // meglepets!

for (int i = 0; i<dim1; i++) { for (int j = 0; j<dim2; j++) cout << m[i][j] << '\t'; cout << '\n'; }

Forrs: http://www.doksi.hu

1134

Fggelkek s trgymutat

Elszr is, az m[][] deklarci hibs, mert egy tbbdimenzis tmb esetben a msodik dimenzit ismernnk kell ahhoz, hogy az elemek helyt meg tudjuk hatrozni. Msrszt, az m[i][j] kifejezst (teljesen szablyosan) *(*(m+i)+j)-knt rtelmezi a fordt, ami ltalban nem azt jelenti, amit a programoz ki akart vele fejezni. A helyes megolds a kvetkez:
void print_mij(int* m, int dim1, int dim2) { for (int i = 0; i<dim1; i++) { for (int j = 0; j<dim2; j++) cout << m[i*dim2+j] << '\t'; // zavaros cout << '\n'; } }

A print_mij() fggvnyben az elemek elrshez hasznlt kifejezs egyenrtk azzal, amit a fordt llt el, amikor ismeri az utols dimenzit. Ennek a fggvnynek a meghvshoz a mtrixot egyszer mutatknt adjuk t:
int main() { int v[3][5] = { {0,1,2,3,4}, {10,11,12,13,14}, {20,21,22,23,24} }; print_m35(v); print_mi5(v,3); print_mij(&v[0][0],3,5);

Figyeljk meg az utols sorban a &v[0][0] kifejezs hasznlatt. A v[0] szintn megfelelne, mert az elbbivel teljesen egyenrtk, a v viszont tpushibt okozna. Az ilyen csnya s krlmnyes programrszleteket jobb elrejteni. Ha kzvetlenl tbbdimenzis tmbkkel kell foglalkoznunk, prbljuk klnvlasztani a vele foglalkoz programrszleteket. Ezzel leegyszersthetjk a kvetkez programoz dolgt, akinek majd a programhoz hozz kell nylnia. Ha kln megadunk egy tbbdimenzis tmb tpust s benne egy megfelel indexel opertort, a legtbb felhasznlt megkmlhetjk attl, hogy az adatok fizikai elrendezsvel kelljen foglalkoznia (22.4.6). A szabvnyos vector (16.3) osztlyban ezek a problmk mr nem jelentkeznek.

Forrs: http://www.doksi.hu

C. Technikai rszletek

1135

C.8. Ha kevs a memria


Ha komolyabb alkalmazst ksztnk, gyakran tbb memrira lenne szksgnk, mint amennyi rendelkezsnkre ll. Kt mdszer van arra, hogy tbb helyet prseljnk ki: 1. Egyetlen bjtban tbb kisebb objektumot trolhatunk. 2. Ugyanazt a terletet klnbz idpontokban klnbz objektumok trolsra hasznlhatjuk. Az elbbit a mezk, az utbbit az unik segtsgvel valsthatjuk meg. Ezekrl az eszkzkrl a korbbiakban mr volt sz. A mezket s az unikat szinte csak optimalizlshoz hasznljuk, ami gyakran a memria adott rendszerbeli felptsn alapul, gy a program nem lesz ms rendszerre tvihet. Teht rdemes ktszer is meggondolnunk, mieltt ezeket a szerkezeteket hasznljuk. Gyakran jobb megoldst jelent, ha az adatokat mskpp kezeljk, pldul tbb dinamikusan lefoglalt terletet (6.2.6) s kevesebb elre lefoglalt (statikus) memrit hasznlunk.

C.8.1. Mezk
Gyakran rettent pazarlsnak tnik, hogy egy binris vltozt pldul egy ki/be kapcsolt egy teljes bjt (char vagy bool) felhasznlsval brzolunk, de ez a legkisebb egysg, amelyet a memriban kzvetlenl megcmezhetnk a C++ segtsgvel (5.1). Viszont, ha a struct tpusban mezket hasznlunk, lehetsg van arra, hogy tbb ilyen kis vltozt egyetlen csomagba fogjunk ssze. Egy tag akkor vlik mezv, ha utna megadjuk az ltala elfoglalt bitek szmt. Nvtelen mezk is megengedettek. Ezek a C++ szempontjbl nincsenek hatssal az elnevezett mezk jelentsre, segtsgkkel azonban pontosan lerhatunk bizonyos gpfgg adatokat:
struct PPN { unsigned int PFN : 22; int : 3; unsigned int CCA : 3; bool nonreachable : 1; bool dirty : 1; bool valid : 1; bool global : 1; }; // R6000 fizikai lapszm // lapkeret-szm // nem hasznlt // Cache Coherency Algorithm

Ezzel a pldval a mezk msik legfontosabb felhasznlsi terlett is bemutattuk: valamilyen kls (nem C++) szerkezet rszeit is pontosan elnevezhetjk velk. A mezk egsz vagy felsorol tpusak (4.1.1). Egy meznek nem lehet lekrdezni a cmt, viszont ettl

Forrs: http://www.doksi.hu

1136

Fggelkek s trgymutat

eltekintve teljesen tlagos vltozknt kezelhetjk. Figyeljk meg, hogy egy bool tpus rtket itt tnyleg egy bittel brzolhatunk. Egy opercis rendszer magjban vagy egy hibakeresben a PPN tpus a kvetkezkppen hasznlhat:
void part_of_VM_system(PPN* p) { // ... if (p->dirty) { } } // ... // a tartalom megvltozott // lemezre msols p->dirty = 0;

Meglep mdon, ha tbb vltozt mezk segtsgvel egyetlen bjtba srtnk ssze, akkor sem felttlenl takartunk meg memrit. Az adatterlet cskken, de az ilyen adatok kezelshez szksges kd a legtbb szmtgpen jelentsen nagyobb. A programok mrete jelentsen cskkenthet azzal, hogy a bitmezkknt brzolt binris adatokat char tpusra alaktjuk, radsul egy char vagy int elrse ltalban sokkal gyorsabb, mint a bitmezk elrse. A mezk csak egy knyelmes rvidtst adnak a bitenknti logikai mveletekhez (6.2.4), hogy egy gpi sz rszeibe egymstl fggetlenl tudjunk adatokat rni, illetve onnan kiolvasni.

C.8.2. Unik
Az uni olyan struct, melyben minden tag ugyanarra a cmre kerl a memriban, gy az uni csak annyi terletet foglal, mint a legnagyobb tagja. Termszetesen az uni egyszerre csak egy tagjnak rtkt tudja trolni. Kpzeljnk el pldul egy szimblumtbla-bejegyzst, amely egy nevet s egy rtket trol:
enum Type { S, I }; struct Entry { char* name; Type t; char* s; int i; };

// s hasznlata, ha t==S // i hasznlata, ha t==I

void f(Entry* p) { if (p->t == S) cout << p->s; // ... }

Forrs: http://www.doksi.hu

C. Technikai rszletek

1137

Az s s az i tagot sohasem fogjuk egyszerre hasznlni, gy feleslegesen pazaroltuk a memrit. Ezen a problmn knnyen segthetnk a union adatszerkezet segtsgvel:
union Value { char* s; int i; };

A rendszer nem tartja nyilvn, hogy a union ppen melyik rtket trolja, teht ezt tovbbra is a programoznak kell megtennie:
struct Entry { char* name; Type t; Value v; };

// v.s hasznlata, ha t==S; v.i hasznlata, ha t==I

void f(Entry* p) { if (p->t == S) cout << p->v.s; // ... }

Sajnos a union bevezetse arra knyszert minket, hogy az egyszer s helyett a v.s kifejezst hasznljuk. Ezt a problmt a nvtelen uni segtsgvel kerlhetjk el, amely egy olyan uni, amelynek nincs neve, gy tpust sem ad meg, csak azt biztostja, hogy tagjai ugyanarra a memriacmre kerljenek:
struct Entry { char* name; Type t; union { char* s; int i; }; }; void f(Entry* p) { if (p->t == S) cout << p->s; // ... }

// s hasznlata, ha t==S // i hasznlata, ha t==I

gy az Entry szerkezetet hasznl programrszleteken nem kell vltoztatnunk. A union tpus olyan felhasznlsakor, amikor egy rtket mindig abbl a tagbl olvasunk vissza, amelyiken keresztl rtuk, csak optimalizlst hajtunk vgre. Az uni ilyen jelleg felhasznl-

Forrs: http://www.doksi.hu

1138

Fggelkek s trgymutat

st azonban nem mindig knny biztostani s a helytelen hasznlat nagyon kellemetlen hibkat okozhat. A hibk elkerlse rdekben az unit befoglalhatjuk egy olyan egysgbe, amely biztostja, hogy az rtk tpusa s a hozzfrs mdja egymsnak megfelel lesz (10.6[20]). Az unit nha szndkosan helytelenl hasznljuk, valamifle tpustalakts rdekben. Ezt a lehetsget ltalban azok a programozk hasznljk, akik az explicit tpustalaktst nem tmogat nyelven is rnak programokat. Ott az ilyen csalsra tnyleg szksg van. Az albbi szerkezettel az int s az int* tpus kztti talaktst prbljuk megvalstani, egyszer bitenknti egyenrtksg felttelezsvel:
union Fudge { int i; int* p; }; int* cheat(int i) { Fudge a; a.i = i; return a.p; }

// hibs hasznlat

Ez valjban nem is igazi talakts. Bizonyos gpeken az int s az int* nem ugyanannyi helyet foglal, ms gpeken az egsz rtkeknek nem lehet pratlan cme. Teht az uni ilyen felhasznlsa nagyon veszlyes s nem is vihet t ms rendszerre, radsul ugyanerre a feladatra van egy egyszerbb, biztonsgosabb s hordozhat megolds: a meghatrozott (explicit) tpustalakts (6.2.7). Az unit idnknt szndkosan a tpustalakts elkerlsre hasznljk. Pldul szksgnk lehet arra, hogy a Fudge segtsgvel megllaptsuk, hogy a 0 mutatt hogyan brzolja a rendszer:
int main() { Fudge foo; foo.p = 0; cout << "A 0 mutat egsz rtke: " << foo.i << '\n'; }

C.8.3. Unik s osztlyok


A bonyolultabb unik esetben gyakran elfordul, hogy egyes tagok jval nagyobbak, mint a rendszeresen hasznlt tagok. Mivel a union mrete legalbb akkora, mint a legnagyobb tagja, sok helyet elpazarolhatunk gy. A pazarlst ltalban elkerlhetjk, ha uni helyett szrmaztatott osztlyokat hasznlunk.

Forrs: http://www.doksi.hu

C. Technikai rszletek

1139

Egy konstruktorral, destruktorral s msol mvelettel rendelkez osztly nem tartozhat egy uni tag tpusba (10.4.12), hiszen a fordt nem tudja, melyik tagot kell trlnie.

C.9. Memriakezels
A C++-ban a memria kezelsre hrom alapvet mdszer ll rendelkezsnkre: Statikus memria: Ebben az esetben az objektum szmra az sszeszerkeszt (linker) foglal helyet a program teljes futsi idejre. Statikus memriban kapnak helyet a globlis s nvtr-vltozk, a static adattagok (10.2.4) s a fggvnyekben szerepl static vltozk (7.1.2). Azok az objektumok, melyek a statikus memriban kapnak helyet, egyszer jnnek ltre s a program vgig lteznek. Cmk a program futsa kzben nem vltozik meg. A statikus objektumok a szlakat (osztott cm memriaterleteket prhuzamosan) hasznl programokban problmt jelenthetnek, mert a megfelel elrshez zrolsokat kell alkalmaznunk. Automatikus memria: Ezen a terleten a fggvnyparamterek s helyi vltozk jnnek ltre. A fggvny vagy blokk minden egyes lefutsakor sajt pldnyok jnnek ltre az ilyen vltozkbl. Ezt a tpus memrit automatikusan foglalja le s szabadtja fel a rendszer, ezrt kapta az automatikus memria nevet. Az automatikus memrira gyakran hivatkozunk gy, hogy az elemek a veremben helyezkednek el. Ha felttlenl hangslyozni akarjuk ezt a tpus helyfoglalst, a C++-ban az auto kulcsszt hasznlhatjuk. Szabad tr: Errl a terletrl a program brmikor kzvetlenl ignyelhet memrit s brmikor felszabadthatja az itt lefoglalt terleteket (a new, illetve a delete opertor segtsgvel). Amikor a programnak jabb terletekre van szksge a szabad trbl, a new opertorral ignyelhet az opercis rendszertl. A szabad trra gyakran hivatkozunk dinamikus memria vagy kupac (heap) nven is. A szabad tr a program teljes lettartama alatt csak nhet, mert az ilyen terleteket a program befejezdsig nem adjuk vissza az opercis rendszernek, gy ms programok azokat nem hasznlhatjk. A programoz szemszgbl az automatikus s a statikus tr egyszeren, biztonsgosan s automatikusan kezelhet. Az rdekes krdst a szabad tr kezelse jelenti. A new segtsgvel knnyen foglalhatunk terleteket, de ha nincs kvetkezetes stratgink a memria felszabadtsra (a memria visszaadsra a szabad tr kezeljnek), a memria elbbutbb elfogy, fleg ha programunk elg sokig fut.

Forrs: http://www.doksi.hu

1140

Fggelkek s trgymutat

A legegyszerbb mdszer, ha automatikus objektumokat hasznlunk a szabad trban lev objektumok kezelshez. Ennek megfelelen a trolkat sokszor gy valstjk meg, hogy a szabad trban lev elemekre mutatkat lltanak (25.7). Az automatikus String-ek (11.12) pldul a szabad trban lev karaktersorozatokat kezelik s automatikusan felszabadtjk a terletet, amikor kikerlnek a hatkrbl. Az sszes szabvnyos trol (16.3, 17. fejezet, 20. fejezet, 22.4) knyelmesen megvalsthat ilyen formban.

C.9.1. Automatikus szemtgyjts


Ha ez az egyszer megolds nem felel meg ignyeinknek, akkor hasznlhatunk valamilyen memriakezel rendszert, amely megkeresi az olyan objektumokat, melyekre mr nincs hivatkozs, s az ezek ltal lefoglalt memriaterletet elrhetv teszi j objektumok szmra. Ezt a rendszert ltalban automatikus szemtgyjt algoritmusnak vagy egyszeren szemtgyjtnek (garbage collection) nevezzk. A szemtgyjts alaptlete az, hogy ha egy objektumra mr sehonnan sem hivatkozunk a programbl, arra a ksbbiekben sem fogunk, teht az ltala lefoglalt memriaterletet biztonsgosan felszabadthatjuk vagy odaadhatjuk ms objektumoknak:
void f() { int* p = new int; p = 0; char* q = new char; }

Itt a p=0 rtkads trli az egyetlen hivatkozst, ami a p ltal eddig meghatrozott int objektumra mutatott, gy ezen int rtk terlett odaadhatjuk egy j objektumnak. A char tpus objektum teht mr szerepelhet ugyanezen a memriaterleten, ami azt jelenti, hogy a q ugyanazt az rtket kapja, mint eredetileg a p. A szabvny nem kveteli meg, hogy minden nyelvi vltozat tartalmazza a szemtgyjtst, de egyre gyakrabban hasznljk az olyan terleteken, ahol a C++-ban a szabad tr egyni kezelse kltsgesebb, mint a szemtgyjts. Amikor a kt mdszer kltsgeit sszehasonltjuk, figyelembe kell vennnk a futsi idt, a memria-felhasznlst, a megbzhatsgot, a hordozhatsgot, a programozs pnzbeli kltsgeit s a teljestmny megjsolhatsgt is.

Forrs: http://www.doksi.hu

C. Technikai rszletek

1141

C.9.1.1. Rejtett mutatk Mikor nevezhetnk egy objektumot hivatkozs nlklinek? Vizsgljuk meg pldul az albbi programrszletet:
void f() { int* p = new int; long i1 = reinterpret_cast<long>(p)&0xFFFF0000; long i2 = reinterpret_cast<long>(p)&0x0000FFFF; p = 0; // #1 pont: nem ltezik az int-re hivatkoz mutat p = reinterpret_cast<int*>(i1|i2); // itt az int-re ismt ltezik mutat

Ha egy programban bizonyos mutatkat idnknt nem mutatknt trolunk, akkor rejtett mutatkrl beszlnk. Esetnkben azt a mutatt, amit eredetileg a p vltoz trolt, elrejtettk az i1 s az i2 egszekben. A szemtgyjt rendszertl azonban nem vrhatjuk el, hogy kezelni tudja a rejtett mutatkat, teht amikor a program a #1 ponthoz r, a szemtgyjt felszabadtja az int rtk szmra lefoglalt terletet. Valjban az ilyen programok helyes mkdst mg az olyan rendszerekben sem garantlhatjuk, ahol nem hasznlunk szemtgyjt algoritmust, mert a reinterpret_cast hasznlata egszek s mutatk kztti tpustalaktsra mg a legjobb esetben is megvalsts-fgg. Az olyan union objektumok, melyek lehetv teszik mutatk s nem mutatk trolst is, kln problmt jelentenek a szemtgyjtk szmra. ltalban nincs md annak megllaptsra, hogy egy ilyen szerkezet ppen mutatt trol-e:
union U { int* p; int i; }; // uni mutat s nem mutat taggal

void f(U u, U u2, U u3) { u.p = new int; u2.i = 999999; u.i = 8; // ... }

Forrs: http://www.doksi.hu

1142

Fggelkek s trgymutat

A biztonsgos felttelezs az, hogy minden rtk mutat, ami egy ilyen union objektumban megjelenik. Egy kellen okos szemtgyjt ennl kicsit jobb megoldst is adhat. Pldul szreveheti, hogy (az adott rendszerben) int rtkek nem helyezhetk el pratlan memriacmen s semmilyen objektumot nem hozhatunk ltre olyan kicsi memriacmen, mint a 8. Ezek figyelembevtelvel a szemtgyjt algoritmusnak nem kell feltteleznie, hogy a 999999 vagy a 8 cmen rtelmes objektum szerepel, amit az f() esetleg felhasznl.

C.9.1.2. A delete Ha rendszernk automatikus szemtgyjtst hasznl, a memria felszabadtshoz nincs szksg a delete s a delete[] opertorokra, teht a szemtgyjtst hasznl programozknak nem kell ezeket hasznlniuk. A memria felszabadtsn kvl azonban a delete s a delete[] destruktorok meghvsra is hasznlatos. Ha szemtgyjt algoritmust hasznlunk, a
delete p;

utasts meghvja a p ltal mutatott objektum destruktort (ha van ilyen), viszont a memria jrafelhasznlst elhalaszthatjuk addig, amg a memriaterletre szksg lesz. Ha sok objektumot egyszerre szabadtunk fel, cskkenthetjk a memria feltredezdsnek mrtkt (C.9.1.4) Ez a megolds rtalmatlann teszi azt az egybknt rendkvl veszlyes hibt is, hogy ugyanazt az objektumot ktszer semmistjk meg, amikor a destruktor csak a memria trlst vgzi. Az olyan objektumok elrse, melyeket mr trltnk, szoks szerint nem meghatrozhat eredmnnyel jr.

C.9.1.3. Destruktorok Amikor a szemtgyjt algoritmus egy objektumot meg akar semmisteni, kt lehetsg kzl vlaszthat: 1. Meghvja az adott objektum destruktort (ha az ltezik). 2. Felttelezi, hogy nyers memriaterletrl van sz s nem hv meg destruktort. Alaprtelmezs szerint a szemtgyjt algoritmus a msodik lehetsget hasznlja, mert a new segtsgvel ltrehozott, de a delete opertorral nem trlt objektumokat nem kell megsemmistennk. Teht a szemtgyjt algoritmust bizonyos szempontbl vgtelen m-

Forrs: http://www.doksi.hu

C. Technikai rszletek

1143

ret memria utnzsnak tekinthetjk. Lehetsg van arra is, hogy a szemtgyjt algoritmust gy valstsuk meg, hogy a destruktor az algoritmus ltal bejegyzett objektumokra fusson le. A bejegyzsre azonban nincs szabvnyos megolds. Figyeljnk arra, hogy az objektumokat mindig olyan sorrendben kell megsemmistennk, hogy az egyik objektum destruktora soha ne hivatkozzon olyan objektumra, amelyet korbban mr trltnk. A programoz segtsge nlkl ilyen sorrendet a szemtgyjt algoritmusok nagyon ritkn kpesek betartani.

C.9.1.4. A memria feltredezdse Ha sok klnbz mret objektumot hozunk ltre, illetve semmistnk meg, a memria feltredezdik. Ez azt jelenti, hogy a memrit olyan kis darabokban hasznljuk, amelyek tl kicsik ahhoz, hogy hatkonyan kezelhetk legyenek. Ennek oka az, hogy a memriakezel rendszerek nem mindig tallnak pontosan olyan mret memriadarabot, amilyenre egy adott objektumnak ppen szksge van. Ha a kelletnl nagyobb terletet hasznlunk, a megmarad memriatredk mg kisebb lesz. Ha egy ilyen egyszer memriakezelvel programunk elg sokig fut, nem ritka, hogy akr a rendelkezsre ll memria felt olyan memriatredkek tltik ki, melyek tl kis mretek ahhoz, hogy hasznlhatk legyenek. A memria-feltredezds problmjnak kezelsre szmos mdszert dolgoztak ki. A legegyszerbb megolds, hogy csak nagyobb memriadarabok lefoglalst tesszk lehetv s minden ilyen darabban azonos mret objektumokat trolunk (15.3, 19.4.2). Mivel a legtbb helyfoglalst kis mret objektumok ignylik (pldul a trolk elemei), ez a mdszer nagyon hatkony lehet. Az eljrst akr egy nll memriafoglal is automatikusan megvalsthatja. A memriatredezdst mindkt esetben tovbb cskkenthetjk, ha az sszes nagyobb memriadarabot azonos mretre (pldul a lapmretre) lltjuk, gy ezek lefoglalsa sem okoz tredezdst. A szemtgyjt rendszerek kt f tpusba tartoznak: 1. A msol szemtgyjtk az objektumokat thelyezik a memriban, ezzel egyestik a feltredezett memria darabjait. 2. A konzervatv szemtgyjtk gy prblnak helyet foglalni az objektumoknak, hogy a memriatredezds a lehet legkisebb legyen. A C++ szemszgbl a konzervatv szemtgyjtk a gyakoribbak, mert rendkvl nehz (st a komoly programokban valsznleg lehetetlen) az objektumokat gy thelyezni, hogy minden mutatt helyesen tlltsunk. A konzervatv szemtgyjtk azt is lehetv teszik, hogy a C++ programrszletek egyttmkdjenek akr a C nyelven kszlt program-

Forrs: http://www.doksi.hu

1144

Fggelkek s trgymutat

rszletekkel is. A msol szemtgyjtket tbbnyire csak olyan nyelvekben valstjk meg, melyek az objektumokat mindig kzvetve mutatkon vagy hivatkozsokon keresztl rik el. (Ilyen nyelv pldul a Lisp vagy a Smalltalk.) Az olyan nagyobb programok esetben, melyeknl a memriafoglal s a lapkezel rendszer kztti kapcsolattarts, illetve a msols mennyisge jelents, az jabb konzervatv szemtgyjtk legalbb olyan hatkonynak tnnek, mint a msol szemtgyjtk. Kisebb programok esetben gyakran az a legjobb megolds, ha egyltaln nem hasznlunk szemtgyjtt fleg a C++ esetben, ahol az objektumok tbbsgt termszetszerleg automatikusan kezelhetjk.

C.10. Nvterek
Ebben a pontban olyan aprsgokat vizsglunk meg a nvterekkel kapcsolatban, melyek csupn technikai rszleteknek tnnek, de a vitk sorn, illetve a komoly programokban gyakran felsznre kerlnek.

C.10.1. Knyelem s biztonsg


A using deklarcik egy j nevet vezetnek be a helyi hatkrbe, a using utastsok (direktvk) azonban nem gy mkdnek, csak elrhetv teszik a megadott hatkrben szerepl neveket:
namespace X { int i, j, k; } int k; void f1() { int i = 0; using namespace X; i++; j++; k++; ::k++; X::k++; }

// az X-beli nevek elrhetv ttele // helyi i // X::j // hiba: X::k vagy globlis k ? // a globlis k // az X-beli k

Forrs: http://www.doksi.hu

C. Technikai rszletek

1145

void f2() { int i = 0; using X::i; using X::j; using X::k; i++; j++; k++;

// hiba: i ktszer deklarlt f2()-ben // elfedi a globlis k nevet // X::j // X::k

A helyileg (nll deklarcival vagy using deklarcival) bevezetett nevek elrejtik az ugyanilyen nven szerepl nem helyi neveket; a fordt pedig minden hibs tlterhelst a deklarci helyn jelez. Figyeljk meg az f1() fggvnyben a k++ utasts ktrtelmsgt. A globlis nevek nem lveznek elsbbsget azokkal a nevekkel szemben, melyek globlis hatkrben elrhet nvterekbl szrmaznak. Ez jelents mrtkben cskkenti a vletlen nvkeveredsek lehetsgt s biztostja, hogy ne a globlis nvtr beszennyezsvel jussunk elnykhz. Amikor using utastsok segtsgvel olyan knyvtrakat tesznk elrhetv, melyek sok nevet vezetnek be, komoly knnytst jelent, hogy a felhasznlatlan nevek tkzse nem okoz hibt. A globlis nvtr ugyanolyan, mint brmelyik msik, tlagos nvtr. A globlis nvtr egyetlen klnlegessge, hogy egy r vonatkoz explicit minsts esetben nem kell kirnunk a nevt. Teht a ::k jells azt jelenti, hogy keresd meg a k nevet a globlis nvtrben vagy a globlis nvtrhez using utastssal csatolt nvterekben, mg az X::k jelentse: keresd meg a k nevet az X nvtrben, vagy a benne using utastssal megemltett nvterekben (8.2.8). Remljk, hogy a nvtereket hasznl j programokban radiklisan cskkenni fog a globlis nevek szma a hagyomnyos C s C++ programokhoz viszonytva. A nvterek szablyait kifejezetten gy talltk ki, hogy ne adjanak elnyket a globlis nevek lusta felhasznlinak azokkal szemben, akik vigyznak r, hogy a globlis hatkrt ne szennyezzk ssze.

Forrs: http://www.doksi.hu

1146

Fggelkek s trgymutat

C.10.2. Nvterek egymsba gyazsa


A nvterek egyik nyilvnval felhasznlsi terlete az, hogy deklarcik s defincik valamilyen rtelemben zrt halmazt egyetlen egysgbe fogjuk ssze:
namespace X { // sajt deklarcik }

A deklarcik listja ltalban tartalmazni fog jabb nvtereket is. Teht gyakorlati okokbl is szksg van a nvterek egymsba gyazsra, azon egyszer ok mellett, hogy ha valamilyen rendszerben nem kifejezetten kptelensg az egymsba gyazs megvalstsa, akkor illik azt lehetv tennnk:
void h(); namespace X { void g(); // ... namespace Y { void f(); void ff(); // ... } }

Az albbi jellsek a szoksos, hatkrkre vonatkoz, illetve minstsi szablyokbl kvetkeznek:


void X::Y::ff() { f(); g(); h(); } void X::g() { f(); Y::f(); } void h() { f(); Y::f(); X::f(); X::Y::f(); }

// hiba: nincs f() X-ben // rendben

// hiba: nincs globlis f() // hiba: nincs globlis Y // hiba: nincs f() X-ben // rendben

Forrs: http://www.doksi.hu

C. Technikai rszletek

1147

C.10.3. Nvterek s osztlyok


A nvterek elnevezett hatkrk, az osztlyok pedig tpusok, melyeket egy elnevezett hatkrrel hatrozunk meg, ami azt rja le, hogy a tpus objektumait hogyan kell ltrehozni s kezelni. Teht a nvtr egyszerbb fogalom, mint az osztly, s idelis esetben az osztlyokat gy is meghatrozhatjuk, mint tovbbi lehetsgeket biztost nvtereket. A meghatrozs azonban csak majdnem pontos, ugyanis a nvtr nyitott szerkezet (8.2.9.3), mg az osztly teljesen zrt. A klnbsg abbl addik, hogy az osztlyoknak objektumok szerkezett kell lerniuk, ez pedig nem engedi meg azt a szabadsgot, amit a nvterek nyjtanak. Ezenkvl a using deklarcik s using utastsok csak nagyon korltozott esetekben hasznlhatk az osztlyokra (15.2.2). Ha csak a nevek egysgbe zrsra van szksgnk, a nvterek jobban hasznlhatk, mint az osztlyok, mert ekkor nincs szksgnk az osztlyok komoly tpusellenrzsi lehetsgeire s az objektumksztsi mdszerekre; az egyszerbb nvtr-kezelsi elvek is elegendek.

C.11. Hozzfrs-szablyozs
Ebben a pontban a hozzfrs-szablyozs olyan vonatkozsaira mutatunk pldkat, melyekrl a 15.3 pontban nem volt sz.

C.11.1. Tagok elrse


Vizsgljuk meg az albbi deklarcit:
class X { // az alaprtelmezs privt: int priv; protected: int prot; public: int publ; void m(); };

Forrs: http://www.doksi.hu

1148

Fggelkek s trgymutat

Az X::m() fggvnynek korltlan hozzfrse van a tagokhoz:


void X::m() { priv = 1; prot = 2; publ = 3; }

// rendben // rendben // rendben

A szrmaztatott osztlyok tagjai a public s protected tagokat rhetik el (15.3):


class Y : public X { void mderived(); }; void Y::mderived() { priv = 1; // hiba: priv privt prot = 2; // rendben: prot vdett s mderived() az Y szrmaztatott osztly tagja publ = 3; // rendben: publ nyilvnos }

A globlis fggvnyek csak a nyilvnos tagokat ltjk:


void f(Y* p) { p->priv = 1; p->prot = 2; p->publ = 3; }

// hiba: priv privt // hiba: prot vdett s f() se nem bart, se nem X vagy Y tagja // rendben: publ nyilvnos

C.11.2. Alaposztlyok elrse


A tagokhoz hasonlan az alaposztlyokat is bevezethetjk private, protected vagy public minstssel:
class X { public: int a; // ... }; class Y1 : public X { }; class Y2 : protected X { }; class Y3 : private X { };

Forrs: http://www.doksi.hu

C. Technikai rszletek

1149

Mivel az X nyilvnos alaposztlya az Y1 osztlynak, brmely fggvny szabadon (s automatikusan) talakthat egy Y1* rtket X* tpusra s elrheti az X osztly nyilvnos tagjait is:
void f(Y1* py1, Y2* py2, Y3* py3) { X* px = py1; // rendben: X nyilvnos alaposztlya Y1 osztlynak py1->a = 7; // rendben px = py2; py2->a = 7; px = py3; py3->a = 7; // hiba: X vdett alaposztlya Y2 osztlynak // hiba // hiba: X privt alaposztlya Y3 osztlynak // hiba

Vegyk az albbi deklarcikat:


class Y2 : protected X { }; class Z2 : public Y2 { void f(Y1*, Y2*, Y3*); };

Mivel X vdett alaposztlya Y2-nek, csak Y2 tagjai s bart (friend) osztlyai, fggvnyei, illetve Y2 leszrmazottainak (pldul Z2-nek) tagjai s ezek bart elemei alakthatjk t (automatikusan) az Y2* tpus rtket X*-ra, s csak ezek frhetnek hozz az X osztly public s protected tagjaihoz:
void Z2::f(Y1* py1, Y2* py2, Y3* py3) { X* px = py1; // rendben: X nyilvnos alaposztlya Y1 osztlynak py1->a = 7; // rendben px = py2; py2->a = 7; px = py3; py3->a = 7; // rendben: X vdett alaposztlya Y2-nek, // s Z2 osztly Y2-bl szrmazik // rendben // hiba: X privt alaposztlya Y3 osztlynak // hiba

Vgl vizsgljuk meg az albbit:


class Y3 : private X { void f(Y1*, Y2*, Y3*); };

Forrs: http://www.doksi.hu

1150

Fggelkek s trgymutat

Az X az Y3-nak privt alaposztlya, gy kizrlag Y3 tagjai s bartai alakthatjk t (automatikusan) az Y3* rtkeket X*-ra, s csak azok rhetik el az X nyilvnos s vdett tagjait:
void Y3::f(Y1* py1, Y2* py2, Y3* py3) { X* px = py1; // rendben: X nyilvnos alaposztlya Y1 osztlynak py1->a = 7; // rendben px = py2; py2->a = 7; px = py3; py3->a = 7; // hiba: X vdett alaposztlya Y2 osztlynak // hiba // rendben: X privt alaposztlya Y3-nak, s Y3::f() Y3 tagja // rendben

C.11.3. Tagosztlyok elrse


A tagosztlyok tagjainak nincs klnleges jogosultsguk a befoglal osztly tagjainak elrsre s ugyangy a befoglal osztly tagjai sem rendelkeznek klnleges hozzfrssel a begyazott osztly tagjaihoz. Minden a szoksos szablyok (10.2.2) szerint mkdik:
class Outer { typedef int T; int i; public: int i2; static int s; class Inner { int x; T y; // hiba: Outer::T privt public: void f(Outer* p, int v); }; }; int g(Inner* p);

void Outer::Inner::f(Outer* p, int v) { p->i = v; // hiba: Outer::i privt p->i2 = v; // rendben: Outer::i2 nyilvnos }

Forrs: http://www.doksi.hu

C. Technikai rszletek

1151

int Outer::g(Inner* p) { p->f(this,2); // rendben: Inner::f() nyilvnos return p->x; // hiba: Inner::x privt }

Ennek ellenre gyakran hasznos, ha a begyazott osztly elrheti a begyaz osztly tagjait. Ezt gy rhetjk el, ha a tagosztlyt friend minstssel vezetjk be:
class Outer { typedef int T; int i; public: class Inner; friend class Inner;

// a tagosztly elzetes bevezetse // elrsi jog Outer::Inner szmra

};

class Inner { int x; T y; // rendben: Inner egy "bart" public: void f(Outer* p, int v); };

void Outer::Inner::f(Outer* p, int v) { p->i = v; // rendben: Inner egy "bart" }

C.11.4. Bartok
A friend minsts hatsa nem rkldik s nem is tranzitv:
class A { friend class B; int a; }; class B { friend class C; };

Forrs: http://www.doksi.hu

1152

Fggelkek s trgymutat

class C { void f(A* p) { p->a++; } }; class D : public B { void f(A* p) { p->a++; } };

// hiba: C nem bartja A-nak, br A bartjnak bartja

// hiba: D nem bartja A-nak, br A bartjbl szrmazik

C.12. Adattagokra hivatkoz mutatk


Termszetesen a tagokra hivatkoz mutatk (15.5) fogalma az adattagokra, valamint a rgztett paramterekkel s visszatrsi rtkkel rendelkez fggvnyekre vonatkozik:
struct C { const char* val; int i; void print(int x) { cout << val << x << '\n'; } int f1(int); void f2(); C(const char* v) { val = v; } }; typedef void (C::*PMFI)(int); typedef const char* C::*PM; void f(C& z1, C& z2) { C* p = &z2; PMFI pf = &C::print; PM pm = &C::val; z1.print(1); (z1.*pf)(2); // C osztly egy int paramterrel meghvhat // tagfggvnyre hivatkoz mutat // C osztly egy char* tpus adattagjra // hivatkoz mutat

Forrs: http://www.doksi.hu

C. Technikai rszletek

1153

z1.*pm = "nv1 "; p->*pm = "nv2 "; z2.print(3); (p->*pf)(4); pf = &C::f1; pf = &C::f2; pm = &C::i; pm = pf; // hiba: nem megfelel visszatrsi tpus // hiba: nem megfelel paramtertpus // hiba: nem megfelel tpus // hiba: nem megfelel tpus

A fggvnyekre hivatkoz mutatk tpust a rendszer ugyangy ellenrzi, mint brmely ms tpust.

C.13. Sablonok
A sablon osztlyok azt hatrozzk meg, hogyan hozhat ltre egy osztly, ha a szksges sablonparamtereket megadjuk. A sablon fggvnyek ehhez hasonlan azt rgztik, hogyan kszthetnk el egy konkrt fggvnyt a megadott sablonparamterekkel. Teht a sablonok tpusok s futtathat kdrszletek ltrehozshoz hasznlhatk fel. Ez a nagy kifejezer azonban nhny bonyodalmat okoz. A legtbb problma abbl addik, hogy ms krnyezet ll rendelkezsnkre a sablon meghatrozsakor s felhasznlsakor.

C.13.1. Statikus tagok


Az osztlysablonoknak is lehetnek static tagjaik. Ezekbl a tagokbl minden, a sablonbl ltrehozott osztly sajt pldnnyal rendelkezik. A statikus tagokat kln kell meghatroznunk, de egyedi cl vltozatokat is kszthetnk bellk:
template<class T> class X { // ... static T def_val; static T* new_X(T a = def_val); }; template<class T> T X<T>::def_val(0,0); template<class T> T* X<T>::new_X(T a) { /* ... */ }

Forrs: http://www.doksi.hu

1154

Fggelkek s trgymutat

template<> int X<int>::def_val<int> = 0; template<> int* X<int>::new_X<int>(int i) { /* ... */ }

Ha egy objektumot vagy fggvnyt a sablonbl ltrehozott sszes osztly sszes pldnyban kzsen szeretnnk hasznlni, bevezethetnk egy alaposztlyt, amely nem sablon:
struct B { static B* nil; }; // kzs nullpointer minden B-bl szrmaztatott osztlynak

template<class T> class X : public B { // ... }; B* B::nil = 0;

C.13.2. Bartok s sablonok


Ms osztlyokhoz hasonlan a sablon is tartalmazhat friend osztlyokat s fggvnyeket. Vizsgljuk meg pldul a 11.5 pontban szerepl Matrix s Vector osztlyt. ltalban mindkt osztlyt sablonknt valstjuk meg:
template<class T> class Matrix; template<class T> class Vector { T v[4]; public: friend Vector operator* <> (const Matrix<T>&, const Vector&); // ... }; template<class T> class Matrix { Vector<T> v[4]; public: friend Vector<T> operator* <> (const Matrix&, const Vector<T>&); // ... };

A bart fggvny neve utn szerepl <> jel nem hagyhat el, mert ez jelzi, hogy a bart egy sablon fggvny. A <> jel nlkl a rendszer nem sablon fggvnyt felttelezne. A fenti bevezets utn a szorzs opertort gy hatrozhatjuk meg, hogy a Vector s a Matrix tagjaira kzvetlenl hivatkozunk:

Forrs: http://www.doksi.hu

C. Technikai rszletek

1155

template<class T> Vector<T> operator* <> (const Matrix<T>&, const Vector<T>&) { // ... m.vi[i] s v.v[i] hasznlata az elemek kzvetlen elrshez }

A bart nincs hatssal arra a hatkrre, melyben a sablon osztlyt meghatroztuk, sem arra, melyben a sablon osztlyt felhasznljuk. Ehelyett a bart fggvnyeket s opertorokat paramtertpusaik alapjn tallja meg a rendszer (11.2.4, 11.5.1). A tagfggvnyekhez hasonlan a bart fggvnyekbl is csak akkor kszl pldny, ha meghvjuk azokat.

C.13.3. Sablonok, mint sablonparamterek


Gyakran hasznos, ha sablonparamterknt osztlyok vagy objektumok helyett sablonokat adunk t:
template<class T, template<class> class C> class Xrefd { C<T> mems; C<T*> refs; // ... }; Xrefd<Entry,vector> x1; Xrefd<Record,set> x2; // az Entry kereszthivatkozsokat vektorban troljuk // a Record kereszthivatkozsokat halmazban troljuk

Ahhoz, hogy egy sablont sablonparamterknt hasznljunk, meg kell adnunk az ltala vrt paramtereket, a paramterknt hasznlt sablon sablonparamtereit viszont nem kell ismernnk. Sablonokat ltalban akkor hasznlunk sablonparamterknt, ha klnbz paramtertpusokkal akarjuk azokat pldnyostani (fent pldul a T s a T* tpussal). Teht a sablon tagjainak deklarciit egy msik sablon fogalmaival fejezzk ki, de ez utbbi sablont paramterknt szeretnnk megadni, hogy a felhasznl vlaszthassa meg tpust. Ha a sablonnak egy trolra van szksge azon elemek trolshoz, melyek tpust sablonparamterknt kapja meg, gyakran egyszerbb a troltpust tadni (13.6, 17.3.1). Sablonparamterek csak sablon osztlyok lehetnek.

Forrs: http://www.doksi.hu

1156

Fggelkek s trgymutat

C.13.4. Sablon fggvnyek paramtereinek levezetse


A fordt kpes arra, hogy levezessen egy tpus- (T vagy TT) vagy nem tpus sablonparamtert (I) egy olyan fggvnysablon-paramterbl, melyet a kvetkez szerkezetek valamelyikvel lltottunk el:
T T* tpus[I] TT<T> T tpus::* T (*)(paramterek) tpus (tpus::*)(paramterek_TI) T (tpus::*)(paramterek_TI) const T volatile T T& T[konstans_kifejezs] osztlysablon_nv<T> osztlysablon_nv<I> T<I> T<> T T::* tpus T::* tpus (T::*)(paramterek) T (tpus::*)(paramterek) T (T::*)(paramterek_TI) tpus (T::*)(paramterek_TI) tpus (*)(paramterek_TI)

Ebben a gyjtemnyben a param_T1 egy olyan paramterlista, melybl egy T vagy egy I meghatrozhat a fenti szablyok tbbszri alkalmazsval, mg a param egy olyan paramterlista, amely nem tesz lehetv kvetkeztetst. Ha nem minden paramter kvetkeztethet ki ezzel a megoldssal, a hvs tbbrtelmnek minsl:
template<class T, class U> void f(const T*, U(*)(U)); int g(int); void h(const char* p) { f(p,g); // T char, U int f(p,h); // hiba: U nem vezethet le }

Ha megnzzk az f() els hvsnak paramtereit, knnyen kikvetkeztethetjk a sablonparamtereket. Az f() msodik meghvsban viszont lthatjuk, hogy a h() nem illeszthet az U(*)(U) mintra, mert paramtere s visszatrsi rtke klnbz. Ha egy sablonparamter egynl tbb fggvnyparamterbl is kikvetkeztethet, akkor mindegyiknek ugyanazt az eredmnyt kell adnia, ellenkez esetben hibazenetet kapunk:
template<class T> void f(T i, T* p); void g(int i) { f(i,&i); f(i,"Vigyzat!"); }

// rendben // hiba, tbbrtelm: T int vagy T const char?

Forrs: http://www.doksi.hu

C. Technikai rszletek

1157

C.13.5. A typename s a template


Az ltalnostott (generikus) programozs tovbbi egyszerstse s mg ltalnosabb ttele rdekben a standard knyvtr troli szabvnyos fggvnyeket s tpusokat biztostanak (16.3.1):
template<class T> class vector { public: typedef T value_type; typedef T* iterator; iterator begin(); iterator end(); }; // ...

template<class T> class list { class link { /* ... */ }; public: typedef link* iterator; iterator begin(); iterator end(); }; // ...

Ez arra sztnz minket, hogy az albbi formt hasznljuk:


template<class C> void f(C& v) { C::iterator i = v.begin(); // formai hiba }

Sajnos a fordt nem gondolatolvas, gy nem tudja, hogy a C::iterator egy tpus neve. Bizonyos esetekben egy okosabb fordt kitallhatja, hogy egy nevet tpusnvnek szntunke vagy valami egszen msnak (pldul fggvny- vagy sablonnvnek), de ez ltalban nem lehetsges. Figyeljk meg pldul az albbit:
int y; template<class T> void g(T& v) { T::x(y); // fggvnyhvs vagy vltoz-deklarci? }

Forrs: http://www.doksi.hu

1158

Fggelkek s trgymutat

Felttlenl igaz-e, hogy ez esetben a T::x egy fggvny, melyet az y paramterrel hvtunk meg? Ugyanezt a sort tekinthetjk az y vltoz deklarcijnak is, ahol a T::x egy tpus s a zrjelek csak felesleges s termszetellenes (de nem tiltott) jelek. Teht el tudunk kpzelni olyan krnyezetet, melyben X::x(y) egy fggvnyhvs, mg az Y::x(y) egy deklarci. A megolds rendkvl egyszer: ha mst nem mondunk, az azonostkrl a rendszer azt felttelezi, hogy olyasvalamire hivatkoznak, ami nem tpus s nem sablon. Ha azt akarjuk kifejezni, hogy egy azonostt tpusknt kell rtelmezni, a typename kulcsszt kell hasznlnunk:
template<class C> void h(C& v) { typename C::iterator i = v.begin(); // ... }

A typename kulcsszt a minstett nv el rva azt fejezhetjk ki, hogy a megadott elem egy tpus. Ebbl a szempontbl szerepe hasonlt a struct s a class szerepre. A typename kulcsszra mindig szksg van, ha a tpus neve egy sablonparamtertl fgg:
void k(vector<T>& v) { vector<T>::iterator i = v.begin(); typename vector<T>::iterator i = v.begin(); // ... }

// formai hiba: a "typename" hinyzik // rendben

Ebben az esetben a fordt esetleg kpes lenne megllaptani, hogy az iterator a vector sablon minden pldnyosztlyban egy tpusnevet ad meg, de a szabvny ezt nem kveteli meg. Teht, ha egy fordt ilyesmire kpes, akkor az nem szabvnyos szolgltats s nem tekinthet hordozhat nyelvkiterjesztsnek. Az egyetlen krnyezet, ahol a fordtnak feltteleznie kell, hogy egy sablon-paramtertl fgg azonost tpusnv, az a nhny helyzet, amikor a nyelvtan csak tpusneveket enged meg. Ilyenek pldul az alap-meghatrozsok (A.8.1). A sablondeklarcikban a typename a class kulcssz szinonimjaknt is hasznlhat:
template<typename T> void f(T);

Mivel a kt vltozat kztt nincs rdemi klnbsg, de a kperny mindig kicsinek bizonyul, n a rvidebb vltozatot ajnlom:
template<class T> void f(T);

Forrs: http://www.doksi.hu

C. Technikai rszletek

1159

C.13.6. Sablonok, mint minstk


A typename kulcssz bevezetsre azrt volt szksg, mert hivatkozhatunk olyan adattagokra is, melyek tpusok, s olyanokra is, melyek nem azok. Ugyanilyen problmt jelent a sablontagok nevnek megklnbztetse ms tagoktl. Vizsgljuk meg pldul egy ltalnos memriakezel osztly egy lehetsges fellett:
class Memory { // valamilyen memriafoglal public: template<class T> T* get_new(); template<class T> void release(T&); // ... }; template<class Allocator> void f(Allocator& m) { int* p1 = m.get_new<int>(); // formai hiba: 'int' a 'kisebb mint' opertor utn int* p2 = m.template get_new<int>(); // explicit minsts // ... m.release(p1); // sablonparamter levezetse: nem kell explicit minst m.release(p2); }

A get_new()fggvny explicit minstsre van szksg, mert a sablonparamtert nem lehet levezetni. Esetnkben a template eltagra van szksg ahhoz, hogy a fordtnak (s az emberi olvasnak) elruljuk, hogy a get_new() egy sablontag, teht a minsts lehetsges a megadott elemtpussal. A template minsts nlkl formai hibt kapunk, mert a < jelet a fordt kisebb, mint opertorknt prblja meg rtelmezni. A template kulcsszval val minstsre ritkn van szksg, mert a legtbb esetben a sablonparamtereket a fordt ki tudja kvetkeztetni.

C.13.7. Pldnyosts
Ha adott egy sablon-meghatrozs s ezt a sablont hasznlni szeretnnk, a fordt feladata, hogy a megfelel programkdot ellltsa. Egy sablon osztlybl s a megadott sablonparamterekbl a fordtnak el kell lltania egy osztly meghatrozst s sszes olyan tagfggvnynek defincijt, melyet programunkban felhasznltunk. Egy sablon fggvny esetben a megfelel fggvnyt kell ellltani. Ezt a folyamatot nevezzk a sablon pldnyostsnak.

Forrs: http://www.doksi.hu

1160

Fggelkek s trgymutat

A ltrehozott osztlyokat s fggvnyeket egyedi cl (szakostott) vltozatoknak (specializciknak) nevezzk. Ha meg akarjuk klnbztetni a fordt ltal automatikusan ltrehozott vltozatokat a programoz ltal megadottaktl (13.5), akkor fordti (generlt) specializcikrl, illetve explicit specializcikrl beszlnk. Az explicit specializcikat nha felhasznli specializciknak nevezzk. Ahhoz, hogy a sablonokat bonyolultabb programokban is hatkonyan hasznlhassuk, meg kell rtennk, hogy a sablon-meghatrozsokban szerepl neveket hogyan kti a fordt konkrt deklarcikhoz s hogyan tudjuk forrsprogramunkat rendszerezni (13.7). Alaprtelmezs szerint a fordt olyan sablonokbl hoz ltre osztlyokat s fggvnyeket, melyeket a szoksos nvktsi szablyok szerint hasznltunk (C.13.8). Ez azt jelenti, hogy a programoznak nem kell megmondania, mely sablonok mely vltozatait kell elkszteni. Ez azrt fontos, mert a programoz ltalban nagyon nehezen tudja megmondani, hogy a sablonoknak pontosan mely vltozataira is van szksge. A knyvtrak gyakran olyan sablonokat hasznlnak, melyekrl a programoz mg nem is hallott, st az sem ritka, hogy egy, a programoz ltal egyltaln nem ismert sablont annak szmra szintn teljesen ismeretlen sablonparamterekkel pldnyostunk. ltalban ahhoz, hogy megkeressk az sszes olyan fggvnyt, melynek kdjt a fordtnak el kell lltania, az alkalmazsban s a knyvtrakban hasznlt sablonok tbbszri tvizsglsra van szksg. Az ilyen vizsglatok sokkal jobban illenek a szmtgphez, mint a programozhoz. Ennek ellenre bizonyos helyzetekben fontos, hogy a programoz pontosan megmondhassa, a fordtnak hol kell elhelyeznie a sablonbl ltrehozott programrszleteket (C.13.10). Ezzel a programoz pontosan szablyozhatja a pldny krnyezett. A legtbb fordtsi krnyezetben ez azt is jelenti, hogy pontosan rgztjk, mikor jjjn ltre a pldny. Az explicit pldnyosts pldul jl hasznlhat arra, hogy a fordtsi hibkat megjsolhat helyen s idben kapjuk, ne pedig ott s akkor, amikor az adott fordt gy dnt, hogy ltre kell hoznia egy pldnyt. Bizonyos krlmnyek kztt a tkletesen megjsolhat programkd-ltrehozs elengedhetetlenl fontos lehet.

C.13.8. Nvkts
Nagyon fontos, hogy a sablon fggvnyeket gy hatrozzuk meg, hogy a lehet legkisebb mrtkben fggjenek nem helyi adatoktl. Ennek oka az, hogy a sablonbl ismeretlen tpusok alapjn, ismeretlen helyen fog a fordt fggvnyt vagy osztlyt ltrehozni. Minden apr krnyezetfggsg eslyes arra, hogy a programoz szmra tesztelsi problmaknt jelentkezzen, pedig a programoz valsznleg egyltaln nem is akar tudni a sablon megvalstsnak rszleteirl. Az az ltalnos szably, miszerint amennyire csak lehet, el kell ke-

Forrs: http://www.doksi.hu

C. Technikai rszletek

1161

rlnnk a globlis nevek hasznlatt, a sablonok esetben mg elengedhetetlenebb. Ezrt a sablon-meghatrozsokat prbljuk annyira nllv tenni, amennyire csak lehet, s minden olyan elemet, amelyet egybknt esetleg globlis eszkzkkel valstannk meg, sablonparamterknt adjunk t (pl. traits a 13.4 s a 20.2.1 pontban). Ennek ellenre knytelenek vagyunk nhny nem helyi nevet is felhasznlni. Sokkal gyakrabban ksztnk pldul egymssal egyttmkd sablon fggvnyeket, mint egyetlen nagy, nll eljrst. Nha ezek a fggvnyek jl sszefoghatk egy osztlyba, de nem mindig. Idnknt a nem helyi fggvnyek hasznlata mellett dntnk. Jellemz plda erre a sort() fggvnyben szerepl swap() s less() eljrshvs (13.5.2). A standard knyvtr algoritmusai nagymret pldaknt tekinthetk (18. fejezet). A hagyomnyos nvvel s szereppel megvalstott fggvnyek (pldul a +, a *, a [] vagy a sort()) egy ms jelleg forrst jelentenek a sablon-meghatrozsokban lev nem helyi nevek hasznlatra. Vizsgljuk meg pldul az albbi programrszletet:
#include<vector> bool tracing; // ... template<class T> T sum(std::vector<T>& v) { T t = 0; if (tracing) cerr << "sum(" << &v << ")\n"; for (int i = 0; i<v.size(); i++) t = t + v[i]; return t; } // ... #include<quad.h> void f(std::vector<Quad>& v) { Quad c = sum(v); }

Az rtatlan kinzet sum() sablon fggvny a + opertortl fgg. Pldnkban a + mveletet a <quad.h> fejllomny hatrozza meg:
Quad operator+(Quad,Quad);

Forrs: http://www.doksi.hu

1162

Fggelkek s trgymutat

Fontos, hogy a sum() kifejtsekor semmilyen, a komplex szmokhoz kapcsold elem nem elrhet s a sum() fggvny megalkotja semmit sem felttelezhet a Quad osztlyrl. gy knnyen elfordulhat pldul, hogy a + opertort jval ksbb hatrozzuk meg, mint a sum() eljrst, mind a program szvegben, mind idben. Azt a folyamatot, melynek sorn a fordt megkeresi a sablonban elfordul nevek deklarcijt, nvktsnek (name binding) nevezzk. A sablonok nvktsnl a legnagyobb problmt az jelenti, hogy pldnyostskor hrom klnbz krnyezetet kell figyelembe venni s ezek nem vlaszthatk el egymstl pontosan. A hrom krnyezet a kvetkez: 1. A sablon meghatrozsnak krnyezete 2. A paramtertpus bevezetsnek krnyezete 3. A sablon felhasznlsi helynek krnyezete

C.13.8.1. Fgg nevek Amikor egy fggvny sablont meghatrozunk, biztosak szeretnnk lenni abban, hogy rendelkezsnkre ll a megfelel krnyezet, melyben az aktulis paramterek fogalmaival el tudjuk vgezni az adott feladatot, anlkl, hogy a felhasznlsi krnyezet elemeit vletlenl beptennk az eljrsba. A nyelv ennek megvalstst azzal segti, hogy a sablon defincijban felhasznlt neveket kt kategriba osztja: 1. A sablonparamterektl fgg nevek. Ezeket a neveket a pldnyosts helyn kti le a fordt (C.13.8.3). A sum() pldafggvnyben a + opertor meghatrozsa a pldnyostsi krnyezetben tallhat, mert operandusaiban felhasznlja a sablon tpusparamtert. 2. A sablonparamterektl fggetlen nevek. Ezeket a neveket a sablon meghatrozsnl kti le a fordt (C.13.8.2). A sum() pldafggvnyben a vector sablont a <vector> szabvnyos fejllomny hatrozza meg, a logikai tracing vltoz pedig akkor is a hatkrben van, amikor a fordt tallkozik a sum() fggvny meghatrozsval. Az N fgg a T sablonparamtertl kifejezs legegyszerbb defincija az lenne, hogy N tagja a T osztlynak. Sajnos ez nem mindig elegend: A Quad elemek sszeadsa (C.13.8) j ellenplda erre. Teht egy fggvnyhvst egy sablonparamtertl fggnek akkor neveznk, ha az albbi felttelek valamelyike teljesl: 1. Az aktulis paramter tpusa fgg egy T sablonparamtertl a tpuslevezetsi szablyok szerint. Pldul f(T(1)), f(t), f(g(t)) vagy f(&t), ha felttelezzk, hogy t tpusa T.

Forrs: http://www.doksi.hu

C. Technikai rszletek

1163

2. A meghvott fggvnynek van egy olyan formlis paramtere, amely fgg a T tpustl, a tpuslevezetsi szablyok szerint (13.3.1). Pldul f(T), f(list<T>&) vagy f(const T*). Alapjban vve azt mondhatjuk, hogy a meghvott fggvny akkor fgg egy sablonparamtertl, ha a konkrt s formlis paramtereire nzve nyilvnvalan fgg tle. Egy olyan hvs, melyben a fggvny egyik paramternek tpusa vletlenl ppen megfelel az aktulis sablonparamternek, nem jelent fggsget:
template<class T> T f(T a) { return g(1); // hiba: nincs g() a hatkrben s g(1) nem fgg T-tl } void g(int); int z = f(2);

Az nem szmt, hogy az f(2) hvsban a T ppen az int tpust jelli s a g() paramtere is ilyen tpus. Ha a g(1) hvst fggnek tekintennk, a sablon-meghatrozs olvasjnak a fggvny jelentse teljesen rthetetlen lenne. Ha a programoz azt akarja, hogy a g(int) fggvny fusson le, akkor a g(int)-et be kell vezetnie az f() kifejtse eltt, hogy a g(int) fggvny elrhet legyen az f() feldolgozsakor. Ez teljesen ugyanaz a szably, mint a nem sablon fggvnyek esetben. A fggvnyneveken kvl a vltozk, tpusok, konstansok stb. neve is fgg, ha tpusuk fgg a sablonparamtertl:
template<class T> void fct(const T& a) { typename T::Memtype p = a.p; // p s Memtype fgg T-tl cout << a.i << ' ' << p->j; // i s j fgg T-tl }

C.13.8.2. Kts meghatrozskor Amikor a fordt egy sablon meghatrozsval tallkozik, megllaptja, mely nevek fggk (C.13.8.1). Ha egy nv fgg, deklarcijnak megkeresst el kell halasztanunk a pldnyostsig (C.13.8.3).

Forrs: http://www.doksi.hu

1164

Fggelkek s trgymutat

Azoknak a neveknek, melyek nem fggnek sablonparamterektl, a sablon meghatrozsakor (defincijnl) elrhetnek kell lennik (4.9.4):
int x; template<class T> T f(T a) { x++; // rendben y++; // hiba: nincs y a hatkrben s y nem fgg T-tl return a; } int y; int z = f(2);

Ha a fordt tall megfelel deklarcit, mindenkppen azt fogja hasznlni, fggetlenl attl, hogy ksbb esetleg jobb deklarcit is tallhatna hozz:
void g(double); template<class T> class X : public T { public: void f() { g(2); } // g(double) meghvsa // ... }; void g(int); class Z { }; void h(X<Z> x) { x.f(); }

Amikor a fordt elkszti az X<Z>::f() fggvnyt, nem a g(int) fggvnyt fogja hasznlni, mert azt az X utn vezettk be. Az nem szmt, hogy az X sablont nem hasznljuk a g(int) bevezetse eltt. Ehhez hasonlan egy fggetlen hvst sem trthet el egy alaposztly:
class Y { public: void g(int); }; void h(X<Y> x) { x.f(); }

Forrs: http://www.doksi.hu

C. Technikai rszletek

1165

Az X<Y>::f() most is a g(double) fggvnyt fogja meghvni. Ha a programoz az alaposztly g() fggvnyt akarja hasznlni, az f() meghatrozst a kvetkezkppen kell megfogalmaznia:
template<class T> class XX : public T { void f() { T::g(2); } // T::g() meghvsa // ... };

Ez termszetesen annak az alapszablynak a megjelense, hogy a sablon defincijnak a lehet legnllbbnak kell lennie (C.13.8).

C.13.8.3. Kts pldnyostskor A sablon minden klnbz sablonparamter-halmazzal val felhasznlsakor j pldnyostsi pontot hatrozunk meg. A pldnyostsi pont helye a legkzelebbi globlis vagy nvtr-hatkrben van, kzvetlenl a felhasznlst tartalmaz deklarci eltt:
template<class T> void f(T a) { g(a); } void g(int); void h() { extern g(double); f(2); }

Esetnkben az f<int>() megfelel pldnya kzvetlenl a h() eltt jn ltre, teht az f() fggvnyben meghvott g() a globlis g(int) lesz, nem pedig a helyi g(double). A pldnyostsi pont meghatrozsbl kvetkezik, hogy a sablonparamtereket nem kthetjk helyi nevekhez vagy osztlytagokhoz:
void f() { struct X { /* ... */ }; vector<X> v; // ... }

// helyi szerkezet // hiba: helyi szerkezet nem hasznlhat sablonparamterknt

Forrs: http://www.doksi.hu

1166

Fggelkek s trgymutat

Ugyangy a sablonban hasznlt minstetlen neveket sem kthetjk helyi nvhez. A minstetlen nevek akkor sem fognak egy osztly tagjaihoz ktdni, ha a sablont elszr ebben az osztlyban hasznljuk. A helyi nevek elkerlse nagyon fontos, ha nem akarunk szmtalan, makrszer problmval szembekerlni:
template<class T> void sort(vector<T>& v) { sort(v.begin(),v.end()); // standard knyvtrbeli sort() hasznlata } class Container { vector<int> v; // ... public: void sort() { sort(v); } // ... }; // elemek // elemek rendezse // a sort(vector<int>&) meghvsa a Container::sort() helyett

Ha a sort(vector<T>&) a sort() fggvnyt az std::sort() jellssel hvta volna meg, az eredmny ugyanez lett volna, de program sokkal olvashatbb lenne. Ha egy nvtrben meghatrozott sablon pldnyostsi pontja egy msik nvtrben tallhat, a nvktsnl mindkt nvtr nevei rendelkezsre llnak. A fordt szoks szerint a tlterhelsi szablyokat hasznlja arra, hogy megllaptsa, melyik nvtr nevt kell hasznlnia (8.2.9.2). Figyeljnk r, hogy ha egy sablont tbbszr hasznlunk ugyanazokkal a sablonparamterekkel, mindig j pldnyostsi pontot hatrozunk meg. Ha a fggetlen neveket a klnbz helyeken klnbz nevekhez ktjk, programunk helytelen lesz. Az ilyen hibt nagyon nehz szrevenni egy alkalmazsban, fleg ha a pldnyostsi pontok klnbz fordtsi egysgekben vannak. A legjobb, ha a nvktssel jr problmkat kikerljk azzal, hogy a lehet legkevesebb nem helyi nevet hasznljuk a sablonban s fejllomnyok segtsgvel biztostjuk, hogy mindenhol a megfelel krnyezet lljon a sablon rendelkezsre.

C.13.8.4. Sablonok s nvterek Amikor egy fggvnyt meghvunk, annak deklarcijt a fordt akkor is megtallhatja, ha az nincs is az aktulis hatkrben. Ehhez az kell, hogy a fggvnyt ugyanabban a nvtrben vezessk be, mint valamelyik paramtert (8.2.6). Ez nagyon fontos a sablon-megha-

Forrs: http://www.doksi.hu

C. Technikai rszletek

1167

trozsokban meghvott fggvnyek szempontjbl, mert ez a szolgltats teszi lehetv, hogy a fgg fggvnyeket a fordt a pldnyosts kzben megtallja. A sablon egyedi cl vltozata a pldnyosts tetszleges pontjn ltrejhet (C.13.8.3), de a pldnyostst kveten az adott fordtsi egysgben, esetleg egy olyan fordtsi egysgben is, mely kifejezetten az egyedi cl vltozatok ltrehozshoz kszlt. Ebbl hrom nyilvnval mdszer alakthat ki az egyedi cl vltozatok elksztshez: 1. Akkor hozzuk ltre azokat, amikor els meghvsukkal tallkozunk. 2. A fordtsi egysg vgn ltrehozzuk az sszeset, amelyre az adott fordtsi egysgben szksg van. 3. Miutn a program sszes fordtsi egysgt megvizsgltuk, a programban hasznlt sszes egyedi cl vltozatot egyszerre ksztjk el. Mindhrom mdszernek vannak elnyei s htrnyai, ezrt gyakran ezen lehetsgek valamilyen prostst hasznljk. A fggetlen nevek ktst mindenkppen a sablon meghatrozsakor vgzi el a fordt. A fgg nevek ktshez kt dolgot kell megvizsglni: 1. A sablon meghatrozsakor a hatkrben lv neveket 2. A fgg hvsok paramtereinek nvterben szerepl neveket (a globlis fggvnyeket gy tekintjk, hogy a beptett tpusok nvterben szerepelnek) Pldul:
namespace N { class A { /* ... */ }; } char f(A);

char f(int); template<class T> char g(T t) { return f(t); } char c = g(N::A()); // N::f(N::A) meghvst okozza

Itt az f(t) egyrtelmen fgg, gy a definci feldolgozsakor nem kthetjk sem az f(int), sem az f(N::A) fggvnyhez. A g<N::A>(N::A) szakostsakor a fordt az N nvtrben keresi a meghvott f() fggvny meghatrozst s ott az N::f(N::A) vltozatot tallja meg.

Forrs: http://www.doksi.hu

1168

Fggelkek s trgymutat

A program hibs, ha klnbz eredmnyeket kaphatunk azzal, hogy klnbz pldnyostsi pontokat vlasztunk, vagy azzal, ha az egyedi cl vltozat ltrehozsakor hasznlt krnyezetekben a nvterek tartalmt megvltoztatjuk:
namespace N { class A { /* ... */ }; } char f(A,int);

template<class T, class T2> char g(T t, T2 t2) { return f(t,t2); } char c = g(N::A(),'a'); namespace N { void f(A,char); } // hiba: f(t) ms feloldsa is lehetsges // az N nvtr kibvtse (8.2.9.3)

Ha a pldnyosts pillanatban hozzuk ltre a szakostott vltozatot, az f(N::A,int) fggvny kerl meghvsra, viszont ha elksztst a fordtsi egysg vgre halasztjuk, a fordt az f(N::A,char) fggvnyt tallja meg elbb. Teht a g(N::A(),a) hvs helytelen. Nagyon rendetlen programozsi stlust mutat, ha egy tlterhelt fggvnyt kt deklarcija kztt hvunk meg. Egy nagyobb program esetben a programoz nem gyanakodna hibra, ebben az esetben azonban a fordt kpes szrevenni a tbbrtelmsget. Hasonl problmk jelentkezhetnek klnbz fordtsi egysgekben is, gy a hibk szlelse mr sokkal nehezebb vlik. Az egyes C++-vltozatoknak nem ktelessgk az ilyen tpus hibk figyelse. A fggvnyhvsok tbbfle feloldsi lehetsgnek problmja leggyakrabban akkor jelentkezik, amikor beptett tpusokat hasznlunk. Teht a legtbb hibt kiszrhetjk azzal, hogy a beptett tpusokat nagy krltekintssel hasznljuk a paramterekben. Szoks szerint a globlis fggvnyek csak mg bonyolultabb teszik a dolgokat. A globlis nvteret a beptett tpusok szintjnek tekinthetjk, gy a globlis fggvnyek felhasznlhatk olyan fgg fggvnyek lektsre is, melyeket beptett tpusokkal hvtunk meg:
int f(int); template<class T> T g(T t) { return f(t); } char c = g('a'); char f(char); // hiba: f(t) ms feloldsa is lehetsges

Forrs: http://www.doksi.hu

C. Technikai rszletek

1169

A g<char>(char) fggvny egyedi cl vltozatt elkszthetjk a pldnyostsi ponton is; ekkor az f(int) fggvnyt fogjuk hasznlni. Ha a vltozatot a fordtsi egysg vgn lltjuk el, az f(char) fggvny fut majd le. Teht a g(a) hvs hibs.

C.13.9. Mikor van szksg egyedi cl vltozatokra?


Egy sablon osztly egyedi cl vltozatt csak akkor kell ellltani, ha az adott osztlyra tnyleg szksg van. Teht amikor egy, az osztlyra hivatkoz mutatt adunk meg, az osztly tnyleges meghatrozsra mg nincs szksgnk:
class X; X* p; X a; // rendben: X meghatrozsra nincs szksg // hiba: X meghatrozsra szksg van

A sablon osztlyok meghatrozsakor ez a klnbsg fontos lehet. A sablon osztlyt mindaddig nem pldnyostjuk, amg arra nincs szksg:
template<class T> class Link { Link* suc; // rendben: Link meghatrozsra (mg) nincs szksg // ... }; Link<int>* pl; Link<int> lnk; // Link<int> pldnyostsra nincs szksg // most van szksg Link<int> pldnyostsra

A pldnyostsi pont az a hely, ahol a defincira elszr szksg van.

C.13.9.1. Sablon fggvnyek pldnyostsa A fordt a sablon fggvnyeket csak akkor pldnyostja, amikor a fggvnyt felhasznljuk. Ennek megfelelen egy sablon osztly pldnyostsa nem vonja maga utn sem sszes tagjnak pldnyostst, sem a sablon osztly deklarcijban szerepl tagok pldnyostst. Ez nagy rugalmassgot biztost a sablon osztlyok meghatrozsakor:
template<class T> class List { // ... void sort(); };

Forrs: http://www.doksi.hu

1170

Fggelkek s trgymutat

class Glob { /* nincs sszehasonlt mvelet */ }; void f(List<Glob>& lb, List<string>& ls) { ls.sort(); // lb mveleteinek hasznlata, kivve az lb.sort()-ot }

Itt a List<string>::sort() fggvnyt a fordt pldnyostja, de a List<Glob>::sort() fggvnyre nincs szksg. Ez egyrszt kevesebb kd ltrehozst teszi lehetv, msrszt megkml minket attl, hogy az egsz programot t kelljen szerveznnk. Ha a List<Glob>::sort() fggvnyt is ltrehozn a fordt, akkor a Glob osztlyban szerepelnie kellene az sszes olyan mveletnek, melyet a List::sort() felhasznl, a sort() fggvnyt ki kellene vennnk a List sablonbl, vagy a Glob objektumokat kellene valamilyen ms trolban trolnunk.

C.13.10. Explicit pldnyosts


A pldnyostst gy knyszerthetjk ki, ha a template kulcssz utn (melyet ez esetben nem kvet < jel) deklarljuk a kvnt egyedi cl vltozatot:
template class vector<int>; template int& vector<int>::operator[](int); template int convert<int,double>(double); // osztly // tag // fggvny

Teht egy sablon deklarcija a template< kifejezssel kezddik, mg egy pldnyostsi krelmet a template kulcsszval fejeznk ki. Figyeljk meg, hogy a template sz utn a teljes deklarci szerepel, nem elg csak a pldnyostani kvnt nevet lernunk:
template vector<int>::operator[]; template convert<int,double>; // formai hiba // formai hiba

A fggvnyparamterekbl levezethet sablonparamterek ugyangy elhagyhatk, mint a sablon fggvnyek meghvsakor (13.3.1):
template int convert<int,double>(double); template int convert<int>(double); // rendben (felesleges) // rendben

Amikor egy sablon osztlyt gy pldnyostunk, a tagfggvnyekbl is ltrejn egy pldny.

Forrs: http://www.doksi.hu

C. Technikai rszletek

1171

Az explicit pldnyosts klnbz ellenrzsek elvgzsre is felhasznlhat (13.6.2):


template<class T> class Calls_foo { void constraints(T t) { foo(t); } // ... }; // minden konstruktorbl meghvand

template class Calls_foo<int>; template Calls_foo<Shape*>::constraints();

// hiba: foo(int) nem meghatrozott // hiba: foo(Shape*) nem meghatrozott

A pldnyostsi krelmek hatsa az sszeszerkesztsi idre s az jrafordts hatkonysgra nzve jelents lehet. Arra is lttam mr pldt, hogy a sablon-pldnyostsok egyetlen fordtsi egysgbe val sszefogsval a fordtsi idt nhny rrl nhny percre sikerlt cskkenteni. Hibt jelent, ha ugyanarra az egyedi cl vltozatra kt meghatrozs is van. Nem szmt, hogy ezek felhasznl ltal megadottak (13.5), automatikusan ltrehozottak (C.13.7), vagy pldnyostsi krelemmel kszltek. A fordt nem kteles szrevenni a tbbszrs pldnyostst, ha az egyes vltozatok klnbz fordtsi egysgekben fordulnak el. Ez lehetv teszi a felesleges pldnyostsok elegns elkerlst, gy megszabadulhatunk azoktl a problmktl, melyek a tbb knyvtrt hasznl programokban az explicit pldnyostsbl szrmazhatnak (C.13.7). A szabvny azonban nem kveteli meg, hogy a megvalstsok elegnsak legyenek. A kevsb elegns megvalstsok hasznlinak rdemes elkerlnik a tbbszrs pldnyostst. Ha ezt a tancsot mgsem fogadjuk meg, programunk a legrosszabb esetben nem fog futni, de a programban nem kvetkezik be rejtett vltozs. A nyelv nem kveteli meg, hogy a felhasznlk explicit pldnyostst hasznljanak. Ez csupn olyan optimalizcis lehetsg, mellyel sajt kezleg szablyozhatjuk a fordts s sszeszerkeszts menett (C.13.7).

C.14. Tancsok
[1] A technikai rszletek helyett sszpontostsunk a programfejleszts egszre. C.1. [2] A szabvny szigor betartsa sem garantlja a hordozhatsgot. C.2. [3] Kerljk a nem meghatrozott helyzeteket (a nyelvi bvtsekben is). C.2.

Forrs: http://www.doksi.hu

1172

Fggelkek s trgymutat

[4] Legynk tisztban a megvalsts-fgg szolgltatsokkal. C.32. [5] A {, }, [, ], | s ! jelek helyett csak akkor hasznljunk kulcsszavakat s digrf jeleket, ha ezek nem elrhetek az adott rendszerben; trigrf karaktereket pedig csak akkor, ha a \ sem llthat el gpnkn. C.3.1. [6] Az egyszer adatkzls rdekben a programok lersra hasznljuk az ANSI karaktereket. C.3.3. [7] A karakterek szmmal jellse helyett lehetleg hasznljuk vezrlkarakter megfeleliket. C.3.2. [8] Soha ne hasznljuk ki a char tpus eljelessgt vagy eljel nlklisgt. C.3.4. [9] Ha ktsgeink vannak egy egsz literl tpusval kapcsolatban, hasznljunk uttagokat. C.4. [10] Kerljk az rtkveszt automatikus talaktsokat. C.6. [11] Hasznljuk a vector osztlyt a beptett tmbk helyett. C.7. [12] Kerljk a union tpus hasznlatt. C.8.2. [13] A nem ltalunk ksztett szerkezetek lersra hasznljunk mezket. C.8.1. [14] Figyeljnk a klnbz memriakezelsi stlusok elnyeire s htrnyaira. C.9. [15] Ne szennyezzk be a globlis nvteret. C.10.1. [16] Ha nem tpusra, hanem nll hatkrre (modulra) van szksgnk, osztlyok helyett hasznljunk nvtereket. C.10.3. [17] Sablon osztlyokban is megadhatunk static tagokat. C.13.1. [18] Ha egy sablonparamter tagtpusaira van szksgnk, az egyrtelmsg rdekben hasznljuk a typename kulcsszt. C.13.5. [19] Ha sablonparamterekkel val kifejezett minstsre van szksgnk, hasznljuk a template kulcsszt a sablon osztly tagjainak egyrtelm megadshoz. C.13.6. [20] A sablon meghatrozst gy fogalmazzuk meg, hogy a lehet legkevesebbet hasznljuk fel a pldnyostsi krnyezetbl. C.13.8. [21] Ha egy sablon pldnyostsa tl sokig tart, esetleg rdemes explicit pldnyostst alkalmaznunk. C.13.10. [22] Ha a fordts sorrendjnek pontosan megjsolhatnak kell lennie, explicit pldnyostsra lehet szksg. C.13.10.

Forrs: http://www.doksi.hu

D
Helyi sajtossgok
Ha Rmban jrsz, tgy gy, mint a rmaiak. A kulturlis eltrsek kezelse A locale osztly Nevestett loklok Loklok ltrehozsa Loklok msolsa s sszehasonltsa A global() s classic() loklok Karakterlncok sszehasonltsa A facet osztly Jellemzk elrse a loklokban Egyszer felhasznli facet-ek Szabvnyos facet-ek Karakterlncok sszehasonltsa Numerikus I/O Pnz I/O Dtum s id I/O Alacsonyszint idmveletek Egy Date osztly A karakterek osztlyozsa Karakterkd-talaktsok zenetkatalgusok Tancsok Gyakorlatok

D.1. A kulturlis eltrsek kezelse


A locale (lokl) a helyi sajtossgokat jelkpez objektum. Olyan kulturlis jellemzket tartalmaz, mint a karakterek trolsnak s a karakterlncok sszehasonltsnak mdja, illetve a szmok megjelensi formja a kimeneten. A helyi sajtossgok kre bvthet: a programoz a loklhoz tovbbi olyan jellemzket (facet; arculat, szempont) adhat, amelyeket a standard knyvtr nem tmogat kzvetlenl. Ilyenek pldul az irnytszmok vagy a telefonszmok. A locale elsdleges szerepe a standard knyvtrban a kimeneti adatfolyamban (ostream) lev adatok megjelentsi formjnak, illetve a bemeneti adatfolyamok (istream) ltal elfogadott informci formtumnak szablyozsa.

Forrs: http://www.doksi.hu

1174

Fggelkek s trgymutat

A 21.7 pontban mr trgyaltuk, hogyan mdosthatjuk az adatfolyamok formtumt; ez a fggelk azt rja le, hogy pthet fel jellemzkbl (facet) egy locale, illetve azt magyarzza el, hogyan befolysolja a lokl az adatfolyamot. Ismerteti a facet-ek definilsnak mdjt is, felsorolja a szabvnyos jellemzket, amelyekkel az adatfolyamok tulajdonsgai bellthatk s mdszereket mutat be mindezek ltrehozsra s hasznlatra. Az adat- s idbrzolst segt standard knyvtrbeli eszkzket a dtumok be- s kivitelnek trgyalsakor mutatjuk be. A loklok s jellemzk trgyalst a kvetkez rszekre bontottam: D.1 A kulturlis eltrsek loklokkal val brzolsa mgtt rejl alapgondolatok bemutatsa D.2 A locale osztly D.3 A facet osztly D.4 A szabvnyos jellemzk ttekintse s rszletes ismertetse: D.4.1 Karakterlncok sszehasonltsa D.4.2 Szmrtkek bevitele s kivitele D.4.3 Pnzrtkek bevitele s kivitele D.4.4 Dtum s id bevitele s kivitele D.4.5 A karakterek osztlyozsa D.4.6 Karakterkd-konverzik D.4.7 zenetkatalgusok A locale nem a C++ fogalma, a legtbb opercis rendszerben s felhasznli krnyezetben ltezik. A helyi sajtossgokon (terleti belltsokon) elvileg az adott rendszerben megtallhat valamennyi program osztozik, fggetlenl attl, hogy a programok milyen programnyelven rdtak. Ezrt a C++ standard knyvtrnak locale-jt gy tekinthetjk, mint amelynek segtsgvek a programok szabvnyos s hordozhat mdon frhetnek hozz olyan adatokhoz, melyek brzolsa a klnbz rendszerekben jelentsen eltr. gy a C++ locale kzs felletet biztost azon rendszeradatok elrshez, melyeket az egyes rendszerek ms s ms mdon trolnak.

D.1.1. A kulturlis eltrsek programozsa


Kpzeljk el, hogy olyan programot runk, melyet sok orszgban fognak hasznlni. Azt, hogy a programot olyan stlusban rjuk meg, ami ezt lehetv teszi, gyakran hvjk nemzetkzi tmogatsnak (internacionalizci; ez kihangslyozza, hogy a programot tbb orszgban hasznljk) vagy honostsnak (lokalizcinak, kihangslyozva a program helyi

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1175

viszonyokhoz igaztst). A program ltal kezelt adatok nmelyike a szoksoknak megfelelen klnbzkppen jelenik meg az egyes orszgokban. A problmt gy kezelhetjk, hogy a bemeneti/kimeneti eljrsok megrsnl ezt figyelembe vesszk:
void print_date(const Date& d) // kirs a megfelel formban { switch(where_am_I) // felhasznli jelzrtk { case DK: // pl. 7. marts 1999 cout << d.day() << ". " << dk_month[d.month()] << " " << d.year(); break; case UK: // pl. 7 / 3 / 1999 cout << d.day() << " / " << d.month() << " / " << d.year(); break; case US: // pl. 3/7/1999 cout << d.month() << "/" << d.day() << "/" << d.year(); break; // ... } }

Egy ilyen kddal a feladat megoldhat, de a kd elg csnya s csak kvetkezetes hasznlatval biztosthatjuk, hogy minden kimenet megfelelen igazodjon a helyi szoksokhoz. Ami mg ennl is rosszabb: ha jabb dtumformtummal szeretnnk kiegszteni, mdostanunk kell a kdot. Felmerlhet az tlet, hogy a problmt egy osztlyhierarchia ltrehozsval oldjuk meg (12.2.4). A Date-ben trolt adatok azonban fggetlenek a megjelents mdjtl, gy nem Date tpusok (pldul US_date, UK_date, s JP_date) rendszert kell ltrehoznunk, hanem a dtumok megjelentsre kell megadnunk tbbfle mdszert (mondjuk amerikai, brit s japn stlus kimenetet). Lsd mg: D.4.4.5. Az engedjk meg a felhasznlnak, hogy bemeneti/kimeneti fggvnyeket rjon s kezeljk azok a helyi sajtossgokat megkzeltsnek is vannak buktati: 1. Egy rendszerfejleszt programoz nem tudja knnyen, ms rendszerre tltethet mdon s hatkonyan mdostani a beptett tpusok megjelenst a standard knyvtr segtsge nlkl. 2. Egy nagy programban nem mindig lehet az sszes I/O mveletet (s minden olyan mveletet, amely a helyi sajtossgokhoz igaztva kszt el adatot I/Ohoz) megtallni. 3. A programot nha nem igazthatjuk az j szablyokhoz s mg ha lehetsges is lenne, jobban szeretnnk egy olyan megoldst, amely nem ignyel jrarst.

Forrs: http://www.doksi.hu

1176

Fggelkek s trgymutat

4. Pazarls lenne minden felhasznlval megterveztetni s elkszttetni az eltr helyi sajtossgokat kezel programelemeket. 5. A klnbz programozk klnflekppen kezelik a kulturlis eltrseket, ezrt a valjban ugyanazokkal az adatokkal dolgoz programok is klnbzni fognak, noha alapveten azonosnak kellene lennik. gy azoknak a programozknak, akik tbb forrsfjlban lv kdot tartanak karban, tbbfajta programozsi megkzeltst kell megtanulniuk, ami fraszt s hibalehetsget rejt magban. Kvetkezskppen a standard knyvtr a helyi sajtossgok kezelshez bvthet eljrsgyjtemnyt knl. Az iostream knyvtr (21.7) mind a beptett, mind a felhasznli tpusok kezelshez ezekre az eljrsokra tmaszkodik. Vegynk pldul egy egyszer ciklust, amely mrsek sorozatt vagy tranzakcik egy halmazt brzol (Date, double) prokat msol:
void cpy(istream& is, ostream& os) // (Date,double) adatfolyamot msol { Date d; double volume; while (is >> d >> volume) os << d << << volume << \n; }

Egy valdi program termszetesen csinlna valamit a rekordokkal s egy kicsit jobban trdne a hibakezelssel is. Hogyan kszthetnk ebbl egy olyan programot, amely a francia szoksoknak megfelel fjlt olvas be (ahol a magyarhoz hasonlan vesszt hasznlnak a tizedespont jellsre a lebegpontos szmokban; pldul a 12,5 jelentse tizenkett s fl) s az amerikai formnak megfelelen rja azt ki? Loklok s I/O mveletek meghatrozsval a cpy()-t hasznlhatjuk az talaktsra:
void f(istream& fin, ostream& fout, istream& fin2, ostream& fout2) { fin.imbue(locale("en_US")); // amerikai angol fout.imbue(locale("fr")); // francia cpy(fin,fout); // amerikai angol olvass, francia rs fin2.imbue(locale("fr")); // francia fout2.imbue(locale("en_US")); // amerikai angol cpy(fin2,fout2); // francia olvass, amerikai angol rs }

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1177

Ha adottak a kvetkez adatfolyamok,


Apr 12, 1999 1000.3 Apr 13, 1999 345.45 Apr 14, 1999 9688.321 ... 3 juillet 1950 10,3 3 juillet 1951 134,45 3 juillet 1952 67,9 ...

a program a kvetkezt fogja kirni:


12 avril 1999 1000,3 13 avril 1999 345,45 14 avril 1999 9688,321 ... July 3, 1950 10.3 July 3, 1951 134.45 July 3, 1952 67.9 ...

A fggelk tovbbi rsznek legjavt annak szenteljk, hogy lerjuk, milyen nyelvi tulajdonsgok teszik ezt lehetv, illetve hogy elmagyarzzuk, hogyan hasznljuk azokat. Jegyezzk meg, hogy a programozk tbbsgnek nem kell rszletekbe menen foglalkoznia a loklokkal vagy kifejezetten azokat kezel kdot rnia. Akik mgis megteszik, azok is leginkbb egy szabvnyos locale-t fognak elkeresni, hogy az adott adatfolyamnak elrjk annak hasznlatt (21.7). Azok az eljrsok azonban, melyekkel a loklokat ltrehozhatjuk s hasznlatukat egyszerv tehetjk, sajt programnyelvet alkotnak. Ha egy program vagy rendszer sikeres, olyanok is hasznlni fogjk, akiknek az ignye s zlse eltr attl, amire az eredeti tervezk s programozk szmtottak. A legtbb sikeres programot hasznlni fogjk azokban az orszgokban is, ahol a (termszetes) nyelvek s karakterkszletek eltrnek az eredeti tervezk s programozk ltal ismertektl. Egy program szleskr hasznlata a siker jele, ezrt a nyelvi s kulturlis hatrok kztt tvihet programok tervezse s rsa a sikerre val felkszlst jelenti. A nemzetkzi tmogats fogalma egyszer, a gyakorlati megszortsok azonban a locale objektumok elksztst meglehetsen bonyolultt teszik: 1. A loklok az olyan helyi sajtossgokat tartalmazzk, mint a dtumok megjelensi formja. A szablyok azonban egy adott kultrn bell is szmos apr s

Forrs: http://www.doksi.hu

1178

Fggelkek s trgymutat

2. 3. 4.

5.

nem rendszerezhet mdon eltrhetnek. A helyi szoksoknak semmi kzk a programnyelvekhez, ezrt egy programnyelv nem szabvnyosthatja azokat. A lokloknak bvthetnek kell lennik, mert nem lehetsges az sszes helyi sajtossgot felsorolni, amely minden C++ felhasznlnak fontos. A locale objektumokat olyan I/O mveletekben hasznljuk, melyeknl a futsi id igen fontos. A locale objektumoknak lthatatlannak kell lennik a legtbb programoz szmra, hiszen k anlkl szeretnk kihasznlni a megfelel dolgot vgz adatfolyam-bemenetet s -kimenetet, hogy pontosan ismernk annak felptst, megvalstst. A lokloknak elrhetnek kell lennik azok szmra, akik olyan eszkzket terveznek, amelyek helyi sajtossgoktl fgg adatokat kezelnek az adatfolyam I/O knyvtr keretein kvl.

Egy bemeneti s kimeneti mveleteket vgz program tervezsekor vlasztanunk kell, hogy a kimenet formtumt szoksos kddal vagy locale-ek felhasznlsval vezreljke. Az elbbi (hagyomnyos) megkzelts ott clszer, ahol biztostani tudjuk, hogy minden bemeneti mveletet knnyen t lehet alaktani a helyi sajtossgoknak megfelelen. Ha azonban a beptett tpusok megjelensnek kell vltoznia, ha klnbz karakterkszletekre van szksg, vagy ha a bemenetre/kimenetre vonatkoz szablyok halmazai kztt kell vlasztanunk, a locale objektumok hasznlata tnik sszerbbnek. A locale objektumok gynevezett facet-ekbl llnak, amelyek az egyes jellemzket (lebegpontos rtk kirsakor hasznlt elvlaszt karakter (decimal_point(), D.4.2), pnzrtk beolvassakor hasznlt formtum (moneypunct, D.4.3) stb.) szablyozzk. A facet egy, a locale::facet osztlybl szrmaz osztly objektuma (D.3); leginkbb gy kpzelhetjk el, hogy a locale facet-ek trolja (D.2, D.3.1).

D.2. A locale osztly


A locale osztly s a hozz tartoz szolgltatsok a <locale> fejllomnyban tallhatk:
class std::locale { public: class facet; class id; typedef int category;

// lokljellemzket tartalmaz tpus, D.3 // loklt azonost tpus, D.3 // facet-ek csoportostsra szolgl tpus

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1179

static const category // a tnyleges rtkek megvalstsfggek none = 0, collate = 1, ctype = 1<<1, monetary = 1<<2, numeric = 1<<3, time = 1<<4, messages = 1<<5, all = collate | ctype | monetary | numeric | time | messages; locale() throw(); locale(const locale& x) throw(); explicit locale(const char* p); ~locale() throw(); locale(const locale& x, const char* p, category c); // x msolata, plusz p-beli c facet-ek locale(const locale& x, const locale& y, category c); // x msolata, plusz y-beli c facet-ek template <class Facet> locale(const locale& x, Facet* f); // x msolata, plusz az f facet template <class Facet> locale combine(const locale& x); // *this msolata, // plusz az x-beli Facet const locale& operator=(const locale& x) throw(); bool operator==(const locale&) const; bool operator!=(const locale&) const; string name() const; // loklok sszehasonltsa // az adott lokl neve (D.2.1) // a globlis lokl msolata (D.2.1) // x msolata // a p nev lokl msolata (D.2.1)

template <class Ch, class Tr, class A> // karakterlncok sszehasonltsa // az adott lokl segtsgvel bool operator()(const basic_string<Ch,Tr,A>&, const basic_string<Ch,Tr,A>&) const; static locale global(const locale&); // a globlis lokl belltsa s visszatrs a rgivel static const locale& classic(); // a "klasszikus" C-stlus lokl private: // brzols };

A locale-ekre gy gondolhatunk, mint map<id,facet*>-ek felletre, vagyis valami olyasmire, ami lehetv teszi, hogy egy locale::id segtsgvel megtalljuk a megfelel objektumot, amelynek osztlya a locale::facet-bl szrmazik. A locale megvalstsa alatt az e gondolat alapjn elksztett (hatkony) szerkezeteket rtjk. Az elrendezs ilyesmi lesz:

Forrs: http://www.doksi.hu

1180

Fggelkek s trgymutat

collate<char>: locale: compare ( ) hash ( ) ...

numpunct<char>: decimal_point ( ) truename ( ) ...

Itt a collate<char> s a numpunct<char> a standard knyvtr facet-jei (D.4). Mint mindegyik facet, ezek is a locale::facet-bl szrmaznak. A locale-nek szabadon s olcsn msolhatnak kell lennie. Kvetkezskppen a locale-t majdnem mindig gy valstjk meg, mint egy lert arra a szakostott map<id,facet*>-re, amely a szolgltatsok legtbbjt tartalmazza. A lokl jellemzinek (a facet-eknek) gyorsan elrhetnek kell lennik, ezrt az egyedi cl map<id,facet*> a tmbkhz hasonl gyors hozzfrst kell, hogy nyjtson. A locale jellemzinek elrse a use_facet<Facet>(loc) jellssel trtnik (lsd D.3.1-et). A standard knyvtr a facet-ek gazdag vlasztkt nyjtja. A programoz ezeket logikai csoportokban kezelheti, mert a szabvnyos facet-ek kategrikat alkotnak (pl. numeric s collate, D.4). A programoz az egyes kategrikban lev jellemzket kicserlheti (D.4, D.4.2.1), de nem adhat meg j kategrikat. A kategria fogalma csak a standard knyvtrbeli facetekre vonatkozik, nem terjeszthet ki a felhasznli jellemzkre. Ezrt nem szksges, hogy egy facet kategriba tartozzon s sok felhasznli facet nem is tartozik ilyenbe. A loklokat messze a leggyakrabban az adatfolyamok bemeneti s kimeneti mveletinl hasznljuk, mg ha nem is tudunk rla. Minden istream s ostream rendelkezik sajt lokllal. Az adatfolyamok loklja a folyam ltrehozsnak pillanatban alaprtelmezs szerint a globlis locale (D.2.1) lesz. A folyam lokljt az imbue() (megtlts) mvelettel lehet belltani, a locale msolatt pedig a getloc() fggvnnyel kaphatjuk meg (21.6.3).

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1181

D.2.1. Nevestett loklok


A loklok msik locale-bl s jellemzkbl hozhatk ltre. A legegyszerbb egy mr ltez locale lemsolsa:
locale loc0; locale loc1 = locale(); locale loc2(""); msolata locale loc3("C"); locale loc4 = locale::classic(); locale loc5("POSIX"); // a "C" lokl msolata // a "C" lokl msolata // az adott fejlesztkrnyezet ltal meghatrozott // "POSIX" lokl msolata // az rvnyes globlis lokl msolata (D.2.3) // az rvnyes globlis lokl msolata (D.2.3) // a felhasznl ltal elnyben rszestett lokl

A locale(C) jelentst a szabvny klasszikus C loklknt hatrozza meg; ebben a knyvben vgig ezt a loklt hasznljuk. A tbbi locale neve a hasznlt C++-vltozattl fgg. A locale() a felhasznl ltal elnyben rszestett lokl, melyet a program vgrehajtsi krnyezetben a nyelven kvli eszkzk lltanak be. A legtbb opercis rendszer biztost valamilyen eszkzt a programok terleti belltsainak megadsra. A belltsra legtbbszr akkor kerl sor, amikor a felhasznl elszr tallkozik a rendszerrel. Egy olyan gpen pldul, ahol a rendszer alaprtelmezett nyelveknt az argentin spanyolt adtk meg, a locale() valsznleg a locale(es_AR)-t jelenti. Az egyik rendszerem gyors ellenrzse 51 megjegyezhet nvvel rendelkez loklt mutatott ki (pldul POSIX, de, en_UK, en_US, es, es_AR, fr, sv, da, pl, s iso_8859_1). A POSIX ltal ajnlott formtum: kisbets nyelvnv, amit nagybets orszgnv kvet (ez nem ktelez), valamint egy kdjelz (ez sem ktelez); pldul jp_JP.jit. Ezek a nevek azonban nem szabvnyostottak a klnbz platformok kztt. Egy msik rendszerben egyb locale nevek mellett a kvetkezket talltam: g, uk, us, s, fr, sw, s da. A C++ szabvny nem adja meg az orszgok s nyelvek locale-jt, br az egyes platformokra ltezhetnek szabvnyok. Kvetkezskppen, ha a programoz nevestett loklokat akar hasznlni, a rendszer dokumentcijra s tapasztalataira kell hagyatkoznia. ltalban clszer elkerlni a loklneveket jelz karakterlncok programszvegbe gyazst. A fjlnevek s rendszerllandk programban val szerepeltetse korltozza a program hordozhatsgt, a programot j krnyezetbe beilleszteni kvn programoz pedig gyakran arra knyszerl, hogy megkeresse ezeket az rtkeket, hogy mdosthassa azokat.

Forrs: http://www.doksi.hu

1182

Fggelkek s trgymutat

A loklnevek megemltse is hasonl kellemetlen kvetkezmnyekkel jr. Jobb, ha a loklokat kivesszk a program vgrehajtsi krnyezetbl (pldul a locale() felhasznlsval) vagy a programra bzzuk, hogy a tapasztaltabb felhasznlktl a lokl meghatrozst krje, mondjuk egy karakterlnc bekrsvel:
void user_set_locale(const string& question_string) { cout << question_string; // pl. "Ha ms loklt kvn hasznlni, adja meg a nevt" string s; cin >> s; locale::global(locale(s.c_str())); // a felhasznl ltal megadott globlis lokl belltsa }

A kezd felhasznlk szmra ltalban jobb, ha lehetv tesszk, hogy listbl vlaszthassanak. Az ezt kezel eljrsnak viszont tudnia kell, hol s hogyan trolja a rendszer a loklokat. Ha a paramterknt megadott karakterlnc nem definilt locale-re hivatkozik, a konstruktor runtime_error kivtelt vlt ki (14.10):
void set_loc(locale& loc, const char* name) try { loc = locale(name); } catch (runtime_error) { cerr << "A \"" << name << "\" lokl nem definilt.\n"; // ... }

Ha a locale nevestett, a name() visszaadja annak nevt, ha nem, a string(*)-gal tr vissza. A nv elssorban arra val, hogy hivatkozhassunk a vgrehajtsi krnyezetben trolt loklokra, de a hibakeressben is segthet:
void print_locale_names(const locale& my_loc) { cout << "name of current global locale: " << locale().name() << "\n"; cout << "name of classic C locale: " << locale::classic().name() << "\n"; cout << "name of users preferred locale: " << locale("").name() << "\n"; cout << "name of my locale: " << my_loc.name() << "\n"; }

Az alaprtelmezett string(*)-tl eltr, de azonos nev loklok sszehasonltskor egyenrtknek minslnek, az == vagy != opertorokkal azonban az sszehasonlts kzvetlenebb mdon is elvgezhet.

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1183

A nvvel rendelkez locale-ek msolatai ugyanazt a nevet kapjk, mint az eredeti locale (ha annak van neve), gy azonos nven tbb locale is szerepelhet. Ez logikus, mert a loklok nem mdosthatk, gy ezen objektumok mindegyike a helyi sajtossgok ugyanazon halmazt rja le. A locale(loc,Foo,cat) hvs a loc-hoz hasonl loklt hoz ltre, de annak jellemzit (a faceteket) a locale(Foo) cat kategrijbl veszi. Az eredmnyl kapott loklnak kizrlag akkor lesz neve, ha a loc-nak is volt. A szabvny nem hatrozza meg pontosan, milyen nevet kap az j locale, de feltehet, hogy klnbzni fog a loc-tl. A legegyszerbb, ha a nevet a loc nevbl s a Foo-bl ptjk fel. Pldul ha a loc neve en_UK, az j lokl neve en_UK:Foo lesz. Az j loklok elnevezsre vonatkoz szablyok a kvetkezkppen foglalhatk ssze:

Lokl locale(Foo) locale(loc) locale(loc,Foo,cat) locale(loc,loc2,cat) locale(loc,Facet) loc.combine(loc2)

Nv Foo loc.name() j nv, ha a loc-nak van neve; egybknt string(*) j nv, ha a loc-nak s a loc2-nek is van neve; egybknt string(*) string(*) string(*)

A programoz az jonnan ltrehozott locale-ek neveknt nem adhat meg C stlus karakterlncot. A neveket a program vgrehajtsi krnyezete hatrozza meg vagy a locale konstruktorok ptik fel azokat a nevek prostsbl.

D.2.1.1. j loklok ltrehozsa j locale objektumot gy kszthetnk, hogy vesznk egy mr ltez locale-t s ehhez jellemzket adunk vagy kicserlnk benne nhnyat. Az j locale-ek jellemzen egy mr ltez locale kiss eltr vltozatai:

Forrs: http://www.doksi.hu

1184

Fggelkek s trgymutat

void f(const locale& loc, const My_money_io* mio) "My_money_io" { locale loc1(locale("POSIX"), loc, locale::monetary); locale loc2 = locale(locale::classic(), mio); // ...

// A D.4.3.1-ben lert

// A loc-beli pnzformtum// jellemzk hasznlata // a klasszikus, plusz "mio"

Itt loc1 a POSIX lokl msolata, amit gy mdostottunk, hogy a loc pnzformtum-jellemzit hasznlja (D.4.3). Ehhez hasonlan, loc2 a C lokl msolata, amely a My_money_io-t hasznlja (D.4.3.1). Ha a Facet* paramter (itt a My_money_io) rtke 0, az eredmnyl kapott lokl egyszeren a locale paramter msolata lesz. Ha a kvetkezt rjuk,
locale(const locale& x, Facet* f);

az f paramternek egy meghatrozott facet tpust kell jellnie. Egy egyszer facet* nem elegend:
void g(const locale::facet* mio1, const My_money_io* mio2) { locale loc3 = locale(locale::classic(), mio1); // hiba: a facet tpusa nem ismert locale loc4 = locale(locale::classic(), mio2); // rendben: a facet tpusa ismert // ... }

Ennek az az oka, hogy a locale a Facet* paramter tpust hasznlja arra, hogy fordtsi idben megllaptsa a facet tpust. Pontosabban, a locale megvalstsa a jellemz azonostjt, a facet::id-t (D.3.3) hasznlja, hogy a jellemzt megtallja a loklban (D.3.1). Jegyezzk meg, hogy a
template <class Facet> locale(const locale& x, Facet* f);

konstruktor a nyelv ltal nyjtott egyetlen eljrs a programoz szmra, hogy facet-eket adhasson meg, melyeket egy locale-en keresztl hasznlni lehet. Az egyb loklokat a megvalst programozknak kell megadniuk, nevestett locale-ek formjban (D.2.1), melyeket a program vgrehajtsi krnyezetbl lehet megszerezni. Ha a programoz rti az erre hasznlatos a fejlesztkrnyezettl fgg eljrst, a meglev loklok krt jakkal bvtheti (D.6[11,12]).

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1185

A loklok konstruktorainak halmazt gy terveztk, hogy minden jellemz tpusa megllapthat legyen, vagy tpuslevezets tjn (a Facet sablonparamter tpusbl), vagy azrt, mert egy msik loklbl szrmazik, amely ismerte a jellemz tpust. A category paramter megadsval a facet-ek tpust kzvetett mdon is meghatrozhatjuk, hiszen a loklok ismerik a kategrikban lv jellemzk tpust. Ebbl kvetkezik, hogy a locale osztly nyomon kvetheti a facet-ek tpust, gy azokat klnsebb tbbletterhels nlkl mdosthatja is. A lokl a facet tpusok azonostsra a locale::id tagtpust hasznlja (D.3). Nha hasznos lehet olyan loklt ltrehozni, amely egy msik lokl msolata, de az egyik jellemzje egy harmadik loklbl szrmazik. A combine() sablon tagfggvny erre val:
void f(const locale& loc, const locale& loc2) { locale loc3 = loc.combine< My_money_io >(loc2); // ... }

Az eredmnyl kapott loc3 gy viselkedik, mint a loc, de a pnzformtumot a loc2-ben lev My_money_io (D.4.3.1) msolata alapjn lltja be. Ha a loc2 nem rendelkezik a My_money_io-val, hogy tadhassa azt az j loklnak, a combine() runtime_error-t (14.10) vlt ki. A combine() eredmnyeknt kapott lokl nem nevestett.

D.2.2. Loklok msolsa s sszehasonltsa


A loklok kezdeti vagy egyszer rtkadssal msolhatk:
void swap(locale& x, locale& y) { locale temp = x; x = y; y = temp; } // ugyanaz, mint az std::swap()

A msolat az eredetivel egyenrtk, de fggetlen, nll objektum:


void f(locale* my_locale) { locale loc = locale::classic(); // "C" lokl if (loc != locale::classic()) { cerr << "Hiba a megvalstsban: rtestse a ksztt.\n";

Forrs: http://www.doksi.hu

1186

Fggelkek s trgymutat

exit(1);

if (&loc != &locale::classic()) cout << "Nem meglep: a cmek klnbznek.\n"; locale loc2 = locale(loc, my_locale, locale::numeric); if (loc == loc2) { cout << "a classic() hasonl facet-jben levkkel.\n"; // ... } // ...

Ha a my_locale rendelkezik olyan jellemzvel, amely a classic() lokl szabvnyos numpunct<char>-jtl eltren adja meg a szmok elvlaszt rsjeleit (my_numpunct<char>), az eredmnyl kapott loklok a kvetkezkppen lesznek brzolhatk:

collate<char>: loc: compare ( ) hash ( ) ... loc2:

numpunct<char>: decimal_point ( ) curr_symbol ( ) ...

my_numpunct<char>: decimal_point ( ) curr_symbol ( ) ...

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1187

A locale objektumok nem mdosthatk, mveleteik viszont lehetv teszik j loklok ltrehozst a mr meglevkbl. A ltrehozs utni mdosts tiltsa (a lokl nem vltozkony, immutable termszete) alapvet fontossg a futsi idej hatkonysg rdekben, ez teszi ugyanis lehetv a felhasznl szmra, hogy meghvja a facet-ek virtulis fggvnyeit s trolja a visszaadott rtkeket. A bemeneti adatfolyamok pldul anlkl tudhatjk, milyen karakter hasznlatos a tizedesvessz (pontosabban tizedespont) jelzsre, illetve anlkl ismerhetik a true brzolst, hogy minden egyes alkalommal meghvnk szm beolvassakor a decimal_point() fggvnyt vagy logikai rtk beolvassakor a truename()-et (D.4.2). Csak az imbue() meghvsa az adatfolyamra (21.6.3) okozhatja, hogy ezek a hvsok klnbz rtkeket adjanak vissza.

D.2.3. A global() s classic() loklok


A programban rvnyben lev lokl msolatt a locale() adja vissza, a locale::global(x) pedig x-re lltja azt. Az rvnyes loklt gyakran globlis loklnak hvjk, utalva arra, hogy valsznleg globlis (vagy statikus) objektum. Az adatfolyamok ltrehozsukkor automatikusan feltltdnek (imbue; 21.1, 21.6.3) a globlis lokllal, vagyis a locale() msolatval, ami elszr mindig a szabvnyos C locale::classic(). A locale::global() statikus tagfggvny megengedi, hogy a programoz meghatrozza, melyik lokl legyen globlis. Az elz msolatt a global() fggvny adja vissza; ennek segtsgvel a felhasznl visszallthatja az eredeti globlis loklt:
void f(const locale& my_loc) { ifstream fin1(some_name); locale& old_global = locale::global(my_loc); ifstream fin2(some_other_name); // ... locale::global(old_global); }

// fin1 feltltse a globlis lokllal // j globlis lokl belltsa // fin2 feltltse a my_loc lokllal // a rgi globlis lokl visszalltsa

Ha az x lokl rendelkezik nvvel, a locale::global(x) szintn a C globlis loklt lltja be. Ebbl kvetkezik, hogy egy vegyes (C s C++) programban egysgesen s kvetkezetesen kezelhetjk a loklt, ha meghvjuk a C standard knyvtrnak valamelyik loklfggvnyt. Ha az x loklnak nincs neve, akkor nem meghatrozhat, hogy a locale::global(x) befolysolja-e a C globlis loklt, vagyis a C++ programok nem kpesek megbzhatan (s hordozhat mdon) tlltani a C loklt egy olyan loklra, amely nem a vgrehajtsi krnye-

Forrs: http://www.doksi.hu

1188

Fggelkek s trgymutat

zetbl val. Nincs szabvnyos md arra sem, hogy egy C program bellthassa a C++ globlis loklt (kivve ha meghv egy C++ fggvnyt, ami ezt megteszi), ezrt a hibk elkerlse rdekben a vegyes programokban nem clszer a global()-tl eltr C globlis loklt hasznlni. A globlis lokl belltsa nincs hatssal a mr ltez I/O adatfolyamokra; azok ugyanazt a loklt fogjk hasznlni, amivel eredetileg feltltdtek. A fin1-re pldul nem hat a globlis lokl mdostsa, de a fin2-t a mvelet a my_loc-kal tlti fel. A globlis lokl mdostsval ugyanaz a problma, mint a globlis adatokat megvltoztat egyb eljrsokkal: lnyegben nem tudhat, mire van hatssal a vltoztats. Ezrt a legjobb, ha a global()-t a lehet legkevesebbszer hasznljuk s a mdostst olyan kdrszletekre korltozzuk, ahol annak hatsa pontosan nyomon kvethet. Szerencsre ezt segti az a lehetsg, hogy az adatfolyamokat meghatrozott loklokkal tlthetjk meg (imbue, 21.6.3). Azrt vigyzzunk: ha a programban elszrva szmos explicit locale- s facet-kezel kd tallhat, a program nehezen lesz fenntarthat s mdosthat.

D.2.4. Karakterlncok sszehasonltsa


A loklokat tbbnyire arra hasznljuk, hogy sszehasonltsunk kt karakterlncot egy locale alapjn. Ezt a mveletet maga a locale nyjtja, a felhasznlknak nem kell sajt sszehasonlt eljrst rniuk a collate jellemzbl (D.4.1). Az eljrs a lokl operator() () sszehasonlt fggvnye, gy kzvetlenl hasznlhat prediktumknt (18.4.2):
void f(vector<string>& v, const locale& my_locale) { sort(v.begin(), v.end()); // rendezs a globlis lokl szerint // ... sort(v.begin(), v.end(), my_locale); // rendezs a my_locale szablyai szerint // ... }

Alaprtelmezs szerint a standard knyvtrbeli sort() a < mveletet alkalmazza a karakterek szmrtkre, hogy eldntse a rendezsi sorrendet (18.7, 18.6.3.1). Jegyezzk meg, hogy a loklok basic_string-eket hasonltanak ssze, nem C stlusakat.

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1189

D.3. Jellemzk
A jellemzk (facet-ek) a lokl facet tagosztlybl szrmaztatott osztlyok objektumai:
class std::locale::facet { protected: explicit facet(size_t r = 0); virtual ~facet(); private: facet(const facet&); void operator=(const facet&); // brzols };

// "r==0": a lokl szablyozza a facet lettartamt // figyelem: vdett destruktor // nem meghatrozott // nem meghatrozott

A msol mveletek privt tagok s szndkosan nincsenek definilva, hogy a msolst megakadlyozzk (11.2.2). A facet osztly bzisosztly szerepet tlt be, nyilvnos fggvnye nincs. Konstruktora vdett, hogy megakadlyozza az egyszer facet objektumok ltrehozst, destruktora pedig virtulis, hogy biztostsa a szrmazott osztlybeli objektumok megfelel megsemmistst. A jellemzk kezelst a loklok elvileg mutatkon keresztl vgzik. A facet konstruktornak 0 rtk paramtere azt jelenti, hogy a loklnak trlnie kell az adott jellemzt, ha mr nincs r hivatkozs. Ezzel szemben a nem nulla konstruktor-paramter azt biztostja, hogy a locale sohasem trli a jellemzt. Ez az a ritka eset, amikor a facet lettartamt a programoz kzvetlenl, nem a loklon keresztl szablyozza. A collate_byname<char> szabvnyos facet tpus objektumokat pldul gy hozhatjuk ltre (D.4.1.1):
void f(const string& s1, const string& s2) { // szoksos eset: a 0 (alaprtelmezett) paramter azt jelzi, // hogy a lokl felel a felszmolsrt collate<char>* p = new collate_byname<char>("pl"); locale loc(locale(), p); // ritka eset: a paramter rtke 1, teht a felhasznl felel a felszmolsrt collate<char>* q = new collate_byname<char>("ge",1); collate_byname<char> bug1("sw"); // hiba: loklis vltozt nem lehet felszmolni collate_byname<char> bug2("no",1); // hiba: loklis vltozt nem lehet felszmolni // ...

Forrs: http://www.doksi.hu

1190

Fggelkek s trgymutat

// q nem trlhet: a collate_byname<char> destruktora vdett // nincs "delete p", mert a lokl intzi *p felszmolst

Azaz a szabvnyos facet-ek a lokl ltal kezelt bzisosztlyknt hasznosak, ritkn kell ms mdon kezelnnk azokat. A _byname() vgzds jellemzk a vgrehajtsi krnyezetben tallhat nevestett lokl facet-jei (D.2.1). Minden facet-nek rendelkeznie kell azonostval (id), hogy a loklban a has_facet() s use_facet() fggvnyekkel megtallhat legyen (D.3.1):
class std::locale::id { public: id(); private: id(const id&); void operator=(const id&); // brzols };

// nem definilt // nem definilt

A msol mveletek privtok s nincsenek kifejtve, hogy a msolst megakadlyozzk (11.2.2). Az azonost arra val, hogy a jellemzk szmra j felletet ad osztlyokban (pldul lsd D.4.1-et) id tpus statikus tagokat hozhassunk ltre. A loklok eljrsai az id-t hasznljk a facet-ek azonostsra (D.2, D.3.1). Az azonostk tbbnyire egy facet-ekre hivatkoz mutatkbl ll vektor indexrtkei (vagyis map<id,facet*>, ami igen hatkony). A (szrmaztatott) jellemzket meghatroz adatokat a szrmaztatott osztly rja le, nem maga a facet bzisosztly. Ebbl kvetkezik, hogy a programoz tetszleges mennyisg adatot hasznlhat a facet fogalmnak brzolsra s korltozs nlkl mdosthatja azokat. Jegyezzk meg, hogy a felhasznli facet-ek minden fggvnyt const tagknt kell meghatrozni, mert a facet-eknek llandnak kell lennik (D.2.2).

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1191

D.3.1. Jellemzk elrse a loklokban


A loklok facet-jei a use_facet sablon fggvnyen keresztl rhetk el s a has_facet sablon fggvnnyel krdezhetjk le, hogy a lokl rendelkezik-e egy adott jellemzvel:
template <class Facet> bool has_facet(const locale&) throw(); template <class Facet> const Facet& use_facet(const locale&); // bad_cast kivtelt vlthat ki

Ezekre a fggvnyekre gy kell gondolnunk, mintha azok locale paramterkben keresnk Facet sablonparamterket. Ms megkzeltsben a use_facet egyfajta tpusknyszerts (cast), egy lokl konvertlsa egy meghatrozott jellemzre. Ez azrt lehetsges, mert a locale objektumok egy adott tpus facet-bl csak egy pldnyt tartalmazhatnak:
void f(const locale& my_locale) { char c = use_facet< numpunct<char> >(my_locale).decimal_point() // a szabvnyos // facet hasznlata // ... if (has_facet<Encrypt>(my_locale)) { // tartalmaz-e a my_locale Encrypt facet-et? const Encrypt& f = use_facet<Encrypt>(my_locale); // az Encrypt facet kinyerse // a my_locale-bl const Crypto c = f.get_crypto(); // az Encrypt facet hasznlata // ... } // ...

Jegyezzk meg, hogy a use_facet egy konstans facet-re val referencit ad vissza, gy az eredmnyt nem adhatjuk rtkl egy nem konstans vltoznak. Ez azrt logikus, mert a jellemzknek elvileg nem mdosthatknak kell lennik s csak const tagokat tartalmazhatnak. Ha meghvjuk a use_facet<X>(loc) fggvnyt s loc nem rendelkezik az X jellemzvel, a use_facet() bad_cast kivtelt vlt ki (14.10). A szabvnyos facet-ek garantltan hozzfrhetek minden locale szmra (D.4), gy az esetkben nem kell a has_facet-et hasznlnunk, ezekre a use_facet nem fog bad_cast kivtelt kivltani. Hogyan lehetne a use_facet s has_facet fggvnyeket megvalstani? Emlkezznk, hogy egy loklra gy gondolhatunk, mintha map<id,facet*> lenne (D.2). Ha a Facet sablonparamterknt adott egy facet tpus, a has_facet vagy use_facet a Facet::id-re hivatkozhat s ezt hasznlhatja a megfelel jellemz megkeressre. A has_facet s use_facet nagyon egyszer vltozata gy nzhetne ki:

Forrs: http://www.doksi.hu

1192

Fggelkek s trgymutat

// l-megvalsts: kpzeljk gy, hogy a lokl rendelkezik egy facet_map-nek nevezett // map<id,facet*>-tel template <class Facet> bool has_facet(const locale& loc) throw() { const locale::facet* f = loc.facet_map[Facet::id]; return f ? true : false; } template <class Facet> const Facet& use_facet(const locale& loc) { const locale::facet* f = loc.facet_map[Facet::id]; if (f) return static_cast<const Facet&>(*f); throw bad_cast(); }

A facet::id hasznlatt gy is tekinthetjk, mint a fordtsi idej tbbalaksg (parametrikus polimorfizmus) egy formjt. A dynamic_cast a use_facet-hez hasonl eredmnyt adna, de az utbbi sokkal hatkonyabb, mert kevsb ltalnos. Az id valjban inkbb egy felletet s viselkedst azonost, mint osztlyt. Azaz ha kt facet osztlynak pontosan ugyanaz a fellete s (a locale szempontjbl) ugyanaz a szerepk, akkor ugyanaz az id kell, hogy azonostsa ket. A collate<char> s a collate_byname<char> pldul felcserlhet egy loklban, gy mindkettt a collate<char>::id azonostja (D.4.1). Ha egy jellemznek j felletet adunk mint az f()-ben az Encrypt-nek , definilnunk kell szmra az azonostt (lsd D.3.2-t s D.4.1-et).

D.3.2. Egyszer felhasznli facet-ek


A standard knyvtr a kulturlis eltrsek leglnyegesebb terleteihez mint a karakterkszletek kezelse s a szmok be- s kivitele szabvnyos facet-eket nyjt. Ahhoz, hogy az ltaluk nyjtott szolgltatsokat a szles krben hasznlatos tpusok bonyolultsgtl s a velk jr hatkonysgi problmktl elklntve vizsglhassuk, hadd mutassak be elszr egy egyszer felhasznli tpusra vonatkoz facet-et:
enum Season { tavasz, nyr, sz, tl }; // vszakok

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1193

Ez volt a legegyszerbb felhasznli tpus, ami ppen eszembe jutott. Az itt felvzolt I/O kis mdostsokkal a legtbb egyszer felhasznli tpus esetben felhasznlhat.
class Season_io : public locale::facet { public: Season_io(int i = 0) : locale::facet(i) { } ~Season_io() { } // lehetv teszi a Season_io objektumok felszmolst (D.3) // x brzolsa karakterlncknt virtual const string& to_str(Season x) const = 0; // az s karakterlncnak megfelel vszak elhelyezse x-be: virtual bool from_str(const string& s, Season& x) const = 0; static locale::id id; }; locale::id Season_io::id; // facet-azonost objektum (D.2, D.3, D.3.1) // az azonost objektum meghatrozsa

Az egyszersg kedvrt a facet csak a char tpust hasznl megvalstsokra korltozdik. A Season_io osztly ltalnos, elvont felletet nyjt minden Season_io facet szmra. Ha a Season be- s kimenett egy adott loklra szeretnnk definilni, a Season_io-bl szrmaztatunk egy osztlyt, amelyben megfelelen kifejtjk a to_str() s from_str() fggvnyeket. A Season rtkt knny kirni. Ha az adatfolyam rendelkezik Season_io jellemzvel, azt hasznlva az rtket karakterlncc alakthatjuk, ha nem, kirhatjuk a Season egsz rtkt:
ostream& operator<<(ostream& s, Season x) { const locale& loc = s.getloc(); // az adatfolyam lokljnak kinyerse (21.7.1) if (has_facet<Season_io>(loc)) return s << use_facet<Season_io>(loc).to_str(x); return s << int(x); }

szrevehetjk, hogy a << mveletet gy definiltuk, hogy ms tpusokra hvtuk meg a <<t. Ennek szmos elnye van: egyszerbb a <<-t hasznlnunk, mint a kimeneti adatfolyam tmeneti traihoz kzvetlenl hozzfrnnk, a << mveletet kifejezetten a loklhoz igazthatjuk s a mvelet hibakezelst is biztost. A szabvnyos facet-ek a legnagyobb hatkonysg s rugalmassg elrse rdekben tbbnyire kzvetlenl az adatfolyam tmeneti trt kezelik (D.4.2.2, D.4.2.3), de sok felhasznli tpus esetben nincs szksg arra, hogy a streambuf absztrakcis szintjre sllyedjnk.

Forrs: http://www.doksi.hu

1194

Fggelkek s trgymutat

Ahogy lenni szokott, a bemenet kezelse nmileg bonyolultabb, mint a kimenet:


istream& operator>>(istream& s, Season& x) { const locale& loc = s.getloc(); // az adatfolyam lokljnak kinyerse (21.7.1) if (has_facet<Season_io>(loc)) { // a szveges brzols beolvassa const Season_io& f = use_facet<Season_io>(loc); string buf; if (!(s>>buf && f.from_str(buf,x))) s.setstate(ios_base::failbit); return s; } int i; // a szmbrzols beolvassa s >> i; x = Season(i); return s;

A hibakezels egyszer, a beptett tpusok hibakezelsnek stlust kveti. Azaz, ha a bemen karakterlnc nem a vlasztott lokl valamelyik Season-jt jelli, az adatfolyam hibs (failure) llapotba kerl. Ha a kivtelek megengedettek, ios_base::failure kivtel kivltsra kerlhet sor (21.3.6). Vegynk egy egyszer tesztprogramot:
int main() { Season x; // egyszer teszt

// az alaprtelmezett lokl hasznlata (nincs Season_io facet); // egsz rtk I/O-t eredmnyez: cin >> x; cout << x << endl; locale loc(locale(), new US_season_io); cout.imbue(loc); // Season_io facet-tel rendelkez lokl hasznlata cin.imbue(loc); // Season_io facet-tel rendelkez lokl hasznlata cin >> x; cout << x << endl;

A
2 summer

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1195

bemenetre a program vlasza:


2 summer

Ennek elrshez definilnunk kell a US_season_io osztlyt, amelyben megadjuk az vszakok karakterlnc-brzolst s fellrjuk a Season_io azon fggvnyeit, amelyek a karakterlncokat a felsorolt elemekre alaktjk:
class US_season_io : public Season_io { static const string seasons[]; public: const string& to_str(Season) const; bool from_str(const string&, Season&) const; }; // figyelem: nincs US_season_io::id

const string US_season_io::seasons[] = { "spring", "summer", "fall", "winter" }; const string& US_season_io::to_str(Season x) const { if (x<spring || winter<x) { static const string ss = "Nincs ilyen vszak"; return ss; } return seasons[x]; } bool US_season_io::from_str(const string& s, Season& x) const { const string* beg = &seasons[spring]; const string* end = &seasons[winter]+1; const string* p = find(beg,end,s); // 3.8.1, 18.5.2 if (p==end) return false; x = Season(p-beg); return true;

Vegyk szre, hogy mivel a US_season_io csupn a Season_io fellet megvalstsa, nem adunk meg azonostt a US_season_io szmra. St, ha a US_season_io-t Season_io-knt akarjuk hasznlni, nem is szabad ilyet tennnk. A loklok mveletei (pldul a has_facet, D.3.1) arra tmaszkodnak, hogy az azonos fogalmakat brzol facet-eket ugyanaz az id azonostja (D.3).

Forrs: http://www.doksi.hu

1196

Fggelkek s trgymutat

A megvalstssal kapcsolatos egyetlen rdekes krds az, hogy mit kell tenni, ha rvnytelen Season kirst krik? Termszetesen ennek nem lenne szabad megtrtnnie. Az egyszer felhasznli tpusoknl azonban nem ritka, hogy rvnytelen rtket tallunk, gy szmtsba kell vennnk ezt a lehetsget is. Kivlthatnnk egy kivtelt, de miutn olyan egyszer kimenettel foglalkozunk, amelyet emberek fognak olvasni, hasznos, ha a tartomnyon kvli rtkeket az rtktartomnyon kvli szveg is jelzi. A bemenetnl itt a kivtelkezels a >> mveletre hrul, mg a kimenet esetben ezt a facet to_str() fggvnye vgzi (hogy bemutathassuk a lehetsgeket). Valdi programoknl a facet fggvnyei a be- s kimeneti hibk kezelsvel egyarnt foglalkoznak, vagy csak jelentik a hibkat, a << s >> mveletekre bzva azok kezelst. A Season_io ezen vltozata arra tmaszkodott, hogy a szrmaztatott osztlyok adjk meg a loklra jellemz karakterlncokat. Egy msik megolds, hogy a Season_io maga szerzi meg ezeket egy, a loklhoz kapcsold adattrbl (lsd D.4.7). Gyakorlatknt hagytuk annak kidolgozst, hogy egyetlen Season_io osztlyunk van, amelynek az vszakokat jelz karakterlncok a konstruktor paramtereknt addnak t (D.6[2]).

D.3.3. A loklok s jellemzk hasznlata


A loklok elsdlegesen a standard knyvtron bell, az I/O adatfolyamokban hasznlatosak, de a locale a helyi sajtossgok brzolsnak ennl ltalnosabb eszkze. A messages (D.4.7) pldul olyan facet, amelynek semmi kze a be- s kimeneti adatfolyamokhoz. Az iostream knyvtr esetleges bvtsei, st, a nem adatfolyamokkal dolgoz be- s kimeneti eszkzk is kihasznlhatjk a loklok adta lehetsgeket, a felhasznl pedig a locale objektumok segtsgvel tetszleges mdon rendezheti a helyi sajtossgokat. A loklokon s jellemzkn alapul eljrsok ltalnossga rvn a felhasznli facet-ek adta lehetsgek korltlanok. A dtumok, idznk, telefonszmok, trsadalombiztostsi szmok (szemlyi szmok), gyrtsi szmok, hmrskletek, ltalnos (mrtkegysg, rtk) prok, irnytszmok, ruhamretek, s ISBN szmok mind megadhatk facet-knt. Mint minden ers szolgltatssal, a facet-ekkel is vatosan kell bnni. Az, hogy valamit lehet jellemzknt brzolni, mg nem jelenti azt, hogy ez a legjobb megolds. A kulturlis eltrsek brzolsnak kivlasztsakor a kulcskrds mint mindig az, hogy milyen nehz a kd megrsa, mennyire knny a kapott kdot olvasni, valamint hogy hogyan befolysoljk a dntsek a kapott program fenntarthatsgt, illetve az I/O mveletek id- s trbeli hatkonysgt.

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1197

D.4. Szabvnyos facet-ek


A standard knyvtr <locale> fejllomnya a kvetkez facet-eket nyjtja a classic() loklhoz:

Szabvnyos facet-ek (a classic() loklban) Kategria D.4.1 D.4.2 collate numeric Rendeltets Karakterlncok sszehasonltsa Szmok be- s kivitele Facet-ek collate<Ch>

D.4.3

monetary

D.4.4 D.4.5 D.4.7

time ctype messages

numpunct<Ch> num_get<Ch> num_put<Ch> Pnz I/O moneypunct<Ch> moneypunct<Ch,true> money_get<Ch> money_put<Ch> Id I/O time_get<Ch> time_put<Ch> Karakterek osztlyozsa ctype<Ch> codecvt<Ch,char,mbstate_t> zenet-visszakeress messages<Ch>

A tblzatban a Ch helyn char vagy wchar_t tpus szerepelhet. Ha a felhasznlnak arra van szksge, hogy a szabvnyos I/O msfajta X karaktertpust kezeljen, a megfelel faceteket meg kell adnia az X szmra. A codecvt<X,char,mbstate_t> (D.4.6) pldul szksges lehet az X s char tpusok kztti talaktsokhoz. Az mbstate_t tpus arra val, hogy egy tbbjtos karakterbrzols lptetsi llapotait jellje (D.4.6); definicija a <cwchar> s a <wchar.h> fejllomnyokban tallhat. Tetszleges X karaktertpus esetben az mbstate_t-nek a char_traits<X>::state_type felel meg.

Forrs: http://www.doksi.hu

1198

Fggelkek s trgymutat

A standard knyvtr tovbbi facet-jei a <locale> fejllomnyban a kvetkezk:

Szabvnyos facet-ek Kategria D.4.1 D.4.2 collate numeric Rendeltets Karakterlncok sszehasonltsa Facet-ek collate_byname<Ch>

Szmok be- s kivitele numpunct_byname<Ch> num_get<C,In> num_put<C,Out>

D.4.3

monetary Pnz I/O

moneypunct_byname<Ch,International> money_get<C,In> money_put<C,Out>

D.4.4 D.4.5 D.4.7

time ctype messages

Id I/O zenet-visszakeress

time_put_byname<Ch> messages_byname<Ch>

Karakterek osztlyozsa ctype_byname<Ch>

A tblzatban szerepl jellemzk pldnyostsakor a Ch char vagy wchar_t lehet; a C brmilyen karaktertpus (20.1), az International rtke true vagy false, ahol a true azt jelenti, hogy a valuta-szimblum ngykarakteres nemzetkzi brzolst hasznljuk (D.4.3.1). Az mbstate_t tpus a tbbjtos karakter-brzolsok lptetsi llapotait jelli (D.4.6), meghatrozsa a <cwchar> s a <wchar.h> fejllomnyokban tallhat. Az In s az Out bemeneti s kimeneti bejrk (itertorok, 19.1, 19.2.1). Ha a _put s _get jellemzket elltjuk ezekkel a sablonparamterekkel, olyan facet-eket hozhatunk ltre, amelyek nem szabvnyos tmeneti trakhoz frnek hozz (D.4.2.2). Az iostream-ek tmeneti trai (pufferei) adatfolyam tmeneti trak, gy bejrik ostreambuf_iterator-ok (19.2.6.1, D.4.2.2), vagyis a hibakezelshez rendelkezsnkre ll a failed() fggvny. Az F_byname az F facet-bl szrmazik; ugyanazt a felletet nyjtja mint az F, de hozzad egy konstruktort, amelynek egy loklt megnevez karakterlnc paramtere van (lsd D.4.1-et). Az F_byname(nv) jelentse ugyanaz, mint az F locale(nv) szerkezet. Az elgondols az, hogy a program vgrehajtsi krnyezetben egy nevestett loklbl (D.2.1) kivesszk a szabvnyos facet egy adott vltozatt:

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1199

void f(vector<string>& v, const locale& loc) { locale d1(loc, new collate_byname<char>("da")); // dn karakterlnc-sszehasonlts // hasznlata locale dk(d1, new ctype_byname<char>("da")); // dn karakterosztlyozs hasznlata sort(v.begin(), v.end(), dk); // ... }

Az j dk lokl dn stlus karakterlncokat fog hasznlni, de megtartja a szmokra vonatkoz alaprtelmezett szablyokat. Mivel a facet msodik paramtere alaprtelmezs szerint 0, a new mvelettel ltrehozott facet lettartamt a lokl fogja kezelni (D.3). A karakterlnc paramterekkel rendelkez locale-konstruktorokhoz hasonlan a _bynamekonstruktorok is hozzfrnek a program vgrehajtsi krnyezethez. Ebbl az kvetkezik, hogy nagyon lassak azokhoz a konstruktorokhoz kpest, amelyeknek nem kell a krnyezethez fordulniuk informcirt. Majdnem mindig gyorsabb, ha ltrehozunk egy loklt s azutn frnk hozz annak jellemzihez, mintha _byname facet-eket hasznlnnk tbb helyen a programban. Ezrt j tlet, ha a facet-et egyszer olvassuk be a krnyezetbl, majd ksbb mindig a memriban lv msolatt hasznljuk:
locale dk("da"); // a dn lokl beolvassa (belertve sszes facet-jt) egyszer, // majd a dk lokl s jellemzinek hasznlata igny szerint

void f(vector<string>& v, const locale& loc) { const collate<char>& col = use_facet< collate<char> >(dk); const collate<char>& ctyp = use_facet< ctype<char> >(dk); locale d1(loc,col); // dn karakterlnc-sszehasonlts hasznlata locale d2(d1,ctyp); // dn karakterosztlyozs s karakterlnc// sszehasonlts hasznlata sort(v.begin(), v.end(), d2); // ...

A kategrik egyszerbb teszik a loklok szabvnyos facet-jeinek kezelst. Pldul ha adott a dk lokl, ltrehozhatunk belle egy msikat, amely a dn nyelv szablyainak megfelelen (ez az angolhoz kpest hrom tovbbi magnhangzt jelent) olvas be s hasonlt ssze karakterlncokat, de megtartja a C++-ban hasznlatos szmformtumot:
locale dk_us(locale::classic(), dk, collate|ctype); // dn betk, amerikai szmok

Forrs: http://www.doksi.hu

1200

Fggelkek s trgymutat

Az egyes szabvnyos facet-ek bemutatsnl tovbbi pldkat nznk meg a jellemzk hasznlatra. A collate (D.4.1) trgyalsakor pldul a facet-ek sok kzs szerkezetbeli tulajdonsga elkerl. Jegyezzk meg, hogy a szabvnyos facet-ek gyakran fggnek egymstl. A num_put pldul a numpunct-ra tmaszkodik. Csak ha az egyes jellemzket mr rszletesen ismerjk, akkor lehetnk kpesek azokat sikeresen egytt hasznlni, egyeztetni vagy j vltozataikat elkszteni. Ms szavakkal, a 21.7 pontban emltett egyszer mveleteken tl a loklok nem arra valk, hogy a kezdk kzvetlenl hasznljk azokat. A jellemzk megtervezse gyakran nagyon krlmnyes. Ennek oka rszben az, hogy a jellemzk nem rendszerezhet helyi sajtossgokat kell, hogy tkrzzenek, melyekre a knyvtr tervezje nincs befolyssal; msrszt pedig az, hogy a C++ standard knyvtrbeli eszkzeinek nagyrszt sszeegyeztethetnek kell maradniuk azzal, amit a C standard knyvtra s az egyes platformok szabvnyai nyjtanak. A POSIX pldul olyan eszkzkkel tmogatja a loklok hasznlatt, melyeket a knyvtrak tervezi nem szabad, hogy figyelmen kvl hagyjanak. Msfell a loklok s jellemzk ltal nyjtott szerkezet nagyon ltalnos s rugalmas. A facet-ek brmilyen adatot trolhatnak s azokon brmilyen mveletet vgezhetnek. Ha az j facet viselkedst a szablyok nem korltozzk tlsgosan, szerkezete egyszer s tiszta lehet (D.3.2).

D.4.1. Karakterlncok sszehasonltsa


A szabvnyos collate jellemz Ch tpus karakterekbl ll tmbk sszehasonltst teszi lehetv:
template <class Ch> class std::collate : public locale::facet { public: typedef Ch char_type; typedef basic_string<Ch> string_type; explicit collate(size_t r = 0); int compare(const Ch* b, const Ch* e, const Ch* b2, const Ch* e2) const { return do_compare(b,e,b2,e2); } long hash(const Ch* b, const Ch* e) const { return do_hash(b,e); } string_type transform(const Ch* b, const Ch* e) const { return do_transform(b,e); }

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1201

static locale::id id; protected: ~collate();

// facet-azonost objektum (D.2, D.3, D.3.1) // figyelem: vdett destruktor

};

virtual int do_compare(const Ch* b, const Ch* e, const Ch* b2, const Ch* e2) const; virtual string_type do_transform(const Ch* b, const Ch* e) const; virtual long do_hash(const Ch* b, const Ch* e) const;

A tbbi facet-hez hasonlan a collate is nyilvnos mdon szrmazik a facet osztlybl s olyan konstruktorral rendelkezik, amelynek egyetlen paramtere azt mondja meg, hogy a locale osztly vezrli-e a jellemz lettartamt (D.3). Jegyezzk meg, hogy a destruktor vdett (protected). A collate nem kzvetlen hasznlatra val, inkbb arra szntk, hogy minden (szrmaztatott) sszehasonlt osztly alapja legyen s a locale kezelje (D.3). A rendszerfejlesztknek s a knyvtrak ksztinek a karakterlncokat sszehasonlt facet-eket gy kell megrniuk, hogy a collate nyjtotta felleten keresztl lehessen hasznlni azokat. Az alapvet karakterlnc-sszehasonltst a compare() fggvny vgzi, az adott collate-re vonatkoz szablyok szerint; 1-et ad vissza, ha az els karakterlnc tbb karakterbl ll, mint a msodik, 0-t, ha a karakterlncok megegyeznek, s -1-et, ha a msodik karakterlnc nagyobb, mint az els:
void f(const string& s1, const string& s2, collate<char>& cmp) { const char* cs1 = s1.data(); // mivel a compare() mvelet char[] tmbn dolgozik const char* cs2 = s2.data(); switch ( cmp.compare(cs1,cs1+s1.size(), cs2,cs2+s2.size()) ) { case 0: // a cmp szerint azonos karakterlncok // ... break; case -1: // s1 < s2 // ... break; case 1: // s1 > s2 // ... break; }

Forrs: http://www.doksi.hu

1202

Fggelkek s trgymutat

Vegyk szre, hogy a collate tagfggvnyek Ch tpus elemekbl ll tmbket hasonltanak ssze, nem basic_string-eket vagy nulla vgzds C stlus karakterlncokat, vagyis a 0 szmrtk Ch kznsges karakternek minsl, nem vgzdsnek. A compare() abban is klnbzik a strcmp()-tl, hogy pontosan a -1, 0, 1 rtkeket adja vissza, nem egyszeren 0-t s (tetszleges) pozitv s negatv rtkeket (20.4.1). A standard knyvtrbeli string nem fgg a lokloktl, azaz a karakterlncok sszehasonltsa az adott nyelvi vltozat karakterkszlete alapjn trtnik (C.2). Ezenkvl a szabvnyos string nem biztost kzvetlen mdot arra, hogy meghatrozzuk az sszehasonltsi felttelt (20. fejezet). A lokltl fgg sszehasonltshoz a collate kategria compare() fggvnyt hasznlhatjuk. Mg knyelmesebb, ha a fggvnyt a lokl operator() opertorn keresztl, kzvetett mdon hvjuk meg (D.2.4):
void f(const string& s1, const string& s2, const char* n) { bool b = s1 == s2; // sszehasonlts a megvalsts karakterkszletnek rtkei szerint const char* cs1 = s1.data(); const char* cs2 = s2.data(); typedef collate<char> Col; const Col& glob = use_facet<Col>(locale()); // az rvnyes globlis loklbl int i0 = glob.compare(cs1,cs1+s1.size(),cs2,cs2+s2.size()); const Col& my_coll = use_facet<Col>(locale("")); // az elnyben rszestett loklbl int i1 = my_coll.compare(cs1,cs1+s1.size(),cs2,cs2+s2.size()); const Col& coll = use_facet<Col>(locale(n)); // az "n" nev loklbl int i2 = coll.compare(cs1,cs1+s1.size(),cs2,cs2+s2.size()); int i3 = locale()(s1,s2); // sszehasonlts az rvnyes globlis lokl alapjn int i4 = locale("")(s1,s2); // sszehasonlts az elnyben rszestett lokl alapjn int i5 = locale(n)(s1,s2); // sszehasonlts az "n" lokl alapjn // ... // mivel a compare() mvelet char[] tmbn dolgozik

Itt i0==i3, i1==i4, s i2==i5, de knny olyan eseteket elkpzelni, ahol i2, i3, s i4 rtke ms. Vegyk a kvetkez szavakbl ll sorozatot egy nmet sztrbl:
Dialekt, Dit, dich, dichten, Dichtung

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1203

A nyelv szablyainak megfelelen a fnevek (s csak azok) nagy kezdbetsek, de a rendezs nem klnbzteti meg a kis- s nagybetket. Egy kis- s nagybetket megklnbztet nmet nyelv rendezs minden D-vel kezdd szt a d el tenne:
Dialekt, Dit, Dichtung, dich, dichten

Az (umlautos a) egyfajta a-nak minsl, gy a c el kerl. A legtbb karakterkszletben azonban az szmrtke nagyobb a c szmrtknl, kvetkezskppen int(c) < int(a), a szmrtkeken alapul egyszer alaprtelmezett rendezs pedig a kvetkezket adja:
Dialekt, Dichtung, Dit, dich, dichten

rdekes feladat lehet egy olyan fggvnyt rni, amely a sztrnak megfelelen helyesen rendezi ezt a sorozatot (D.6[3]). A hash() fggvny egy hastrtket szmt ki (17.6.2.3), ami magtl rtetden hasttblk ltrehozsakor lehet hasznos. A transform() fggvny egy olyan karakterlncot llt el, amelyet ms karakterlncokkal sszehasonltva ugyanazt az eredmnyt kapjuk, mint amit a paramter-karakterlnccal val sszehasonlts eredmnyezne. A transform() clja az, hogy optimlis kdot kszthessnk az olyan programrszekbl, ahol egy karakterlncot szmos msikkal hasonltunk ssze. Ez akkor hasznos, ha karakterlncok halmazban egy vagy tbb karakterlncot szeretnnk megkeresni. A public compare(), a hash() s a transform() fggvnyek megvalstst a do_compare(), do_hash() s do_transform() nyilvnos virtulis fggvnyek meghvsa biztostja. Ezeket a do_ fggvnyeket a szrmaztatott osztlyokban fell lehet rni. A ktfggvnyes megolds lehetv teszi a knyvtr azon ksztjnek, aki a nem virtulis fggvnyeket rja, hogy valamilyen kzs szerepet biztostson minden hvsnak, fggetlenl attl, hogy mit csinlnnak a felhasznl ltal megadott do_ fggvnyek. A virtulis fggvnyek hasznlata megrzi a facet-ek tbbalak (polimorfikus) termszett, de kltsges lehet. A tl sok fggvnyhvs elkerlshez a locale pontosan meghatrozhatja a hasznlatos jellemzket s brmennyi rtket a gyorsttrba tehet, amennyire csak szksge van a hatkony vgrehajtshoz (D.2.2).

Forrs: http://www.doksi.hu

1204

Fggelkek s trgymutat

A jellemzket locale::id tpus statikus id tagok azonostjk (D.3). A szabvnyos has_facet s use_facet fggvnyek az azonostk s jellemzk kztti sszefggseken alapulnak (D.3.1). Az azonos fellet s szerep facet-eknek ugyanazzal az azonostval kell rendelkeznik, gy a collate<char> s a collate_byname<char> (D.4.1.1) azonostja is megegyezik. Kvetkezskppen kt facet-nek biztosan klnbz az azonostja, ha (a locale szempontjbl nzve) klnbz fggvnyeket hajtanak vgre, gy ez a helyzet a numpunct<char> s a num_put<char> esetben is (D.4.2).

D.4.1.1. Nevestett Collate A collate_byname olyan jellemz, amely a collate azon vltozatt nyjtja az adott loklnak, amelyet a konstruktor karakterlnc-paramtere nevez meg:
template <class Ch> class std::collate_byname : public collate<Ch> { public: typedef basic_string<Ch> string_type; // ltrehozs nvvel rendelkez loklbl explicit collate_byname(const char*, size_t r = 0); // figyelem: nincs azonost s nincsenek j fggvnyek protected: ~collate_byname(); // figyelem: vdett destruktor // a collate<Ch> virtulis fggvnyeinek fellrsa int do_compare(const Ch* b, const Ch* e, const Ch* b2, const Ch* e2) const; string_type do_transform(const Ch* b, const Ch* e) const; long do_hash(const Ch* b, const Ch* e) const;

};

gy a collate_byname arra hasznlhat, hogy kivegynk egy collate-et egy, a program vgrehajtsi krnyezetben lev nevestett loklbl (D.4). A vgrehajtsi krnyezetben a facet-eket egyszeren fjlban, adatknt is trolhatjuk. Egy kevsb rugalmas megolds, ha a jellemzket programszvegknt s adatknt brzoljuk egy _byname facet-ben. A collate_byname<char> osztly olyan facet, amelynek nincs sajt azonostja (D.3). A loklokban a collate_byname<Ch> s a collate<Ch> felcserlhetk. Azonos lokl esetben a collate s a collate_byname csak az utbbi szerepben s a collate_byname ltal felknlt tovbbi konstruktorban klnbzik.

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1205

Jegyezzk meg, hogy a _byname destruktor vdett. Ebbl kvetkezik, hogy loklis (helyi) vltozknt nem hasznlhatunk _byname facet-et:
void f() { collate_byname<char> my_coll(""); // hiba: a my_coll nem szmolhat fel // ... }

Ez azt a nzpontot tkrzi, hogy a loklok s jellemzk olyasmik, amiket a legjobb elgg magas szinten hasznlni a programban, hogy a program minl nagyobb rszre legynk hatssal. Erre plda a globlis lokl belltsa (D.2.3) vagy egy adatfolyam megtltse (21.6.3, D.1). Ha szksges, egy _byname osztlybl egy nyilvnos destruktorral rendelkez osztlyt szrmaztathatunk s ebbl az osztlybl loklis vltozkat hozhatunk ltre.

D.4.2. Szmok be- s kivitele


A szm-kimenetet a num_put facet kezeli, amely egy adatfolyam tmeneti trba r (21.6.4). A bemenet kezelse a num_get dolga; ez is tmeneti trbl olvas. A num_put s num_get ltal hasznlt formtumot a numpunct jellemz hatrozza meg.

D.4.2.1. Szmjegy-formtumok A beptett tpusok (mint a bool, az int, s a double) be- s kimeneti formtumt a numpunct jellemz rja le:
template <class Ch> class std::numpunct : public locale::facet { public: typedef Ch char_type; typedef basic_string<Ch> string_type; explicit numpunct(size_t r = 0); Ch decimal_point() const; // . a classic() loklban Ch thousands_sep() const; // , a classic() loklban string grouping() const; // "" a classic() loklban, jelentse: nincs csoportosts string_type truename() const; string_type falsename() const; // "true" a classic() loklban // "false" a classic() loklban

Forrs: http://www.doksi.hu

1206

Fggelkek s trgymutat

static locale::id id; protected: ~numpunct(); };

// facet-azonost objektum (D.2, D.3, D.3.1)

// virtulis "do_" fggvnyek a nyilvnos fggvnyek szmra (lsd D.4.1)

A grouping() ltal visszaadott karakterlnc karaktereinek beolvassa kis egsz rtkek sorozataknt trtnik. Minden szm a szmjegyek szmt hatrozza meg egy csoport szmra. A 0. karakter a jobb szls csoportot adja meg (ezek a legkisebb helyirtk szmjegyek), az 1. az attl balra lev csoportot s gy tovbb. gy a \004\002\003 egy szmot r le (pl. 123-45-6789, feltve, hogy a - az elvlasztsra hasznlt karakter). Ha szksges, a csoportost minta utols karaktere ismtelten hasznlhat, gy a \003 egyenrtk a \003\003\003-mal. Ahogy az elvlaszt karakter neve, a thousands_sep() mutatja is, a csoportostst leggyakrabban arra hasznljk, hogy a nagy egszeket olvashatbb tegyk. A grouping() s thousands_sep() fggvnyek az egszek be- s kimeneti formtumt is megadjk, a lebegpontos szmok szabvnyos be- s kimenethez azonban nem hasznlatosak, gy nem tudjuk kiratni az 1234567.89-et 1,234,567.89-knt csupn azltal, hogy megadjuk a grouping() s thousands_sep() fggvnyeket. A numpunct osztlybl szrmaztatssal j formtumot adhatunk meg. A My_punct jellemzben pldul lerhatjuk, hogy az egsz rtkek szmjegyeit szkzkkel elvlasztva s hrmasval csoportostva, a lebegpontos rtkeket pedig eurpai stlus szerint, tizedesvesszvel elvlasztva kell kirni:
class My_punct : public std::numpunct<char> { public: typedef char char_type; typedef string string_type; explicit My_punct(size_t r = 0) : std::numpunct<char>(r) { } protected: char do_decimal_point() const { return ,; } // vessz char do_thousands_sep() const { return ; } // szkz string do_grouping() const { return "\003"; } // 3 szmjegy csoportok }; void f() { cout << "Els stlus: " << 12345678 << " *** " << 1234567.8 << \n; locale loc(locale(), new My_punct); cout.imbue(loc); cout << "Msodik stlus: " << 12345678 << " *** " << 1234567.8 << \n;

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1207

Ez a kvetkez eredmnyt adja:


Els stlus: 12345678 *** 1.23457e+06 Msodik stlus: 12 345 678 *** 1,23457e+06

Jegyezzk meg, hogy az imbue() az adatfolyamban msolatot trol paramterrl. Kvetkezskppen az adatfolyam akkor is tmaszkodhat egy megtlttt loklra, ha annak eredeti pldnya mr nem ltezik. Ha a bemeneti adatfolyam szmra be van lltva a boolalpha jelzbit (21.2.2, 21.4.1), a true s false rtkeket a truename() s a falsename() ltal visszaadott karakterlncok jellhetik, ms esetben a 0 s az 1. A numpunct _byname vltozata (D.4, D.4.1) is adott:
template <class Ch> class std::numpunct_byname : public numpunct<Ch> { /* ... */ };

D.4.2.2. Szmok kirsa Az tmeneti trba val rskor (21.6.4) a kimeneti adatfolyamok (ostream) a num_put jellemzre tmaszkodnak:
template <class Ch, class Out = ostreambuf_iterator<Ch> > class std::num_put : public locale::facet { public: typedef Ch char_type; typedef Out iter_type; explicit num_put(size_t r = 0); // a "v" rtk elhelyezse az "s" adatfolyam tmeneti trnak "b" pozcijra: Out put(Out b, ios_base& s, Ch fill, bool v) const; Out put(Out b, ios_base& s, Ch fill, long v) const; Out put(Out b, ios_base& s, Ch fill, unsigned long v) const; Out put(Out b, ios_base& s, Ch fill, double v) const; Out put(Out b, ios_base& s, Ch fill, long double v) const; Out put(Out b, ios_base& s, Ch fill, const void* v) const; static locale::id id; // facet-azonost objektum (D.2, D.3, D.3.1) protected: ~num_put(); }; // virtulis "do_" fggvnyek a nyilvnos fggvnyek szmra (lsd D.4.1)

Forrs: http://www.doksi.hu

1208

Fggelkek s trgymutat

Az Out kimeneti bejr (itertor) paramter azonostja, hogy a put() a szmrtket jell karaktereket hol helyezi el a kimeneti adatfolyam tmeneti trban (21.6.4). A put() rtke az a bejr (iterator), amely egy hellyel az utols karakter mg mutat. Jegyezzk meg, hogy a num_put alaprtelmezett vltozata (amelynek bejrjval ostreambuf_iterator<Ch> tpus karakterekhez lehet hozzfrni) a szabvnyos locale-ek (D.4) rsze. Ha ms vltozatot akarunk hasznlni, akkor azt magunknak kell elksztennk:
template<class Ch> class String_numput : public std::num_put<Ch, typename basic_string<Ch>::iterator> { public: String_numput() : num_put<Ch, typename basic_string<Ch>::iterator>(1) { } }; void f(int i, string& s, int pos) // "i" formzsa "s"-be, a "pos" pozcitl kezdve { String_numput<char> f; ios_base& xxx = cout; // a cout formzsi szablyainak hasznlata f.put(s.begin()+pos, xxx, , i); // "i" formzsa "s"-be }

Az ios_base paramterrel a formtumrl s a loklrl kaphatunk informcit. Pldul ha res helyeket kell kitltennk, az ios_base paramter ltal megadott fill karakter lesz felhasznlva. Az tmeneti tr, amelybe b-n keresztl runk, ltalban ahhoz az ostream-hez kapcsoldik, amelynek s a bzisosztlya. Az ios_base objektumokat nem knny ltrehozni, mert a formtummal kapcsolatban tbb dolgot is szablyoznak s ezeknek egysgesnek kell lennik, hogy a kimenet elfogadhat legyen. Kvetkezskppen az ios_base osztlynak nincs nyilvnos konstruktora (21.3.3). A put() fggvnyek szintn ios_base paramtereket hasznlnak az adatfolyam lokljnak lekrdezshez. A lokl hatrozza meg az elvlaszt karaktereket (D.4.2.1), a logikai rtkek szveges brzolst s a Ch-ra val talaktst. Pldul ha feltesszk, hogy a put() fggvny ios_base paramtere s, a put() fggvnyben ehhez hasonl kdot tallhatunk:
const locale& loc = s.getloc(); // ... wchar_t w = use_facet< ctype<char> >(loc).widen(c); // talakts char-rl Ch-ra // ... string pnt = use_facet< numpunct<char> >(loc).decimal_point(); // alaprtelmezs: . // ... string flse = use_facet< numpunct<char> >(loc).falsename(); // alaprtelmezs: "false"

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1209

A num_put<char>-hoz hasonl szabvnyos facet-eket a szabvnyos I/O adatfolyam-fggvnyek ltalban automatikusan hasznljk, gy a legtbb programoznak nem is kell tudnia rluk. rdemes azonban szemgyre venni, hogy a standard knyvtr fggvnyei hogyan hasznljk a jellemzket, mert jl mutatja, hogyan mkdnek a be- s kimeneti adatfolyamok, illetve a facet-ek. Mint mindig, a standard knyvtr most is rdekes programozsi eljrsokra mutat pldkat. A num_put felhasznlsval az ostream ksztje a kvetkezket rhatja:
template<class Ch, class Tr> ostream& std::basic_ostream<Ch,Tr>::operator<<(double d) { sentry guard(*this); // lsd 21.3.8 if (!guard) return *this; try { if (use_facet< num_put<Ch> >(getloc()).put(*this,*this,this->fill(),d).failed()) setstate(badbit); } catch (...) { handle_ioexception(*this); } return *this;

Itt sok minden trtnik. Az rszem (sentry) biztostja, hogy minden mvelet vgrehajtdik (21.3.8). Az ostream lokljt a getloc() tagfggvny meghvsval kapjuk meg, majd a loklbl a use_facet sablon fggvnnyel (D.3.1) kiszedjk a num_put jellemzt. Miutn ezt megtettk, meghvjuk a megfelel put() fggvnyeket az igazi munka elvgzshez. A put() els kt paramtert knnyen megadhatjuk, hiszen az ostream-bl ltrehozhatjuk az ostream_buf bejrt (19.2.6), az adatfolyamot pedig automatikusan ios_base bzisosztlyra alakthatjuk (21.2.1). A put() kimeneti bejr paramtert adja vissza. A bejrt egy basic_ostream-bl szerzi meg, gy annak tpusa ostreambuf_iterator. Kvetkezskppen a failed() (19.2.6.1) rendelkezsnkre ll a hibaellenrzshez s lehetv teszi szmunkra, hogy megfelelen bellthassuk az adatfolyam llapott. Nem hasznltuk a has_facet fggvnyt, mert a szabvnyos facet-ek (D.4) garantltan ott vannak minden loklban. Ha ez a garancia nem ll fenn, bad_cast kivtel kivltsra kerl sor (D.3.1).

Forrs: http://www.doksi.hu

1210

Fggelkek s trgymutat

A put() a do_put virtulis fggvnyt hvja meg. Kvetkezskppen lehet, hogy felhasznli kd hajtdik vgre s az operator<<()-nek fel kell kszlnie arra, hogy a fellrt do_put() ltal kivltott kivtelt kezelje. Tovbb lehet, hogy a num_put nem ltezik valamilyen karaktertpusra, gy a use_facet() az std::bad_cast kivtelt vlthatja ki (D.3.1). A beptett tpusokra, mint amilyen a double, a << viselkedst a C++ szabvny rja le. Kvetkezskppen nem az a krds, hogy a handle_ioexception() fggvnynek mit kell csinlnia, hanem az, hogyan csinlja azt, amit a szabvny elr. Ha a badbit jelzbit az ostream kivtel llapotra van lltva (21.3.6), a kivtel tovbbdobsra kerl sor, ms esetben a kivtel kezelse az adatfolyam llapotnak belltst s a vgrehajts folytatst jelenti. A badbit jelzbitet mindkt esetben az adatfolyam llapotra kell lltani (21.3.3):
template<class Ch, class Tr> void handle_ioexception(std::basic_ostream<Ch,Tr>& s) // a catch rszbl meghvva { if (s.exceptions()&ios_base::badbit) { try { s.setstate(ios_base::badbit); } catch(...) { } throw; // tovbbdobs } s.setstate(ios_base::badbit); // basic_ios::failure kivtelt vlthat ki }

A try blokk azrt szksges, mert a setstate() basic_ios::failure kivtelt vlthat ki (21.3.3, 21.3.6). Ha azonban a badbit a kivtel llapotra lltott, az operator<<()-nek tovbb kell dobnia azt a kivtelt, amely a handle_ioexception() meghvst okozta (nem pedig egyszeren basic_ios::failure kivtelt kell kivltania). A <<-t gy kell megvalstani a beptett tpusokra, pldul a double-ra, hogy kzvetlenl az adatfolyam tmeneti trba rjunk. Ha a <<-t felhasznli tpusra rjuk meg, az ebbl ered nehzsgeket gy kerlhetjk el, hogy a felhasznli tpusok kimenett mr meglv tpusok kimenetvel fejezzk ki (D.3.2).

D.4.2.3. Szmok bevitele A bemeneti adatfolyamok (istream) a num_get jellemzre tmaszkodnak az tmeneti trbl (21.6.4) val olvasshoz:
template <class Ch, class In = istreambuf_iterator<Ch> > class std::num_get : public locale::facet { public: typedef Ch char_type; typedef In iter_type;

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1211

explicit num_get(size_t r = 0); // olvass [b:e)-bl v-be, az s-beli formzsi szablyok hasznlatval; // hibajelzs az r belltsval: In get(In b, In e, ios_base& s, ios_base::iostate& r, bool& v) const; In get(In b, In e, ios_base& s, ios_base::iostate& r, long& v) const; In get(In b, In e, ios_base& s, ios_base::iostate& r, unsigned short& v) const; In get(In b, In e, ios_base& s, ios_base::iostate& r, unsigned int& v) const; In get(In b, In e, ios_base& s, ios_base::iostate& r, unsigned long& v) const; In get(In b, In e, ios_base& s, ios_base::iostate& r, float& v) const; In get(In b, In e, ios_base& s, ios_base::iostate& r, double& v) const; In get(In b, In e, ios_base& s, ios_base::iostate& r, long double& v) const; In get(In b, In e, ios_base& s, ios_base::iostate& r, void*& v) const; static locale::id id; protected: ~num_get(); }; // facet-azonost objektum (D.2, D.3, D.3.1)

// virtulis "do_" fggvnyek a nyilvnos fggvnyek szmra (lsd D.4.1)

A num_get szerkezete alapveten a num_put-hoz (D.4.2.2) hasonl. Mivel inkbb olvas, mint r, a get()-nek egy bejrprra van szksge, az olvass clpontjt meghatroz paramter pedig egy referencia. Az iostate tpus r vltoz gy van belltva, hogy tkrzze az adatfolyam llapott. Ha a kvnt tpus rtket nem lehet beolvasni, az r-ben a failbit belltsra kerl sor, ha elrtk a bemenet vgt, az eofbit-re. A bemeneti mveletek az r-t arra hasznljk, hogy eldntsk, hogyan lltsk be az adatfolyam llapott. Ha nem trtnt hiba, a beolvasott rtk a v-n keresztl rtkl addik; egybknt a v vltozatlan marad. Az istream ksztje a kvetkezket rhatja:
template<class Ch, class Tr> istream& std::basic_istream<Ch,Tr>::operator>>(double& d) { sentry guard(*this); // lsd 21.3.8 if (!guard) return *this; iostate state = 0; // j istreambuf_iterator<Ch> eos; double dd; try { use_facet< num_get<Ch> >(getloc()).get(*this,eos,*this,state,dd); }

Forrs: http://www.doksi.hu

1212

Fggelkek s trgymutat

catch (...) { handle_ioexception(*this); // lsd D.4.2.2 return *this; } if (state==0 || state==eofbit) d = dd; // d rtknek belltsa csak akkor, // ha a get() sikerrel jrt setstate(state); return *this;

Az istream szmra megengedett kivteleket hiba esetn a setstate() fggvny vltja ki (21.3.6). Egy numpunct jellemzt mint amilyen a my_numpunct a D.4.2 pontban megadva nem szabvnyos elvlaszt karaktereket hasznlva is olvashatunk:
void f() { cout << "Els stlus: " int i1; double d1; cin >> i1 >> d1; // beolvass a szabvnyos "12345678" forma hasznlatval locale loc(locale::classic(), new My_punct); cin.imbue(loc); cout << "Msodik stlus: " int i2; double d2; cin >> i1 >> d2; // beolvass a "12 345 678" forma hasznlatval

Ha igazn ritkn hasznlt szmformtumokat szeretnnk beolvasni, fell kell rnunk a do_get() fggvnyt. Megadhatunk pldul egy num_get facet-et, amely rmai szmokat olvas be ( XXI vagy MM, D.6[15]).

D.4.3. Pnzrtkek be- s kivitele


A pnzsszegek formzsnak mdja az egyszer szmok formzshoz hasonlt (D.4.2), az elbbinl azonban a kulturlis eltrsek jelentsge nagyobb. A negatv sszegeket (vesztesg, tartozs, mondjuk -1,25) pldul egyes helyeken pozitv szmokknt, zrjelben kell feltntetni (1,25). Az is elfordulhat, hogy a negatv sszegek felismersnek megknnytsre szneket kell hasznlnunk.

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1213

Nincs szabvnyos pnz tpus. Ehelyett kifejezetten a pnz facet-eket kell hasznlnunk az olyan szmrtkeknl, amelyekrl tudjuk, hogy pnzsszegeket jelentenek:
class Money { // egyszer tpus pnzsszegek trolsra long int amount; public: Money(long int i) : amount(i) { } operator long int() const { return amount; } }; // ... void f(long int i) { cout << "rtk= " << i << " sszeg= " << Money(i) << endl; }

Ezen facet-ek feladata az, hogy jelentsen megknnytsk az olyan kimeneti mveletek megrst a Money tpusra, melyek az sszeget a helyi szoksoknak megfelelen rjk ki (lsd D.4.3.2-t). A kimenet a cout lokljtl fggen vltozik:
rtk= rtk= rtk= rtk= rtk= 1234567 sszeg= $12345.67 1234567 sszeg= 12345,67 DKK -1234567 sszeg= $-12345.67 -1234567 sszeg= -$12345.67 -1234567 sszeg= (CHF12345,67)

A pnzrtkek esetben rendszerint alapvet a legkisebb pnzegysgre is kiterjed pontossg. Kvetkezskppen n azt a szokst kvetem, hogy inkbb a fillrek (penny, re, cent stb.) szmt brzolom egsz rtkekkel, nem a forintokt (font, korona, dnr, eur stb.). Ezt a megoldst a money_punct frac_digits() fggvnye tmogatja (D.4.3.1). A tizedes-elvlasztt a decimal_point() adja meg. A money_base jellemz ltal lert formtumon alapul bemenetet s kimenetet kezel fggvnyeket a money_get s money_put facet-ek biztostjk. A be- s kimeneti formtum szablyozsra s a pnzrtkek trolsra egy egyszer Money tpust hasznlhatunk. Az els esetben a pnzsszegek trolsra hasznlt (ms) tpusokat kirs eltt a Money tpusra alaktjuk, a beolvasst pedig szintn Money tpus vltozkba vgezzk, mieltt az rtkeket ms tpusra alaktannk. Kevesebb hibval jr, ha a pnzsszegeket kvetkezetesen a Money tpusban troljuk: gy nem feledkezhetnk meg arrl, hogy egy rtket Money tpusra alaktsunk, mieltt kirnnk s nem kapunk bemeneti hibkat, ha a lokltl fggetlenl prblunk pnzsszegeket beolvasni. Lehetsges azonban, hogy a Money tpus bevezetse kivitelezhetetlen, ha a rendszer nincs felksztve r. Ilyen esetekben szksges a Money-konverzikat alkalmazni az olvas s r mveleteknl.

Forrs: http://www.doksi.hu

1214

Fggelkek s trgymutat

D.4.3.1. A pnzrtkek formtuma A pnzsszegek megjelentst szablyoz moneypunct termszetesen a kznsges szmok formtumt megad numpunct facet-re (D.4.2.1) hasonlt:
class std::money_base { public: enum part { none, space, symbol, sign, value }; struct pattern { char field[4]; }; };

// az elrendezs rszei // elrendezs

template <class Ch, bool International = false> class std::moneypunct : public locale::facet, public money_base { public: typedef Ch char_type; typedef basic_string<Ch> string_type; explicit moneypunct(size_t r = 0); Ch decimal_point() const; // . a classic() loklban Ch thousands_sep() const; // , a classic() loklban string grouping() const; // "" a classic() loklban, jelentse: nincs csoportosts string_type curr_symbol() const; string_type positive_sign() const; string_type negative_sign() const; // "$" a classic() loklban // "" a classic() loklban // "-" a classic() loklban

int frac_digits() const; // szmjegyek szma a tizedesvessz utn; 2 a classic() loklban pattern pos_format() const; // { symbol, sign, none, value } a classic() loklban pattern neg_format() const; // { symbol, sign, none, value } a classic() loklban static const bool intl = International; // a "nemzetkzi" pnzformtum hasznlata

static locale::id id; // facet-azonost objektum (D.2, D.3, D.3.1) protected: ~moneypunct(); }; // virtulis "do_" fggvnyek a nyilvnos fggvnyek szmra (lsd D.4.1)

A moneypunct szolgltatsait elssorban a money_put s money_get jellemzk ksztinek szntk (D.4.3.2, D.4.3.3). A decimal_point(), thousands_sep(), s grouping() tagok gy viselkednek, mint a velk egyenrtk fggvnyek a numpunct facet-ben.

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1215

A curr_symbol(), positive_sign() s negative_sign() tagok rendre a valutajelet ($, , FrF, DKr), a pluszjelet s a mnuszjelet jell karakterlncot adjk vissza. Ha az International sablonparamter rtke true volt, az intl tag szintn true lesz s a valutajelek nemzetkzi brzolsa lesz hasznlatos. A nemzetkzi brzols egy ngy karakterbl ll karakterlnc:
"USD " "DKK " "EUR "

Az utols karakter ltalban szkz. A hrom bets valuta-azonostt az ISO-4217 szabvny rja le. Ha az International rtke false, helyi valutajelet $, vagy lehet hasznlni. A pos_format() s neg_format() ltal visszaadott minta (pattern) ngy rszbl (part) ll, amelyek a szmrtk, a valutajel, az eljel s az reshely megjelentsnek sorrendjt adjk meg. A leggyakoribb formtumok ezt a mintt kvetik:
+$ 123.45 $+123.45 $123.45 $123.45-123.45 DKK ($123.45) (123.45DKK) // { sign, symbol, space, value }, ahol a positive_sign() visszatrsi rtke "+" // { symbol, sign, value, none }, ahol a positive_sign() visszatrsi rtke "+" // { symbol, sign, value, none }, ahol a positive_sign() visszatrsi rtke "" // { symbol, value, sign, none } // { sign, value, space, symbol } // { sign, symbol, value, none }, ahol a negative_sign() visszatrsi rtke "()" // { sign, value, symbol, none }, ahol a negative_sign() visszatrsi rtke "()"

A negatv szmok zrjeles brzolst a negative_sign() fggvny visszatrsi rtkeknt a () karakterekbl ll karakterlncot definilva biztostjuk. Az eljel-karakterlnc els karaktere oda kerl, ahol a sign (eljel) tallhat a mintban, a maradk pedig a minta tbbi rsze utn kvetkezik. Ezt a megoldst leggyakrabban ahhoz a szoksos pnzgyi jellshez hasznljk, miszerint a negatv sszegeket zrjelben tntetik fel, de msra is fel lehet hasznlni:
-$123.45 // { sign, symbol, value, none }, ahol a negative_sign() visszatrsi rtke "-" *$123.45 silly // { sign, symbol, value, none }, ahol a negative_sign() // visszatrsi rtke "* silly"

Az eljel, rtk s szimblum (sign, value, symbol) rtkek csak egyszer szerepelhetnek a mintban. A maradk rtk space (reshely) vagy none (semmi) lehet. Ahol space szerepel, oda legalbb egy reshely karakternek kell kerlnie, a none nulla vagy tbb reshely karaktert jelent (kivve ha a none a minta vgn szerepel).

Forrs: http://www.doksi.hu

1216

Fggelkek s trgymutat

Ezek a szigor szablyok nhny ltszlag sszer mintt is megtiltanak:


pattern pat = { sign, value, none, none }; // hiba: a symbol nincs megadva

A decimal_point() helyt a frac_digits() fggvny jelli ki. A pnzsszegeket ltalban a legkisebb valutaegysggel brzoljk (D.4.3), ami jellemzen a f egysg szzadrsze (pldul cent s dollr), gy a frac_digits() rtke ltalban 2. Kvetkezzen egy egyszer formtum, facet-knt megadva:
class My_money_io : public moneypunct<char,true> { public: explicit My_money_io(size_t r = 0) : moneypunct<char,true>(r) { } char_type do_decimal_point() const { return .; } char_type do_thousands_sep() const { return ,; } string do_grouping() const { return "\003\003\003"; } string_type do_curr_symbol() const { return "USD "; } string_type do_positive_sign() const { return ""; } string_type do_negative_sign() const { return "()"; } int do_frac_digits() const { return 2; } // 2 szmjegy a tizedesvessz utn

};

pattern do_pos_format() const { static pattern pat = { sign, symbol, value, none }; return pat; } pattern do_neg_format() const { static pattern pat = { sign, symbol, value, none }; return pat; }

Ezt a facet-et hasznljuk a Money albbi be- s kimeneti mveleteiben is (D.4.3.2 s D.4.3.3). A moneypunct _byname vltozata (D.4, D.4.1) is adott:
template <class Ch, bool Intl = false> class std::moneypunct_byname : public moneypunct<Ch, Intl> { /* ... */ };

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1217

D.4.3.2. Pnzrtkek kirsa A money_put a moneypunct ltal meghatrozott formtumban rja ki a pnzsszegeket. Pontosabban, a money_put olyan put() fggvnyeket nyjt, amelyek megfelelen formzott karakter-brzolsokat tesznek egy adatfolyam tmeneti trba:
template <class Ch, class Out = ostreambuf_iterator<Ch> > class std::money_put : public locale::facet { public: typedef Ch char_type; typedef Out iter_type; typedef basic_string<Ch> string_type; explicit money_put(size_t r = 0); // a "v" rtk elhelyezse az tmeneti tr "b" pozcijra: Out put(Out b, bool intl, ios_base& s, Ch fill, long double v) const; Out put(Out b, bool intl, ios_base& s, Ch fill, const string_type& v) const; static locale::id id; // facet-azonost objektum (D.2, D.3, D.3.1) protected: ~money_put(); }; // virtulis "do_" fggvnyek a nyilvnos fggvnyek szmra (lsd D.4.1)

A b, s, fill s v paramterek ugyanarra hasznlatosak, mint a num_put jellemz put() fggvnyeiben (D.4.3.2.2). Az intl paramter jelzi, hogy a szabvnyos ngykarakteres nemzetkzi valutaszimblum vagy helyi valutajel hasznlatos-e (D.4.3.1). Ha adott a money_put jellemz, a Money osztly szmra(D.4.3) kimeneti mveletet rhatunk:
ostream& operator<<(ostream& s, Money m) { ostream::sentry guard(s); // lsd 21.3.8 if (!guard) return s; try { const money_put<char>& f = use_facet< money_put<char> >(s.getloc()); if (m==static_cast<long double>(m)) { // m long double-knt brzolhat if (f.put(s,true,s,s.fill(),m).failed()) s.setstate(ios_base::badbit); } else { ostringstream v; v << m; // karakterlnc-brzolsra alakt

Forrs: http://www.doksi.hu

1218

Fggelkek s trgymutat

} catch (...) { handle_ioexception(s); } return s;

if (f.put(s,true,s,s.fill(),v.str()).failed()) s.setstate(ios_base::badbit);

// lsd D.4.2.2

Ha a long double nem elg pontos a pnzrtk brzolshoz, az rtket karakterlncc alaktjuk s azt rjuk ki az ilyen paramtert vr put() fggvnnyel.

D.4.3.3. Pnzrtkek beolvassa A money_get a moneypunct ltal meghatrozott formtumnak megfelelen olvassa be a pnzsszegeket. Pontosabban, a money_get olyan get() fggvnyeket nyjt, amelyek a megfelelen formzott karakteres brzolst olvassk be egy adatfolyam tmeneti trbl:
template <class Ch, class In = istreambuf_iterator<Ch> > class std::money_get : public locale::facet { public: typedef Ch char_type; typedef In iter_type; typedef basic_string<Ch> string_type; explicit money_get(size_t r = 0); // olvass [b:e)-bl v-be, az s-beli formzsi szablyok hasznlatval; // hibajelzs az r belltsval: In get(In b, In e, bool intl, ios_base& s, ios_base::iostate& r, long double& v) const; In get(In b, In e, bool intl, ios_base& s, ios_base::iostate& r, string_type& v) const; static locale::id id; protected: ~money_get(); }; // facet-azonost objektum (D.2, D.3, D.3.1)

// virtulis "do_" fggvnyek a nyilvnos fggvnyek szmra (lsd D.4.1)

A b, s, fill, s v paramterek ugyanarra hasznlatosak, mint a num_get jellemz get() fggvnyeiben (D.4.3.2.2). Az intl paramter jelzi, hogy a szabvnyos ngykarakteres nemzetkzi valutaszimblum vagy helyi valutajel hasznlatos-e (D.4.3.1).

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1219

Megfelel money_get s money_put jellemzprral olyan formban adhatunk kimenetet, amelyet hiba s adatveszts nlkl lehet visszaolvasni:
int main() { Money m; while (cin>>m) cout << m << "\n"; }

Ez az egyszer program kimenett el kell, hogy fogadja bemenetknt is. St, ha a programot msodszor is lefuttatjuk az els futtats eredmnyvel, a kimenetnek meg kell egyeznie a program eredeti bemenetvel. A Money osztly szmra a kvetkez megfelel bemeneti mvelet lehet:
istream& operator>>(istream& s, Money& m) { istream::sentry guard(s); // lsd 21.3.8 if (guard) try { ios_base::iostate state = 0; // j istreambuf_iterator<char> eos; string str; use_facet< money_get<char> >(s.getloc()).get(s,eos,true,state,str); if (state==0 || state==ios_base::eofbit) { // csak akkor llt be rtket, // ha a get() sikerrel jrt

} } catch (...) { handle_ioexception(s); } return s;

long int i = strtol(str.c_str(),0,0); if (errno==ERANGE) state |= ios_base::failbit; else m = i; // csak akkor lltja be m rtkt, ha az talakts long int-re sikerlt s.setstate(state);

// lsd D.4.2.2

Forrs: http://www.doksi.hu

1220

Fggelkek s trgymutat

D.4.4. Dtum s id beolvassa s kirsa


Sajnos a C++ standard knyvtra nem nyjt megfelel dtum tpust, de a C standard knyvtrbl alacsonyszint eszkzket rklt a dtumok s idtartomnyok kezelshez. Ezek a C-beli eszkzk szolglnak a C++ rendszerfggetlen idkezel eszkzeinek alapjul. A kvetkezkben azt mutatjuk be, hogyan igazthat a dtum s id megjelentse a loklhoz, tovbb pldkat adunk arra, hogyan illeszthet be egy felhasznli tpus (Date) az iostream (21. fejezet) s locale (D.2) ltal nyjtott szerkezetbe. A Date tpuson keresztl olyan eljrsokkal is megismerkednk, amelyek segthetik az idkezelst, ha nem ll rendelkezsnkre a Date tpus.

D.4.4.1. rk s idztk A legtbb rendszer a legalacsonyabb szinten rendelkezik egy finom idztvel. A standard knyvtr a clock() fggvnyt bocstja rendelkezsnkre, amely megvalsts-fgg clock_t tpus rtkkel tr vissza. A clock() eredmnye a CLOCK_PER_SEC makr segtsgvel szablyozhat. Ha nincs hozzfrsnk megbzhat idmr eszkzhz, akkor gy mrhetjk meg egy ciklus idejt:
int main(int argc, char* argv[]) { int n = atoi(argv[1]); // 6.1.7 // 20.4.1

clock_t t1 = clock(); if (t1 == clock_t(-1)) { // clock_t(-1) jelentse: "a clock() nem mkdik" cerr << "Sajnos nincs rnk.\n"; exit(1); } for (int i = 0; i < n; i++) do_something(); // idzt ciklus

clock_t t2 = clock(); if (t2 == clock_t(-1)) { cerr << "Tlcsorduls.\n"; exit(2); } cout << "A do_something() " << n << " alkalommal val vgrehajtsa " << double(t2-t1)/CLOCKS_PER_SEC << " msodpercet vett ignybe" << " (mrsi rzkenysg: " << CLOCKS_PER_SEC << " per msodperc).\n";

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1221

Az oszts eltt vgrehajtott double(t2-t1) talakts azrt szksges, mert a clock_t lehet, hogy egsz tpus. Az, hogy a clock() mikor kezd futni, az adott nyelvi vltozattl fgg; a fggvny az egyes programrszek futsi idejnek mrsre val. A clock() ltal visszaadott t1 s t2 rtkeket alapul vve a double(t2-t1)/CLOCK_PER_SEC a rendszer legjobb kzeltse kt hvs kzt eltelt idre (msodpercben). Ha az adott feldolgozegysgre nincs megadva a clock() fggvny vagy ha az id tl hossz ahhoz, hogy mrhet legyen, a clock() a clock_t(-1) rtket adja vissza. A clock() fggvny a msodperc tredktl legfeljebb nhny msodpercig terjed idtartomnyok mrsre val. Pldul ha a clock_t egy 32 bites eljeles egsz s a CLOCK_PER_SEC rtke 1 000 000, a clock() fggvnnyel az idt 0-tl valamivel tbb mint 2000 msodpercig (krlbell fl rig) mrhetjk (a msodperc milliomod rszben). A programokrl lnyegi mrseket kszteni nem knny. A gpen fut tbbi program jelentsen befolysolhatja az adott program futsi idejt, nehz megjsolni a gyorsttrazs s az utastscsvek hatsait s az algoritmusok nagymrtkben fgghetnek az adatoktl is. Ha meg akarjuk mrni egy program futsi idejt, futtassuk tbbszr s vegyk hibsnak azokat az eredmnyeket, amelyek jelentsen eltrnek a tbbitl. A hosszabb idtartomnyok s a naptri id kezelsre a standard knyvtr a time_t tpust nyjtja, amely egy idpontot brzol; valamint a tm szerkezetet, amely az idpontokat rszeikre bontja:
typedef megvalsts_fgg time_t; // megvalsts-fgg aritmetikai tpus (4.1.1) // kpes idtartomny brzolsra, // ltalban 32 bites egsz struct tm { int tm_sec; // a perc msodpercei [0,61]; a 60 s a 61 "szk-msodpercek" int tm_min; // az ra percei [0,59] int tm_hour; // a nap ri [0,23] int tm_mday; // a hnap napjai [1,31] int tm_mon; // az v hnapjai [0,11]; a 0 jelentse "janur" (figyelem: NEM [1:12]) int tm_year; // vek 1900 ta; a 0 jelentse "1900", a 102- "2002" int tm_wday; // napok vasrnap ta [0,6]; a 0 jelentse "vasrnap" int tm_yday; // napok janur 1 ta [0,365]; a 0 jelentse "janur 1." int tm_isdst; // nyri idszmts jelz };

A szabvny csak azt biztostja, hogy a tm rendelkezik a fenti emltett int tpus tagokkal, azt nem, hogy a tagok ilyen sorrendben szerepelnek vagy hogy nincsenek ms tagok.

Forrs: http://www.doksi.hu

1222

Fggelkek s trgymutat

A time_t s tm tpusok, valamint a hozzjuk kapcsold szolgltatsok a <ctime> s <time.h> fejllomnyokban tallhatk:
clock_t clock(); time_t time(time_t* pt); double difftime(time_t t2, time_t t1); tm* localtime(const time_t* pt); tm* gmtime(const time_t* pt); time_t mktime(tm* ptm); char* asctime(const tm* ptm); // rajelek szma a program indulsa ta // rvnyes naptri id // t2t1 msodpercben

// *pt rtke helyi id szerint // *pt rtke greenwichi kzpid (GMT) szerint, vagy 0 // (hivatalos neve: Coordinated Universal Time, UTC) // *ptm rtke time_t formban, vagy time_t(-1) // *ptm egy C stlus karakterlnccal brzolva // pl. "Sun Sep 16 01:03:52 1973\n"

char* ctime(const time_t* t) { return asctime(localtime(t)); }

Vigyzzunk: mind a localtime(), mind a gmtime() statikusan lefoglalt objektumra mutat *tm tpus rtket ad vissza, vagyis mindkt fggvny kvetkez meghvsa meg fogja vltoztatni az objektum rtkt. Ezrt rgtn hasznljuk fel a visszatrsi rtket vagy msoljuk a tm-et olyan memriahelyre, amit mi felgyelnk. Ugyangy az asctime() fggvny is statikusan lefoglalt karaktertmbre hivatkoz mutatt ad vissza. A tm tpus legalbb tzezer vnyi dtumot (ez a legkisebb egsz esetben krlbell a [-32000,32000] tartomny) kpes brzolni. A time_t azonban ltalban 32 bites (eljeles) long int. Ha msodperceket szmolunk, a time_t nem sokkal tbb, mint 68 vet tud brzolni valamilyen 0. vtl mindkt irnyban. Az alapv rendszerint 1970, a hozz tartoz alapid pedig janur 1., 0:00 GMT (UTC). Ha a time_t 32 bites eljeles egsz, akkor 2038ban futunk ki az idbl, hacsak nem terjesztjk ki a time_t-t egy nagyobb egsz tpusra, ahogy azt mr nhny rendszeren meg is tettk. A time_t alapveten arra val, hogy a jelenhez kzeli idt brzoljuk, ezrt nem szabad elvrnunk, hogy kpes legyen az [1902,2038] tartomnyon kvli dtumokat is jellni. Ami mg ennl is rosszabb, nem minden idkezel fggvny kezeli azonos mdon a negatv szmokat. A hordozhatsg miatt az olyan rtkeknek, amelyeket tm-knt s time_t-knt is brzolni kell, az [1920,2038] tartomnyba kell esnik. Azoknak, akik az 1970-tl 2038-ig terjed idkereten kvli dtumokat szeretnnek brzolni, tovbbi eljrsokat kell kidolgozniuk ahhoz, hogy ezt megtehessk.

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1223

Ennek egyik kvetkezmnye, hogy az mktime() hibt eredmnyezhet. Ha az mktime() paramtert nem lehet time_t-knt brzolni, a fggvny a time_t(-1) hibajelzst adja vissza. Ha van egy sokig fut programunk, gy mrhetjk le futsi idejt:
int main(int argc, char* argv[]) // 6.1.7 { time_t t1 = time(0); do_a_lot(argc,argv); time_t t2 = time(0); double d = difftime(t2,t1); cout << "A do_a_lot() vgrehajtsa" << d << " msodpercig tartott.\n"; }

Ha a time() paramtere nem 0, a fggvny eredmnyeknt kapott id rtkl addik a fggvny time_t tpusra mutat paramternek is. Ha a naptri id nem elrhet (mondjuk valamilyen egyedi processzoron), akkor a time_t(-1) rtk addik vissza. A mai dtumot a kvetkezkppen prblhatjuk meg vatosan megllaptani:
int main() { time_t t; if (time(&t) == time_t(-1)) { // a time_t(1) jelentse: "a time() nem mkdik" cerr << "Az id nem llapthat meg.\n"; exit(1); } tm* gt = gmtime(&t); cout << gt->tm_mon+1 << '/' << gt->tm_mday << '/' << 1900+gt->tm_year << endl;

D.4.4.2. Egy dtum osztly Amint a 10.3 pontban emltettk, nem valszn, hogy egyetlen Date tpus minden ignyt ki tud szolglni. A dtum felhasznlsa tbbfle megvalstst ignyel s a XIX. szzad eltt a naptrak nagyban fggtek a trtnelem szeszlyeitl. Pldaknt azonban a 10.3-hoz hasonlan kszthetnk egy Date tpust, a time_t tpust felhasznlva:
class Date { public: enum Month { jan=1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec };

Forrs: http://www.doksi.hu

1224

Fggelkek s trgymutat

class Bad_date {}; Date(int dd, Month mm, int yy); Date(); friend ostream& operator<<(ostream& s, const Date& d); // ... private: time_t d; // szabvnyos dtum- s idbrzols }; Date::Date(int dd, Month mm, int yy) { tm x = { 0 }; if (dd<0 || 31<dd) throw Bad_date(); // tlegyszerstett: lsd 10.3.1 x.tm_mday = dd; if (mm<jan || dec<mm) throw Bad_date(); x.tm_mon = mm-1; // a tm_mon 0 alap x.tm_year = yy-1900; // a tm_year 1900 alap d = mktime(&x); } Date::Date() { d = time(0); // alaprtelmezett Date: a mai nap if (d == time_t(-1)) throw Bad_date(); }

A feladat az, hogy a << s >> opertorokat a Date tpusra loklfgg mdon valstsuk meg.

D.4.4.3. Dtum s id kirsa A num_put facet-hez (D.4.2) hasonlan a time_put is put() fggvnyeket ad, hogy bejrkon keresztl az tmeneti trakba rhassunk:
template <class Ch, class Out = ostreambuf_iterator<Ch> > class std::time_put : public locale::facet { public: typedef Ch char_type; typedef Out iter_type; explicit time_put(size_t r = 0);

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1225

// rs az s adatfolyam tmeneti trba b-n keresztl, az fmt formtum hasznlatval Out put(Out b, ios_base& s, Ch fill, const tm* t, const Ch* fmt_b, const Ch* fmt_e) const; Out put(Out b, ios_base& s, Ch fill, const tm* t, char fmt, char mod = 0) const { return do_put(b,s,fill,t,fmt,mod); } static locale::id id; // facet-azonost objektum (D.2, D.3, D.3.1) protected: ~time_put(); }; virtual Out do_put(Out, ios_base&, Ch, const tm*, char, char) const;

A put(b,s,fill,t,fmt_b,fmt_e) a t-ben lv dtumot b-n keresztl az s adatfolyam tmeneti trba helyezi. A fill karakterek a kitltshez hasznlatosak, ha az szksges. A kimeneti formtumot a printf() formzhoz hasonl (fmt_b,fmt_e) formzsi karakterlnc (vagy formtumvezrl) hatrozza meg. A tnyleges kimenethez a printf()-hez hasonl (21.8) formtum hasznlatos, ami a kvetkez klnleges formtumvezrlket tartalmazhatja: %a %A %b %B %c %d %H %I %j %m %M %p %S %U %w %W %x %X %y %Y %Z A ht napjnak rvidtett neve (pl. Szo) A ht napjnak teljes neve (pl. Szombat) A hnap rvidtett neve (pl. Feb) A hnap teljes neve (pl. Februr) A dtum s id (pl. Szo Feb 06 21:46:05 1999) A hnap napja [01,31] (pl. 06) ra [00,23] (pl. 21) ra [01,12] (pl. 09) Az v napja [001,366] (pl. 037) Az v hnapja [01,12] (pl. 02) Perc [00,59] (pl. 48) Dleltt/dlutn (am./pm. de./du.) jelzse a 12 rs rhoz (pl. PM) Msodperc [00,61] (pl. 48) Az v hete [00,53] vasrnappal kezdden (pl. 05): az 1. ht az els vasrnappal kezddik A ht napja [0,6]: a 0 jelenti a vasrnapot (pl. 6) Az v hete [00,53] htfvel kezdden (pl. 05): az 1. ht az els htfvel kezddik Dtum (pl. 02/06/99) Id (pl.21:48:40) v az vszzad nlkl [00,99] (pl. 99) v (pl.1999) Az idzna jelzse (pl. EST), ha az idzna ismert

Forrs: http://www.doksi.hu

1226

Fggelkek s trgymutat

Ezeket az igen rszletes formz szablyokat a bvthet I/O rendszer paramtereiknt hasznlhatjuk, de mint minden egyedi jellst, ezeket is clszerbb (s knyelmesebb) csak eredeti feladatuk elltsra alkalmazni. A fenti formz utastsokon tl a legtbb C++-vltozat tmogatja az olyan mdostkat (modifier), mint amilyen a mezszlessget (21.8) meghatroz egsz szm (%10X). Az id- s dtumformtumok mdosti nem kpezik rszt a C++ szabvnynak, de nhny platformszabvny, mint a POSIX, ignyelheti azokat. Kvetkezskppen a mdostkat nehz elkerlni, mg akkor is, ha nem tkletesen hordozhatk. A <ctime> vagy <time.h> fejllomnyban tallhat az sprintf()-hez (21.8) hasonl strftime() fggvny a kimenetet az id- s dtumformz utastsok felhasznlsval hozza ltre:
size_t strftime(char* s, size_t max, const char* format, const tm* tmp);

A fggvny legfeljebb max karaktert tesz a *tmp-bl s a format-bl a *s-be, a format formznak megfelelen:
int main() { const int max = 20; // hanyag: abban bzik, hogy az strftime() // soha sem eredmnyez 20-nl tbb karaktert char buf[max]; time_t t = time(0); strftime(buf,max,"%A\n",localtime(&t)); cout << buf; }

A fenti program az alaprtelmezett classic() loklban (D.2.3) egy szerdai napon Wednesday-t fog kirni, dn loklban onsdag-ot. Azok a karakterek, melyek nem rszei a meghatrozott formtumnak, mint a pldban az jsor karakter, egyszeren bemsoldnak az els (s) paramterbe. Amikor a put() azonost egy f formtumkaraktert (s egy nem ktelez m mdostt), meghvja a do_put() virtulis fggvnyt, hogy az vgezze el a tnyleges formzst: do_put(b,s,fill,t,f,m).

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1227

A put(b,s,fill,t,f,m) hvs a put() egyszerstett formja, ahol a formtumkarakter (f) s a mdost (m) pontosan meghatrozott. Ezrt a
const char fmt[] = "%10X"; put(b, s, fill, t, fmt, fmt+sizeof(fmt));

hvst a kvetkez alakra lehet rvidteni:


put(b, s, fill, t, X, 10);

Ha egy formtum tbbjtos karaktereket tartalmaz, annak az alaprtelmezett llapotban (D.4.6) kell kezddnie s vgzdnie. A put() fggvnyt felhasznlhatjuk arra is, hogy a Date szmra lokltl fgg kimeneti mveletet adjunk meg:
ostream& operator<<(ostream& s, const Date& d) { ostream::sentry guard(s); // lsd 21.3.8 if (!guard) return s; tm* tmp = localtime(&d.d); try { if (use_facet< time_put<char> >(s.getloc()).put(s,s,s.fill(),tmp,x).failed()) s.setstate(ios_base::failbit); } catch (...) { handle_ioexception(s); // lsd D.4.2.2 } return s;

Mivel nincs szabvnyos Date tpus, nem ltezik alaprtelmezett formtum a dtumok be- s kivitelre. Itt gy hatroztam meg a %x formtumot, hogy formzknt az x karaktert adtam t. Mivel a get_time() fggvny (D.4.4.4) alaprtelmezett formtuma a %x, valsznleg ez ll legkzelebb a szabvnyhoz. A D.4.4.5 pontban pldt is lthatunk arra, hogyan hasznlhatunk ms formtumokat.

Forrs: http://www.doksi.hu

1228

Fggelkek s trgymutat

D.4.4.4. Dtum s id beolvassa Mint mindig, a bemenet trkksebb, mint a kimenet. Amikor rtk kirsra runk kdot, gyakran klnbz formtumok kzl vlaszthatunk. A beolvassnl emellett a hibkkal is foglalkoznunk kell s nha szmtanunk kell arra, hogy szmos formtum lehetsges. A dtum s id beolvasst a time_get jellemzn keresztl kezeljk. Az alaptlet az, hogy egy lokl time_get facet-je el tudja olvasni a time_put ltal ltrehozott idket s dtumokat. Szabvnyos dtum- s idtpusok azonban nincsenek, ezrt a programoz egy locale-t hasznlhat arra, hogy a kimenetet klnfle formtumoknak megfelelen hozza ltre. A kvetkez brzolsokat pldul mind ltre lehet hozni egyetlen kimeneti utastssal, gy, hogy a time_put jellemzt (D.4.4.5) klnbz loklokbl hasznljuk:
January 15th 1999 Thursday 15th January 1999 15 Jan 1999AD Thurs 15/1/99

A C++ szabvny arra biztat, hogy a time_get elksztsnl gy fogadjuk el a dtum- s idformtumokat, ahogy azt a POSIX s ms szabvnyok elrjk. A problma az, hogy nehz szabvnyostani a dtum s id beolvassnak brmilyen mdjt, amely egy adott kultrban szablyos. Blcsebb ksrletezni s megnzni, mit nyjt egy adott locale (D.6[8]). Ha egy formtum nem elfogadott, a programoz msik, megfelel time_get facet-et kszthet. Az id beolvassra hasznlt szabvnyos time_get a time_base osztlybl szrmazik:
class std::time_base { public: enum dateorder { no_order, // nincs sorrend, esetleg tbb elem is van (pl. a ht napja) dmy, // nap, hnap, v sorrend mdy, // hnap, nap, v sorrend ymd, // v, hnap, nap sorrend ydm // v, nap, hnap sorrend }; };

Ezt a felsorolst arra hasznlhatjuk, hogy egyszerstsk a dtumformtumok elemzst.

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1229

A num_get -hez hasonlan a time_get is egy bemeneti bejrpron keresztl fr hozz tmeneti trhoz:
template <class Ch, class In = istreambuf_iterator<Ch> > class time_get : public locale::facet, public time_base { public: typedef Ch char_type; typedef In iter_type; explicit time_get(size_t r = 0); dateorder date_order() const { return do_date_order(); } // olvass [b:e)-bl d-be, az s-beli formzsi szablyok hasznlatval; hibajelzs az r // belltsval: In get_time(In b, In e, ios_base& s, ios_base::iostate& r, tm* d) const; In get_date(In b, In e, ios_base& s, ios_base::iostate& r, tm* d) const; In get_year(In b, In e, ios_base& s, ios_base::iostate& r, tm* d) const; In get_weekday(In b, In e, ios_base& s, ios_base::iostate& r, tm* d) const; In get_monthname(In b, In e, ios_base& s, ios_base::iostate& r, tm* d) const; static locale::id id; protected: ~time_get(); }; // facet-azonost objektum (D.2, D.3, D.3.1)

// virtulis "do_" fggvnyek a nyilvnos fggvnyek szmra (lsd D.4.1)

A get_time() a do_get_time() fggvnyt hvja meg. Az alaprtelmezett get_time() a %X formtum (D.4.4) felhasznlsval gy olvassa be az idt, ahogy a lokl time_put() fggvnye ltrehozza azt. Ugyangy a get_date() fggvny a do_get_date()-et hvja meg s az alaprtelmezett get_time() a %x formtum felhasznlsval (D.4.4) a lokl time_put() fggvnynek eredmnye szerint olvas. A Date tpusok legegyszerbb bemeneti mvelete valami ilyesmi:
istream& operator>>(istream& s, Date& d) { istream::sentry guard(s); // lsd 21.3.8 if (!guard) return s; ios_base::iostate res = 0; tm x = { 0 }; istreambuf_iterator<char,char_traits<char> > end;

Forrs: http://www.doksi.hu

1230

Fggelkek s trgymutat

try { use_facet< time_get<char> >(s.getloc()).get_date(s,end,s,res,&x); if (res==0 || res==ios_base::eofbit) d = Date(x.tm_mday,Date::Month(x.tm_mon+1),x.tm_year+1900); else s.setstate(res); } catch (...) { handle_ioexception(s); // lsd D.4.2.2 } return s;

A get_date(s,end,s,res,&x) hvs az istream-rl val kt automatikus talaktson alapul: az els s paramter egy istreambuf_iterator ltrehozsra hasznlatos, a harmadik paramterknt szerepl s pedig az istream bzisosztlyra, az ios_base-re alakul t. A fenti bemeneti mvelet azon dtumok esetben mkdik helyesen, amelyek a time_t tpussal brzolhatk. Egy egyszer prba a kvetkez lenne:
int main() try { Date today; cout << today << endl; Date d(12, Date::may, 1998);

// rs %x formtummal

} catch (Date::Bad_date) { cout << "Rossz dtum: a program kilp.\n"; }

cout << d << endl; Date dd; while (cin >> dd) cout << dd << endl; // az %x formtummal ltrehozott // dtumok olvassa

A put_time _byname vltozata (D.4, D.4.1) szintn adott:


template <class Ch, class Out = ostreambuf_iterator<Ch> > class std::time_put_byname : public time_put<Ch,Out> { /* ... */ };

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1231

D.4.4.5. Egy rugalmasabb Date osztly Ha megprblnnk felhasznlni a D.4.4.2 pontbl a Date osztlyt a D.4.4.3-ban s D.4.4.4-ben szerepl be- s kimenettel, hamarosan korltokba tkznnk: 1. A Date csak olyan dtumokat tud kezelni, amelyek a time_t tpussal brzolhatk: ez ltalban az [1970,2038] tartomnyt jelenti. 2. A Date csak a szabvnyos formtumban fogadja el a dtumokat brmilyen legyen is az. 3. A Date-nl a bemeneti hibk jelzse elfogadhatatlan. 4. A Date csak char tpus adatfolyamokat tmogat, nem tetszleges karakter tpusakat. Egy hasznosabb bemeneti mvelet a dtumok szlesebb tartomnyt fogadn el, felismerne nhny gyakori formtumot s megbzhatan jelenten a hibkat valamilyen jl hasznlhat formban. Ahhoz, hogy ezt elrjk, el kell trnnk a time_t brzolstl:
class Date { public: enum Month { jan=1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec }; struct Bad_date { const char* why; Bad_date(const char* p) : why(p) { } }; Date(int dd, Month mm, int yy, int day_of_week = 0); Date(); void make_tm(tm* t) const; time_t make_time_t() const; int year() const { return y; } Month month() const { return m; } int day() const { return d; } // ... private: char d; Month m; int y; }; // a Date tm brzolst *t-be teszi // a Date time_t brzolsval tr vissza

Itt az egyszersg kedvrt a (d,m,y) brzolshoz (10.2) trtnk vissza.

Forrs: http://www.doksi.hu

1232

Fggelkek s trgymutat

A konstruktort gy adhatjuk meg:


Date::Date(int dd, Month mm, int yy, int day_of_week) : d(dd), m(mm), y(yy) { if (d==0 && m==Month(0) && y==0) return; // Date(0,0,0) a "null dtum" if (mm<jan || dec<mm) throw Bad_date("rossz a hnap"); if (dd<1 || 31<dd) // jcskn leegyszerstve; lsd 10.3.1 throw Bad_date("rossz a hnap napja"); if (day_of_week && day_in_week(yy,mm,dd)!=day_of_week) throw Bad_date("rossz a ht napja"); } Date::Date() : d(0), m(0), y(0) { } // egy "null dtum"

A day_in_week() kiszmtsa nem knny s nem kapcsoldik a loklokhoz, ezrt kihagytam. Ha szksgnk van r, rendszernkben biztosan megtallhatjuk valahol. Az sszehasonlt mveletek mindig hasznosak az olyan tpusok esetben, mint a Date:
bool operator==(const Date& x, const Date& y) { return x.year()==y.year() && x.month()==y.month() && x.day()==y.day(); } bool operator!=(const Date& x, const Date& y) { return !(x==y); }

Mivel eltrtnk a szabvnyos tm s time_t formtumoktl, szksgnk van konverzis fggvnyekre is, amelyek egytt tudnak mkdni azokkal a programokkal, amelyek ezeket a tpusokat vrjk el:
void Date::make_tm(tm* p) const // a dtum *p-be helyezse { tm x = { 0 }; *p = x; p->tm_year = y-1900; p->tm_mday = d; p->tm_mon = m-1; } time_t Date::make_time_t() const { if (y<1970 || 2038<y) // tlegyszerstett

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1233

throw Bad_date("A dtum a time_t tartomnyn kvl esik."); tm x; make_tm(&x); return mktime(&x);

D.4.4.6. Dtumformtumok megadsa A C++ nem hatrozza meg a dtumok kirsnak szabvnyos formtumt (a %x kzelti meg a legjobban; D.4.4.3), de mg ha ltezne is ilyen, valsznleg szeretnnk ms formtumokat is hasznlni. Ezt gy tehetjk meg, hogy meghatrozunk egy alaprtelmezett formtumot s lehetsget adunk annak mdostsra:
class Date_format { static char fmt[]; // alaprtelmezett formtum const char* curr; // az ppen rvnyes formtum const char* curr_end; public: Date_format() : curr(fmt), curr_end(fmt+strlen(fmt)) { } const char* begin() const { return curr; } const char* end() const { return curr_end; } void set(const char* p, const char* q) { curr=p; curr_end=q; } void set(const char* p) { curr=p; curr_end=curr+strlen(p); } }; static const char* default_fmt() { return fmt; }

const char Date_format<char>::fmt[] = "%A, %B %d, %Y"; // pl. Friday, February 5, 1999 Date_format date_fmt;

Hogy az strftime() formtumot (D.4.4.3) hasznlhassuk, tartzkodtam attl, hogy a Date_format osztlyt a hasznlt karaktertpussal paramterezzem. Ebbl kvetkezik, hogy ez a megolds csak olyan dtumjellst enged meg, amelynek formtumt ki lehet fejezni a char[] tpussal. Ezenkvl felhasznltam egy globlis formtum-objektumot is (date_fmt), az alaprtelmezett dtumformtum megadshoz. Mivel a date_fmt rtkt meg lehet vltoztatni, a Date formzshoz rendelkezsnkre ll egy meglehetsen nyers mdszer, hasonlan ahhoz, ahogy a global() loklt (D.2.3) lehet hasznlni a formzsra.

Forrs: http://www.doksi.hu

1234

Fggelkek s trgymutat

Sokkal ltalnosabb megolds, ha ltrehozzuk a Date_in s Date_out facet-eket az adatfolyambl trtn olvass s rs vezrlshez. Ezt a megkzeltst a D.4.4.7 pontban mutatjuk be. Ha adott a Date_format, a Date::operator<<()-t gy rhatjuk meg:
template<class Ch, class Tr> basic_ostream<Ch,Tr>& operator<<(basic_ostream<Ch,Tr>& s, const Date& d) // rs felhasznli formtummal { typename basic_ostream<Ch,Tr>::sentry guard(s); // lsd 21.3.8 if (!guard) return s; tm t; d.make_tm(&t); try { const time_put<Ch>& f = use_facet< time_put<Ch> >(s.getloc()); if (f.put(s,s,s.fill(),&t,date_fmt.begin(),date_fmt.end()).failed()) s.setstate(ios_base::failbit); } catch (...) { handle_ioexception(s); // lsd D.4.2.2 } return s;

A has_facet fggvnnyel ellenrizhetnnk, hogy az s loklja rendelkezik-e a time_put<Ch> jellemzvel, itt azonban egyszerbb gy kezelni a problmt, hogy minden kivtelt elkapunk, amit a use_facet kivlt. me egy egyszer tesztprogram, ami a kimenet formtumt a date_fmt-n keresztl vezrli:
int main() try { while (cin >> dd && dd != Date()) cout << dd << endl; // rs az alaprtelmezett // date_fmt hasznlatval date_fmt.set("%Y/%m/%d"); while (cin >> dd && dd != Date()) cout << dd << endl; // rs az "%Y/%m/%d" // hasznlatval

} catch (Date::Bad_date e) { cout << "Rossz dtum: " << e.why << endl; }

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1235

D.4.4.7. Bemeneti facet-ek dtumokhoz Mint mindig, a bemenet kezelse kicsit nehezebb, mint a kimenet. Mgis, mivel a get_date() javtott a felleten az alacsonyszint bemenethez s mert a D.4.4.4 pontban a Date tpushoz megadott operator>>() nem frt hozz kzvetlen mdon a Date brzolshoz, az operator>>()-t vltozatlanul hasznlhatjuk. me az operator>>() sablon fggvnyknt megrt vltozata:
template<class Ch, class Tr> istream<Ch,Tr>& operator>>(istream<Ch,Tr>& s, Date& d) { typename istream<Ch,Tr>::sentry guard(s); if (guard) try { ios_base::iostate res = 0; tm x = { 0 }; istreambuf_iterator<Ch,Tr> end; use_facet< time_get<Ch> >(s.getloc()).get_date(s,end,s,res,&x); if (res==0 || res==ios_base::eofbit) d = Date(x.tm_mday,Date::Month(x.tm_mon+1),x.tm_year+1900,x.tm_wday); else s.setstate(res); // lsd D.4.2.2

} catch (...) { handle_ioexception(s); } return s;

Ez a bemeneti mvelet az istream time_get jellemzjnek get_date() fggvnyt hvja meg, gy a bemenet j, rugalmasabb formjt adhatjuk meg, gy, hogy szrmaztatssal egy j facet-et ksztnk a time_get-bl:
template<class Ch, class In = istreambuf_iterator<Ch> > class Date_in : public std::time_get<Ch,In> { public: Date_in(size_t r = 0) : std::time_get<Ch>(r) { } protected: In do_get_date(In b, In e, ios_base& s, ios_base::iostate& r, tm* tmp) const; private: enum Vtype { novalue, unknown, dayofweek, month }; In getval(In b, In e, ios_base& s, ios_base::iostate& r, int* v, Vtype* res) const; };

Forrs: http://www.doksi.hu

1236

Fggelkek s trgymutat

A getval() fggvnynek egy vet, egy hnapot, a hnap napjt s a ht egy napjt kell beolvasnia (ez utbbi nem ktelez), az eredmnyt pedig egy tm szerkezetbe kell tennie. A hnapok s a ht napjainak nevei a lokltl fggnek, kvetkezskppen nem emlthetjk meg azokat kzvetlenl a bemeneti fggvnyben. Ehelyett a hnapokat s napokat gy ismerjk fel, hogy meghvjuk azokat a fggvnyeket, amelyeket a time_get ad erre a clra: a get_monthname() s get_weekday() fggvnyeket (D.4.4.4). Az v, a hnap napja s valsznleg a hnap is egszknt van brzolva. Sajnos egy szm nem jelzi, hogy egy hnap napjt jelli-e vagy valami egszen mst. A 7 pldul jellhet jliust, egy hnap 7. napjt vagy ppen a 2007-es vet is. A time_get date_order() fggvnynek valdi clja az, hogy feloldja az effle tbbrtelmsgeket. A Date_in rtkeket olvas be, osztlyozza azokat, majd a date_order() fggvnnyel megnzi, a bert adatok rtelmesek-e (illetve hogyan rtelmesek). A tnyleges olvasst az adatfolyam tmeneti trbl, illetve a kezdeti osztlyozst a privt getval() fggvny vgzi:
template<class Ch, class In> In Date_in<Ch,In>::getval(In b,In e,ios_base& s,ios_base::iostate& r,int* v,Vtype* res) const // A Date rsznek olvassa: szm, a ht napja vagy hnap. reshelyek s rsjelek tlpse. { const ctype<Ch>& ct = use_facet< ctype<Ch> >(s.getloc()); // a ctype lerst // lsd D.4.5.-ben Ch c; *res = novalue; // nem tallt rtket

for (;;) { // reshelyek s rsjelek tlpse if (b == e) return e; c = *b; if (!(ct.is(ctype_base::space,c) || ct.is(ctype_base::punct,c))) break; ++b; } if (ct.is(ctype_base::digit,c)) { // egsz olvassa a numpunct figyelmen kvl hagysval int i = 0; do { // tetszleges karakterkszlet szmjegynek decimlis rtkk alaktsa static char const digits[] = "0123456789"; i = i*10 + find(digits,digits+10,ct.narrow(c,' '))-digits; c = *++b; } while (ct.is(ctype_base::digit,c)); *v = i;

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1237

} if (ct.is(ctype_base::alpha,c)) { // hnap nevnek vagy a ht napjnak keresse basic_string<Ch> str; while (ct.is(ctype_base::alpha,c)) { // karakterek olvassa karakterlncba str += c; if (++b == e) break; c = *b; } tm t; basic_stringstream<Ch> ss(str); typedef istreambuf_iterator<Ch> SI; get_monthname(ss.rdbuf(),SI(),s,r,&t);

*res = unknown; return b;

// egy egsz, de nem tudjuk, mit brzol

// bejrtpus az ss tmeneti trhoz // olvass memriabeli adatfolyam // tmeneti trbl

if ((r&(ios_base::badbit|ios_base::failbit))==0) { *v= t.tm_mon; *res = month; return b; } r = 0; // az adatfolyam-llapot trlse a msodik olvass eltt get_weekday(ss.rdbuf(),SI(),s,r,&t); // olvass memriabeli adatfolyam // tmeneti trbl if ((r&(ios_base::badbit|ios_base::failbit))==0) { *v = t.tm_wday; *res = dayofweek; return b; }

} r |= ios_base::failbit; return b;

Itt a trkks rsz a ht napjainak s a hnapoknak a megklnbztetse. Mivel bemeneti bejrkon keresztl olvasunk, nem olvashatjuk be [b,e)-t ktszer, elszr egy hnapot, msodszor pedig egy napot keresve. Msfell nem tudjuk megklnbztetni a hnapokat sem a napoktl, ha egyszerre csak egy karaktert olvasunk be, mert csak a get_monthname() s a get_weekday() fggvnyek tudjk, hogy az adott loklban a hnapok s a ht napjainak nevei mely karaktersorozatokbl plnek fel. Azt a megoldst vlasztottam, hogy az alfabetikus karakterekbl ll sorozatokat beolvastam egy karakterlncba, ebbl egy stringstream-et ksztettem, majd ismtelten olvastam az adatfolyam streambuf-jbl.

Forrs: http://www.doksi.hu

1238

Fggelkek s trgymutat

A hibajelz rendszer kzvetlenl hasznlja az olyan llapotbiteket, mint az ios_base::badbit. Ez azrt szksges, mert az adatfolyam llapott kezel knyelmesebb fggvnyeket, mint a clear() s a setstate(), a basic_ios osztly hatrozza meg, nem pedig annak bzisosztlya, az ios_base (21.3.3). Ha szksges, a >> opertor felhasznlja a get_date() ltal jelentett hibkat, hogy visszalltsa a bemeneti adatfolyam llapott. Ha a getval() adott, elszr beolvashatjuk az rtkeket, majd ksbb megnzhetjk, hogy rtelmesek-e. A dateorder() szerepe dnt lehet:
template<class Ch, class In> In Date_in<Ch,In>::do_get_date(In b, In e, ios_base& s, ios_base::iostate& r, tm* tmp) const // a "ht napja" (nem ktelez), melyet md, dmy, mdy, vagy ydm formtum kvet { int val[3]; // nap, hnap, s v rtkek szmra Vtype res[3] = { novalue }; // rtkosztlyozshoz for (int i=0; b!=e && i<3; ++i) { // nap, hnap, v beolvassa b = getval(b,e,s,r,&val[i],&res[i]); if (r) return b; // hopp: hiba if (res[i]==novalue) { // nem tudjuk befejezni a dtumot r |= ios_base::badbit; return b; } if (res[i]==dayofweek) { tmp->tm_wday = val[i]; --i; // hopp: nem nap, hnap, vagy v } } time_base::dateorder order = dateorder(); // most megprblunk rjnni // a beolvasott adat jelentsre

if (res[0] == month) { // mdy formtum vagy hiba // ... } else if (res[1] == month) { // dmy vagy ymd vagy hiba tmp->tm_mon = val[1]; switch (order) { case dmy: tmp->tm_mday = val[0]; tmp->tm_year = val[2]; break; case ymd: tmp->tm_year = val[0]; tmp->tm_mday = val[1];

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1239

} else if (res[2] == month) { // ... } else { // ... } tmp->tm_year -= 1900; return b;

break; default: r |= ios_base::badbit; return b; } // ydm vagy hiba // megbzunk a dateorder-ben vagy hiba // az alapv igaztsa a tm-hez

Azokat a kdrszleteket, amelyek nem jrulnak hozz a loklok, dtumok s a bemenet kezelsnek megrtshez, kihagytam. A jobb s ltalnosabb dtumformtumok beolvassra alkalmas fggvnyek megrst feladatknt tztem ki (D.6[9-10]). me egy egyszer tesztprogram:
int main() try { cin.imbue(loc(locale(), new Date_in)); // dtumok olvassa a Date_in hasznlatval while (cin >> dd && dd != Date()) cout << dd << endl; } catch (Date::Bad_date e) { cout << "Rossz dtum: " << e.why << endl; }

Vegyk szre, hogy a do_get_date() rtelmetlen dtumokat is elfogad, mint amilyen a


Thursday October 7, 1998

s az
1999/Feb/31

Az v, hnap, nap s (nem ktelezen) a ht napja egysgessgnek ellenrzse a Date konstruktorban trtnik. A Date osztlynak kell tudnia, mi alkot helyes dtumot, a Date_in osztllyal pedig nem kell megosztania ezt a tudst.

Forrs: http://www.doksi.hu

1240

Fggelkek s trgymutat

Meg lehetne oldani azt is, hogy a get_val() vagy a get_date() fggvnyek prbljk kitallni a szmrtkek jelentst. Pldul a
12 May 1922

vilgos, hogy nem a 12-es v 1922. napja. Azaz gyanthatnnk, hogy egy olyan szmrtk, ami nem lehet a megadott hnap napja, biztosan vet jell. Az ilyen feltevsek bizonyos esetekben hasznosak lehetnek, de nem j tlet ezeket ltalnosabb krnyezetben hasznlni. Pldul a
12 May 15

a 12., 15., 1912.,1915., 2012., vagy 2015. vben jellhet egy dtumot. Nha jobb megkzelts, ha kiegsztjk a jellst valamivel, ami segt az vek s napok megklnbztetsben. Az angolban a 1st s 15th pldul biztosan egy hnap napjait jellik. Ugyangy a 751BC-t s az 1453AD-t (i.e.751 s i.sz.1453) nyilvnvalan vknt lehetne azonostani.

D.4.5. Karakterek osztlyozsa


Amikor a bemenetrl karaktereket olvasunk be, gyakran van szksg arra, hogy osztlyozzuk azokat, hogy rtelmezhessk, mit is olvastunk. Egy szm beolvasshoz pldul egy bemeneti eljrsnak tudnia kell, hogy a szmjegyeket mely karakterek jellik. A 6.1.2 pontban is bemutattuk a szabvnyos karakterosztlyoz fggvnyek egy felhasznlst a bemenet elemzsre. Termszetesen a karakterek osztlyozsa fgg az ppen hasznlatos bctl. Ezrt rendelkezsnkre ll a ctype jellemz, amely az adott loklban a karakterek osztlyozst brzolja. A karakterosztlyokat a mask felsorol tpus hatrozza meg:
class std::ctype_base { public: enum mask { space = 1, print = 1<<1, cntrl = 1<<2, upper = 1<<3, lower = 1<<4, alpha = 1<<5, digit = 1<<6, punct = 1<<7,

// a tnyleges rtkek megvalstsfggek // reshely (a "C" loklban , \n, \t, ...) // nyomtathat karakterek // vezrlkarakterek // nagybetk // kisbetk // alfabetikus karakterek // decimlis szmjegyek // rsjelek

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1241

};

};

xdigit = 1<<8, alnum=alpha|digit, graph=alnum|punct

// hexadecimlis szmjegyek // alfanumerikus karakterek

A mask nem fgg egyetlen karaktertpustl sem, kvetkezskppen a felsorols egy (nem sablon) bzisosztlyban tallhat. Vilgos, hogy a mask a hagyomnyos C s C++-beli osztlyozst tkrzi (20.4.1). Eltr karakterkszletek esetben azonban a klnbz karakterrtkek klnbz osztlyokba esnek. Az ASCII karakterkszletben pldul a 125 egsz rtk a } karaktert jelli, ami az rsjelekhez (punct) tartozik, a dn karakterkszletben viszont az magnhangzt, amit egy dn locale-ben az alpha osztlyba kell sorolni. Az osztlyozst maszknak nevezik, mert a kis karakterkszletek hagyomnyos s hatkony osztlyozsa egy tblval trtnik, amelyben minden bejegyzs az osztly(oka)t brzol biteket trolja:
table[a] == lower|alpha|xdigit table[1] == digit table[ ] == space

Ha a fenti adott, a table[c]&m nem nulla, ha a c karakter az m osztlyba tartozik (c egy m), egybknt pedig 0. A ctype defincija gy fest:
template <class Ch> class std::ctype : public locale::facet, public ctype_base { public: typedef Ch char_type; explicit ctype(size_t r = 0); bool is(mask m, Ch c) const; // "c" az "m" osztlyba tartozik?

// minden [b:e)-beli Ch osztlyozsa, az eredmny a v-be kerl const Ch* is(const Ch* b, const Ch* e, mask* v) const; const Ch* scan_is(mask m, const Ch* b, const Ch* e) const; // m keresse const Ch* scan_not(mask m, const Ch* b, const Ch* e) const; // nem m keresse

Forrs: http://www.doksi.hu

1242

Fggelkek s trgymutat

Ch toupper(Ch c) const; const Ch* toupper(Ch* b, const Ch* e) const; Ch tolower(Ch c) const; const Ch* tolower(Ch* b, const Ch* e) const;

// [b:e) talaktsa

Ch widen(char c) const; const char* widen(const char* b, const char* e, Ch* b2) const; char narrow(Ch c, char def) const; const Ch* narrow(const Ch* b, const Ch* e, char def, char* b2) const; static locale::id id; protected: ~ctype(); }; // facet-azonost objektum (D.2, D.3, D.3.1)

// virtulis "do_" fggvnyek a nyilvnos fggvnyek szmra (lsd D.4.1)

Az is(c,m) hvssal vizsglhatjuk meg, hogy a c karakter az m osztlyba tartozik-e:


int count_spaces(const string& s, const locale& loc) { const ctype<char>& ct = use_facet< ctype<char> >(loc); int i = 0; for(string::const_iterator p = s.begin(); p != s.end(); ++p) if (ct.is(ctype_base::space,*p)) ++i; // reshely a ct meghatrozsa alapjn return i; }

Az is() fggvnnyel azt is megnzhetjk, hogy egy karakter adott osztlyok valamelyikbe tartozik-e:
ct.is(ctype_base::space|ctype_base::punct,c); // c reshely vagy rsjel ct alapjn?

Az is(b,e,v) hvs a [b,e)-ben szerepl minden karakter osztlyt eldnti s a megfelel pozcira helyezi azokat a v tmbben. A scan_is(m,b,e) egy mutatt ad vissza a [b,e)-ben lv els olyan karakterre, amely az m osztlyba tartozik. Ha egyetlen karakter sem tartozik az m osztlyba, a fggvny az e-t adja vissza. Mint mindig a szabvnyos facet-ek esetben, a nyilvnos tagfggvny sajt do_ virtulis fggvnyt hvja meg. Egy egyszer vltozat a kvetkez lenne:

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1243

template <class Ch> const Ch* std::ctype<Ch>::do_scan_is(mask m, const Ch* b, const Ch* e) const { while (b!=e && !is(m,*b)) ++b; return b; }

A scan_not(m,b,e) a [b,e)-ben lv els olyan karakterre ad vissza mutatt, amely nem tartozik az m osztlyba. Ha minden karakter az m osztlyba tartozik, a fggvny az e-t adja vissza. A toupper(c) a c nagybets vltozatt adja vissza, ha az ltezik a hasznlatban lv karakterkszletben; klnben pedig magt a c-t. A toupper(b,e) a [b,e) tartomnyban minden karaktert nagybetsre alakt s az e-t adja vissza. Ennek egy egyszer megvalstsa a kvetkez lehet:
template <class Ch> const Ch* std::ctype<Ch>::to_upper(Ch* b, const Ch* e) { for ( ; b!=e; ++b) *b = toupper(*b); return e; }

A tolower() fggvnyek hasonlak a toupper()-hez, azzal az eltrssel, hogy kisbetsre alaktanak. A widen(c) a c karaktert a neki megfelel Ch tpus rtkre alaktja. Ha a Ch karakterkszlete tbb c-nek megfelel karaktert nyjt, a szabvny a legegyszerbb sszer talakts alkalmazst rja el. Pldul a
wcout << use_facet< ctype<wchar_t> >(wcout.getloc()).widen('e');

az e karakternek a wcout loklban lv sszer megfeleljt fogja kirni. A klnbz karakterbrzolsok, mint az ASCII s az EBCDIC kztti talaktst szintn el lehet vgezni a widen() fggvnnyel. Pldul tegyk fel, hogy ltezik az ebcdic lokl:
char EBCDIC_e = use_facet< ctype<char> >(ebcdic).widen('e');

A widen(b,e,v) hvs a [b,e) tartomnyban lv minden karakter szlestett vltozatt helyezi a megfelel pozcira a v tmbben.

Forrs: http://www.doksi.hu

1244

Fggelkek s trgymutat

A narrow(ch,def) egy char tpus rtket ad a Ch tpus ch karakternek. Itt is a legegyszerbb sszer talaktst kell alkalmazni. Ha nem ltezik megfelel char tpus rtk, a fggvny a def-et adja vissza. A narrow(b,e,v) hvs a [b,e) tartomnyban lv minden karakter leszktett vltozatt helyezi a megfelel pozcira a v tmbben. Az ltalnos elgondols az, hogy a narrow() a nagyobb karakterkszletet alaktja egy kisebbre, mg a widen() ennek ellenkezjt vgzi. Egy kisebb karakterkszletben szerepl c karakter esetben elvrnnk a kvetkezt:
c == narrow(widen(c), 0) // nem garantlt

Ez akkor igaz, ha a c ltal brzolt karakternek csak egyetlen jellse van a kisebbik karakterkszletben, ez azonban nem biztos. Ha a char tpussal brzolt karakterek nem kpezik rszhalmazt azoknak, amelyeket a nagyobb (Ch) karakterkszlet brzol, rendellenessgekre s problmkra szmthatunk a karaktereket ltalnossgban kezel programokban. A nagyobb karakterkszletben lv ch karakterre ugyangy elvrnnk az albbit:
widen(narrow(ch,def)) == ch || widen(narrow(ch,def)) == widen(def) // nem garantlt

Ez gyakran teljesl, de a nagyobb karakterkszletben tbb rtkkel jellt karakterek esetben nem mindig biztosthat, hogy csak egyszer legyenek brzolva a kisebbik kszletben. Egy szmjegynek (pl. a 7-nek) pldul gyakran tbb eltr brzolsa ltezik egy nagy karakterkszletben. Ennek jellemzen az az oka, hogy egy nagy karakterkszletnek ltalban szmos hagyomnyos karakterkszlet-rszhalmaza van s a kisebb karakterkszletek karaktereibl msodpldnyok lteznek, hogy megknnytsk az talaktst. Az alap karakterkszletben (C.3.3) szerepl minden karakterre biztostott a kvetkez:
widen(narrow(ch_lit,0)) == ch_lit

Pldul:
widen(narrow(x)) == x

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1245

A narrow() s widen() fggvnyek mindentt figyelembe veszik a karakterek osztlyozst, ahol lehetsges. Pldul ha is(alpha,c) igaz, akkor az is(alpha,narrow(c,a)) s az is(alpha,widen(c)) is igaz lesz ott, ahol az alpha rvnyes maszk a hasznlatban lv loklra nzve. A ctype facet-et klnsen a narrow() s widen() fggvnyeket ltalban arra hasznljuk, hogy olyan kdot rjunk, amely tetszleges karakterkszleten vgzi a be- s kimenet, illetve a karakterlncok kezelst; azaz, hogy az ilyen kdot ltalnoss tegyk a karakterkszletek szempontjbl. Ebbl kvetkezik, hogy az iostream-megvalstsok alapveten fggnek ezektl az eszkzktl. A felhasznlnak legtbbszr nem kell kzvetlenl hasznlnia a ctype jellemzt, ha az <iostream>-re s a <string>-re tmaszkodik. A ctype _byname vltozata (D.4, D.4.1) szintn adott:
template <class Ch> class std::ctype_byname : public ctype<Ch> { /* ... */ };

D.4.5.1. Knyelmet szolgl felletek A ctype facet-et leggyakrabban arra hasznljuk, hogy lekrdezzk, hogy egy karakter egy adott osztlyba tartozik-e. Kvetkezskppen erre a clra a kvetkez fggvnyek adottak:
template <class Ch> bool isspace(Ch c, const locale& loc); template <class Ch> bool isprint(Ch c, const locale& loc); template <class Ch> bool iscntrl(Ch c, const locale& loc); template <class Ch> bool isupper(Ch c, const locale& loc); template <class Ch> bool islower(Ch c, const locale& loc); template <class Ch> bool isalpha(Ch c, const locale& loc); template <class Ch> bool isdigit(Ch c, const locale& loc); template <class Ch> bool ispunct(Ch c, const locale& loc); template <class Ch> bool isxdigit(Ch c, const locale& loc); template <class Ch> bool isalnum(Ch c, const locale& loc); template <class Ch> bool isgraph(Ch c, const locale& loc);

A fenti fggvnyek a use_facet-et hasznljk:


template <class Ch> inline bool isspace(Ch c, const locale& loc) { return use_facet< ctype<Ch> >(loc).is(space, c); }

Forrs: http://www.doksi.hu

1246

Fggelkek s trgymutat

Ezen fggvnyek egyparamter vltozatai (20.4.2) az aktulis C globlis loklhoz, nem a C++ globlis lokljhoz, a locale()-hez kszltek. Azokat a ritka eseteket kivve, amikor a C s a C++ globlis lokl klnbzik (D.2.3), ezeket a vltozatokat gy tekinthetjk, mintha a ktparamter vltozatokat a locale()-re alkalmaztuk volna:
inline int isspace(int i) { return isspace(i, locale()); }

// majdnem

D.4.6. Karakterkd-talaktsok
A fjlban trolt karakterek brzolsa nha klnbzik ugyanazoknak a karaktereknek az elsdleges memriban kvnatos brzolstl. A japn karaktereket pldul gyakran olyan fjlokban troljk, amelyekben lptetsek jelzik, hogy az adott karaktersorozat a ngy legelterjedtebb karakterkszlet (kandzsi, katakana, hiragana s romadzsi) kzl melyikhez tartozik. Ez kiss esetlen megolds, mert minden bjt jelentse a lptetsi llapottl fgg, de memrit takarthat meg, mert csak egy kandzsi karakter brzolsa ignyel egy bjtnl tbbet. Az elsdleges memriban ezek a karakterek knnyebben kezelhetk, ha tbbjtos karakterknt brzoltak, ahol minden karakter mrete megegyezik. Az ilyen karakterek (pldul a Unicode karakterek) ltalban szles karakterekben vannak elhelyezve (wchar_t; 4.3). Mindezek miatt a codecvt facet eszkzket nyjt a karakterek egyik brzolsrl a msikra val talaktsra; mikzben azokat olvassuk vagy rjuk:

brzols lemezen:

JIS

A codecvt ltal felgyelt I/O talaktsok

Unicode brzols az elsdleges memriban:

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1247

Ez a kdtalakt rendszer elg ltalnos ahhoz, hogy a karakterbrzolsok kztt tetszleges talaktst tegyen lehetv, gy a bemeneti adatfolyamok ltal hasznlt locale belltsval olyan programot rhatunk, amely (char, wchar_t vagy ms tpusban trolt) alkalmas bels karakterbrzolst hasznl s tbbfle karakter-adatfolyam brzolst elfogad bemenetknt. A msik lehetsg az lenne, hogy magt a programot mdostjuk vagy a beolvasott s kirt fjlok formtumt alaktjuk oda-vissza. A codecvt a klnbz karakterkszletek kztti talaktsra akkor ad lehetsget, amikor a karaktert az adatfolyam tmeneti tra s egy kls tr kztt mozgatjuk:
class std::codecvt_base { public: enum result { ok, partial, error, noconv }; };

// eredmnyjelz

template <class I, class E, class State> class std::codecvt : public locale::facet, public codecvt_base { public: typedef I intern_type; typedef E extern_type; typedef State state_type; explicit codecvt(size_t r = 0); result in(State&, const E* from, const E* from_end, const E*& from_next, // olvass I* to, I* to_end, I*& to_next) const; result out(State&, const I* from, const I* from_end, const I*& from_next, // rs E* to, E* to_end, E*& to_next) const; result unshift(State&, E* to, E* to_end, E*& to_next) const; // karaktersorozat vge int encoding() const throw(); bool always_noconv() const throw(); // alapvet kdolsi tulajdonsgok // mehet az I/O kdtalakts nlkl?

int length(const State&, const E* from, const E* from_end, size_t max) const; int max_length() const throw(); // a lehetsges legnagyobb length() static locale::id id; // facet-azonost objektum (D.2, D.3, D.3.1) protected: ~codecvt(); }; // virtulis "do_" fggvnyek a nyilvnos fggvnyek szmra (lsd D.4.1)

Forrs: http://www.doksi.hu

1248

Fggelkek s trgymutat

A codecvt facet-et a basic_filebuf (21.5) karakterek olvasshoz s rshoz hasznlja s az adatfolyam lokljbl olvassa ki (21.7.1). A State sablonparamter az ppen talaktott adatfolyam lptetsi llapotnak trolsra szolgl tpus. Ha specializcit ksztnk belle, a State-et a klnbz talaktsok azonostsra is felhasznlhatjuk. Ez utbbi azrt hasznos, mert gy tbbfajta karakterkdolshoz (karakterkszlethez) tartoz karaktereket lehet ugyanolyan tpus objektumokban trolni:
class JISstate { /* .. */ }; p = new codecvt<wchar_t,char,mbstate_t>; q = new codecvt<wchar_t,char,JISstate>; // szabvnyos char-rl szles karakterre // JIS-rl szles karakterre

A klnbz State paramterek nlkl a facet nem tudn megllaptani, melyik kdolst ttelezze fel egy adott char adatfolyamra. A rendszer szabvnyos talaktst a char s a wchar_t tpusok kztt a <cwchar> vagy <wchar.h> fejllomnyban lv mbstate_t tpus rja le. Szrmaztatott osztlyknt, nvvel azonostva j codecvt-t is ltrehozhatunk:
class JIScvt : public codecvt<wchar_t,char,mbstate_t> { /* ... */ };

Az in(s,from,from_end,from_next,to,to_end,to_next) hvs minden karaktert beolvas a [from,from_next) tartomnybl s megprblja talaktani azokat. Ha egy karakterrel vgzett, akkor azt j alakjban a [to,to_end) tartomny megfelel helyre rja; ha talaktsra nem kerl sor, a fggvny ezen a ponton megll. A visszatrskor az in() a from_next-ben az utols beolvasott karakter utni pozcit, a to_next-ben pedig az utols rott karakter utni pozcit trolja. Az in() ltal visszaadott result rtk jelzi, hogy a fggvny mennyire tudta elvgezni a munkt: ok: partial: error: noconv: A [from,from_end) tartomnyban minden karakter talaktsra kerlt. A [from,from_end) tartomnyban nem minden karakter kerlt talaktsra. Az out() olyan karakterrel tallkozott, amit nem tudott talaktani. Nem volt szksg talaktsra.

A partial (rszleges) talakts nem biztos, hogy hiba. Elkpzelhet, hogy tbb karaktert kell beolvasni, mieltt egy tbbjtos karakter teljes lesz s ki lehet rni, esetleg a kimeneti tmeneti trat kell kirteni, hogy helyet csinljunk a tovbbi karaktereknek.

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1249

A bemeneti karaktersorozat llapott az in() meghvsnak kezdetn a State tpus s paramter jelzi. Ennek akkor van jelentsge, ha a kls karakterbrzols lptetsi llapotokat hasznl. Jegyezzk meg, hogy az s (nem konstans) referencia paramter: a hvs vgn a bemeneti sorozat lptetsnek llapott trolja, ami lehetv teszi, hogy a programoz kezelhesse a rszleges talaktsokat s hossz sorozatokat is talakthasson az in() tbbszri meghvsval. Az out(s,from,from_end,from_next,to,to_end,to_next) ugyangy alaktja t a [from,from_end)-et a bels brzolsrl a klsre, ahogy az in() a kls brzolst a belsre. A karakterfolyamoknak semleges (nem lptetett) llapotban kell kezddnik s vgzdnik. Ez az llapot ltalban a State(). Az unshift(s,to,to_end,to_next) hvs megnzi az s-t s a [to,to_end)-be annyi karaktert tesz, amennyire szksg van ahhoz, hogy a karaktersorozatot visszalltsa a nem lptetett llapotba. Az unshift() eredmnye s a to_next felhasznlsi mdja az out() fggvnyvel azonos. A length(s,from,from_end,max) azoknak a karaktereknek a szmt adja vissza, amelyeket az in() t tudott alaktani a [from,from_end)-bl. Az encoding() visszatrsi rtkei a kvetkezk lehetnek: -1, ha a kls karakterkszlet kdolsa llapotot (pldul lptetett s nem lptetett karaktersorozatokat) hasznl, 0, ha a kdols az egyes karakterek brzolsra klnbz szm bjtot hasznl (pldul egy bjtban egy bitet annak a jellsre, hogy egy vagy kt bjtos-e a karakter brzolsa.), n, ha a kls karakterkszlet minden karakternek brzolsa pontosan n bjtos. Az always_noconv() hvs true-t ad vissza, ha a bels s a kls karakterkszletek kztt nincs szksg talaktsra, klnben pedig false-t. Vilgos, hogy az always_noconv()==true megnyitja a lehetsget az adott nyelvi vltozat szmra, hogy a lehet leghatkonyabb megvalstst nyjtsa; azltal, hogy egyltaln nem hvja meg az talakt fggvnyeket. A max_length() hvs azt a legnagyobb rtket adja vissza, amit a length() visszaadhat egy adott rvnyes paramterhalmazra. A bemenet nagybetsre alaktsa a legegyszerbb kdtalakts. Annyira egyszer, amennyire csak egy codecvt lehet, mgis elltja a feladatt:
class Cvt_to_upper : public codecvt<char,char,mbstate_t> { explicit Cvt_to_upper(size_t r = 0) : codecvt(r) { } // nagybetss alakts

Forrs: http://www.doksi.hu

1250

Fggelkek s trgymutat

protected: // kls brzols olvassa, bels brzols rsa: result do_in(State& s, const char* from, const char* from_end, const char*& from_next, char* to, char* to_end, char*& to_next) const; // bels brzols olvassa, kls brzols rsa: result do_out(State& s, const char* from, const char* from_end, const char*& from_next, char* to, char* to_end, char*& to_next) const { return codecvt<char,char,mbstate_t>::do_out (s,from,from_end,from_next,to,to_end,to_next); } result do_unshift(State&, E* to, E* to_end, E*& to_next) const { return ok; } int do_encoding() const throw() { return 1; } bool do_always_noconv() const throw() { return false; } int do_length(const State&, const E* from, const E* from_end, size_t max) const; int do_max_length() const throw(); // a lehetsges legnagyobb length()

};

codecvt<char,char,mbstate_t>::result Cvt_to_upper::do_in(State& s, const char* from, const char* from_end, const char*& from_next, char* to, char* to_end, char*& to_next) const { // ... D.6[16] ... } int main() // egyszer teszt { locale ulocale(locale(), new Cvt_to_upper); cin.imbue(ulocale); } while (cin>>ch) cout << ch;

A codecvt _byname vltozata (D.4, D.4.1) is adott:


template <class I, class E, class State> class std::codecvt_byname : public codecvt<I,E,State> { /* ... */ };

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1251

D.4.7. zenetek
Termszetesen minden felhasznl a sajt anyanyelvt szereti hasznlni, amikor trsalog a programmal, a lokltl fgg zenetek kifejezsre azonban nem tudunk szabvnyos eljrst biztostani. A knyvtr csupn egyszer eszkzket nyjt ahhoz, hogy loklfgg karakterlnc-halmazokat trolhassunk, melyekbl egyszer zeneteket (message) hozhatunk ltre. A messages osztly alapjban vve egy igen egyszer, csak olvashat adatbzist r le:
class std::messages_base { public: typedef int catalog; };

// katalgus-azonost tpus

template <class Ch> class std::messages : public locale::facet, public messages_base { public: typedef Ch char_type; typedef basic_string<Ch> string_type; explicit messages(size_t r = 0); catalog open(const basic_string<char>& fn, const locale&) const; string_type get(catalog c, int set, int msgid, const string_type& d) const; void close(catalog c) const; static locale::id id; protected: ~messages(); }; // facet-azonost objektum (D.2, D.3, D.3.1)

// virtulis "do_" fggvnyek a nyilvnos fggvnyek szmra (lsd D.4.1)

Az open(s,loc) hvs megnyitja az s nev zenetkatalgust a loc loklban. A katalgus egy megvalststl fggen elrendezett karakterlnc-halmaz, amelyhez a messages::get() fggvnnyel frhetnk hozz. Ha az s nev katalgust nem lehet megnyitni, az open() negatv rtket ad vissza. A katalgust meg kell nyitni a get() els hasznlata eltt. A close(cat) hvs bezrja a cat ltal azonostott katalgust s minden vele kapcsolatos erforrst felszabadt. A get(cat,set,id,foo) a (set,id)-vel azonostott zenetet keresi meg a cat katalgusban. Ha megtallja a karakterlncot, akkor a get() ezzel tr vissza; ha nem, az alaprtelmezett karakterlnccal (itt ez afoo).

Forrs: http://www.doksi.hu

1252

Fggelkek s trgymutat

me egy plda a messages facet-re, ahol az zenetkatalgus zenetek halmazbl ll vektor, az zenet pedig egy karakterlnc:
struct Set { vector<string> msgs; }; struct Cat { vector<Set> sets; }; class My_messages : public messages<char> { vector<Cat>& catalogs; public: explicit My_messages(size_t = 0) : catalogs(*new vector<Cat>) { } catalog do_open(const string& s, const locale& loc) const; // az s katalgus megnyitsa string do_get(catalog c, int s, int m, const string&) const; // az (s,m) zenet // megszerzse c-bl void do_close(catalog cat) const { if (catalogs.size()<=cat) catalogs.erase(catalogs.begin()+cat); } ~My_messages() { delete &catalogs; }

};

A messages minden tagfggvnye const, gy a katalgus adatszerkezete (a vector<Set>) a facet-en kvl troldik. Az zeneteket egy katalgus, azon bell egy halmaz, majd a halmazon bell egy zenetkarakterlnc megadsval jellhetjk ki. Egy karakterlncot paramterknt adunk meg; ezt a fggvny alaprtelmezett visszatrsi rtkknt hasznlja, ha nem tallja az zenetet a katalgusban:
string My_messages::do_get(catalog cat, int set, int msg, const string& def) const { if (catalogs.size()<=cat) return def; Cat& c = catalogs[cat]; if (c.sets.size()<=set) return def; Set& s = c.sets[set]; if (s.msgs.size()<=msg) return def; return s.msgs[msg]; }

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1253

A katalgus megnyitsakor a lemezrl egy szveges brzolst olvasunk egy Cat szerkezetbe. Itt olyan brzolst vlasztottam, ami knnyen olvashat: a <<< s >>> jelek egy halmazt hatrolnak, az zenetek pedig szvegsorok:
messages<char>::catalog My_messages::do_open(const string& n, const locale& loc) const { string nn = n + locale().name(); ifstream f(nn.c_str()); if (!f) return -1; catalogs.push_back(Cat()); // a katalgus memriba helyezse Cat& c = catalogs.back(); string s; while (f>>s && s=="<<<") { // halmaz olvassa c.sets.push_back(Set()); Set& ss = c.sets.back(); while (getline(f,s) && s != ">>>") ss.msgs.push_back(s); // zenetek beolvassa } return catalogs.size()-1;

me egy egyszer felhasznls:


int main() { if (!has_facet< My_messages >(locale())) { cerr << "A " << locale().name() << "loklban nincs messages facet.\n"; exit(1); } const messages<char>& m = use_facet< My_messages >(locale()); extern string message_directory; // itt tartom az zeneteimet int cat = m.open(message_directory,locale()); if (cat<0) { cerr << "Nincs katalgus.\n"; exit(1); } cout cout cout cout << << << << m.get(cat,0,0,"Megint m.get(cat,1,2,"Megint m.get(cat,1,3,"Megint m.get(cat,3,0,"Megint tves!") tves!") tves!") tves!") << << << << endl; endl; endl; endl;

Forrs: http://www.doksi.hu

1254

Fggelkek s trgymutat

Ha a katalgus
<<< hell viszlt >>> <<< igen nem taln >>>

a program a kvetkezt rja ki:


hell taln Megint tves! Megint tves!

D.4.7.1. Ms facet-ekben lv zenetek hasznlata Azon tl, hogy a felhasznlkkal val kapcsolattartsra alkalmas loklfgg karakterlncokat trolnak, az zenetek arra is felhasznlhatk, hogy ms facet-ek szmra troljanak karakterlncokat. A Season_io jellemzt (D.3.2) pldul gy is megrhattuk volna:
class Season_io : public locale::facet { const messages<char>& m; // zenetknyvtr int cat; // zenetkatalgus public: class Missing_messages { }; Season_io(int i = 0) : locale::facet(i), m(use_facet<Season_messages>(locale())), cat(m.open(message_directory,locale())) { if (cat<0) throw Missing_messages(); } ~Season_io() { } // lehetv teszi a Season_io objektumok felszmolst (D.3) // x brzolsa karakterlnccal // az s-nek megfelel // Season objektumot x-be helyezi

const string& to_str(Season x) const;

bool from_str(const string& s, Season& x) const;

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1255

};

static locale::id id;

// facet-azonost objektum (D.2, D.3, D.3.1) // az azonost objektum meghatrozsa

locale::id Season_io::id;

const string& Season_io::to_str(Season x) const { return m->get(cat, x, "Nincs ilyen vszak"); } bool Season_io::from_str(const string& s, Season& x) const { for (int i = Season::spring; i<=Season::winter; i++) if (m->get(cat, i, "Nincs ilyen vszak") == s) { x = Season(i); return true; } return false; }

Ez az zenetekre pl megolds abban klnbzik az eredetitl (D.3.2), hogy a Season karakterlncok halmazt egy adott loklhoz megr programoznak a karakterlncokat hozz kell tudnia adni egy messages knyvtrhoz. Ezt az teheti meg knnyen, aki j loklt ad a vgrehajtsi krnyezethez. Mgis, mivel a messages csak olvashat felletet nyjt, az vszakok egy jabb halmaznak megadsa a rendszerprogramozk hatskrn kvl esik. A messages _byname vltozata (D.4, D.4.1) is adott:
template <class Ch> class std::messages_byname : public messages<Ch> { /* ... */ };

D.5. Tancsok
[1] [2] [3] [4] Szmtsunk r, hogy a felhasznlkkal kzvetlenl trsalg programokat s rendszereket tbb orszgban is hasznlni fogjk. D.1. Ne ttelezzk fel, hogy mindenki ugyanazt a karakterkszletet hasznlja, amit mi. D.4.1. Ha a bemenet s kimenet fgg a helyi sajtossgoktl, loklokat hasznljuk s ne alkalmi kdot rjunk. D.1. Kerljk a loklnevek begyazst a programszvegbe. D.2.1.

Forrs: http://www.doksi.hu

1256

Fggelkek s trgymutat

[5] Hasznljuk a lehet legkevesebb globlis formzst. D.2.3, D.4.4.7. [6] Rszestsk elnyben a loklhoz igazod karakterlnc-sszehasonltsokat s rendezseket. D.2.4, D.4.1. [7] A facet-ek legyenek nem mdosthatk. D.2.2, D.3. [8] Csak kevs helyen vltoztassuk meg a loklt egy programban. D.2.3. [9] Hagyjuk, hogy a lokl szablyozza a facet-ek lettartamt. D.3. [10] Amikor loklfgg I/O fggvnyeket runk, ne felejtsk el kezelni a felhasznli (fellrt) fggvnyek okozta kivteleket. D.4.2.2. [11] A pnzrtkek trolsra hasznljunk egy egyszer Money tpust. D.4.3. [12] Hasznljunk egyszer felhasznli tpusokat az olyan rtkek trolsra, amelyek lokltl fgg I/O-t ignyelnek (s ne a beptett tpusok rtkeit alaktsuk oda-vissza). D.4.3. [13] Ne higgynk az idrtkeknek, amg nincs valamilyen j mdszernk arra, hogy beleszmtsuk az sszes lehetsges mdost tnyezt. D.4.4.1. [14] Legynk tisztban a time_t tpus korltaival. D.4.4.1, D.4.4.5. [15] Hasznljunk olyan dtumbeviteli eljrst, amely tbbfle formtumot is elfogad. D.4.4.5. [16] Rszestsk elnyben az olyan karakterosztlyoz fggvnyeket, amelyekben a lokl kifejezetten megadott. D.4.5, D.4.5.1.

D.6. Gyakorlatok
1. (*2,5) Ksztsk el a Season_io-t (D.3.2) az amerikai angoltl eltr nyelvre. 2. (*2) Hozzunk ltre egy Season_io osztlyt (D.3.2), amelynek konstruktora nevek halmazt veszi paramterknt, hogy a klnbz loklokban lv vszakok neveit ezen osztly objektumaiknt lehessen brzolni. 3. (*3) rjunk egy collate<char>::compare fggvnyt, amely bcsorrendet llt fel. Lehetleg olyan nyelvre ksztsk el, mint a nmet, a francia vagy a magyar, mert ezek bcje az angolnl tbb bett tartalmaz. 4. (*2) rjunk egy programot, ami logikai rtkeket szmokknt, angol szavakknt s egy tetszleges nyelv szavaiknt olvas be s r ki. 5. (*2,5) Hatrozzuk meg a Time tpust a pontos id brzolsra. Hatrozzuk meg a Date_and_time tpust is, a Time s valamilyen Date tpus felhasznlsval. Fejtsk ki ennek a megkzeltsnek a D.4.4. pontban szerepl Date tpussal szembeni elnyeit s htrnyait. rjuk meg a Time s a Date_and_time tpusok loklfgg be- s kimeneti eljrsait.

Forrs: http://www.doksi.hu

D. Helyi sajtossgok

1257

6. (*2,5) Tervezznk meg s ksztsnk el egy postai irnytszm facet-et. rjuk meg legalbb kt, eltr cmzsi szoksokat hasznl orszgra. 7. (*2,5) Ksztsnk egy telefonszm facet-et. rjuk meg legalbb kt, eltr telefonszm-formt (pldul (973) 360-8000 s 1223 343000) hasznl orszgra. 8. (*2,5) Prbljuk meg kitallni, milyen be- s kimeneti formtumokat hasznl C++-vltozatunk a dtumokhoz. 9. (*2,5) rjunk olyan get_time() fggvnyt, amely megprblja kitallni a tbbrtelm dtumok pldul a 12/05/1995 jelentst, de elutast minden vagy majdnem minden hibt. Hatrozzuk meg pontosan, melyik tallgatst fogadjuk el s gondoljuk t a hibk valsznsgt. 10. (*2) rjunk olyan get_time() fggvnyt, ami tbbfajta bemeneti formtumot fogad el, mint a D.4.4.5 pontban szerepl vltozat. 11. (*2) Ksztsnk listt a rendszernk ltal tmogatott loklokrl. 12. (*2,5) Talljuk meg, rendszernk hol trolja a nevestett loklokat. Ha van hozzfrsnk a rendszer azon rszhez, ahol a loklok troldnak, ksztsnk egy j, nvvel rendelkez locale-t. Legynk vatosak, nehogy tnkretegyk a mr ltez locale-eket. 13. (*2) Hasonltsuk ssze a Season_io kt megvalstst (D.3.2 s D.4.7.1). 14. (*2) rjuk meg, s teszteljk le a Date_out facet-et, ami Date-eket r ki a konstruktornak megadott paramter ltal meghatrozott formban. Fejtsk ki ennek a megkzeltsnek az elnyeit s htrnyait a date_fmt ltal nyjtott globlis adatformtummal (D.4.4.6) szemben. 15. (*2,5) rjuk meg a rmai szmok (pldul XI s MDCLII) be- s kimeneti eljrsait. 16. (*2,5) Ksztsk el s ellenrizzk a Cvt_to_upper-t (D.4.6). 17. (*2,5) llaptsuk meg a kvetkezk tlagos kltsgt a clock() fggvny felhasznlsval: (1) fggvnyhvs, (2) virtulis fggvnyhvs, (3) egy char beolvassa, (4) egy 1 szmjegy int beolvassa, (5) egy 5 szmjegy int beolvassa (6) egy 5 szmjegy double, (7) egy 1 karakteres string, (8) egy 5 karakteres string, s (9) egy 40 karakteres string beolvassa. 18. (*6,5) Tanuljunk meg egy msik termszetes nyelvet.

Forrs: http://www.doksi.hu

E
Kivtelbiztossg a standard knyvtrban
Minden gy mkdik, ahogy elvrjuk feltve, hogy elvrsaink helyesek. (Hyman Rosen) Kivtelbiztossg Kivtelbiztos eljrsok Erforrsok brzolsa rtkads push_back() Konstruktorok s invarinsok A szabvnyos trolk szolgltatsai Elemek beillesztse s eltvoltsa Garancik s kompromisszumok swap() A kezdeti rtkads s a bejrk Hivatkozs elemekre Prediktumok Karakterlncok, adatfolyamok, algoritmusok, a valarray s a complex A C standard knyvtra Javaslatok a knyvtrak felhasznli szmra Tancsok Gyakorlatok

E.1. Bevezets
A standard knyvtr fggvnyei gyakran olyan fggvnyeket hvnak meg, melyeket a felhasznl ad meg akr fggvny-, akr sablonparamterek formjban. Termszetesen ezek a felhasznli eljrsok nha kivteleket vltanak ki. Egyes fggvnyek (pldul a memriafoglal eljrsok) nmagukban is kpesek kivtel kivltsra:

Forrs: http://www.doksi.hu

1260

Fggelkek s trgymutat

void f(vector<X>& v, const X& g) { v[2] = g; // X tpus rtkadsa kivtelt vlthat ki v.push_back(g); // vector<X> memriafoglalja kivtelt vlthat ki sort(v.begin(), v.end()); // X "kisebb mint" opertora kivtelt vlthat ki vector<X> u = v; // X msol konstruktora kivtelt vlthat ki // ... } // u itt semmisl meg: biztostanunk kell, hogy X destruktora helyesen mkdjn

Mi trtnik, ha az rtkads kivtelt vlt ki, mikzben a g rtkt prbljuk lemsolni? A v ezutn egy rvnytelen elemet fog tartalmazni? Mi trtnik, ha az a konstruktor, amit a v.push_back() hasznl a g lemsolshoz, std::bad_alloc kivtelt vlt ki? Megvltozik az elemek szma? Bekerlhet rvnytelen elem a trolba? Mi trtnik, ha az X osztly kisebb, mint opertora eredmnyez kivtelt a rendezs kzben? Az elemek ezutn rszben rendezettekk vlnak? Elkpzelhet, hogy a rendez eljrs eltvolt egy elemet a trolbl s egy hiba miatt nem teszi vissza? A fenti pldban szerepl sszes kivtel-lehetsg megtallsa az E.8.[1] feladat clja, ezen fggelk az, hogy elmagyarzzuk, hogyan illik viselkednie a fenti programnak az sszes megfelelen meghatrozott X tpus esetben (belertve azt az esetet is, amikor az X kivteleket vlthat ki). Termszetesen a fggelk nagy rszben azt fogjuk tisztzni, mit is neveznk helyes viselkedsnek, illetve megfelelen meghatrozottnak a kivtelekkel kapcsolatban. A fggelkben a kvetkezkkel foglalkozunk: 1. Megvizsgljuk, hogyan adhat meg a felhasznl olyan tpusokat, amelyek megfelelnek a standard knyvtr elvrsainak. 2. Rgztjk, milyen garancikat biztost a standard knyvtr. 3. Megnzzk, milyen elvrsokat llt a standard knyvtr a felhasznl ltal megadott programrszekkel szemben. 4. Bemutatunk nhny hatkony mdszert, mellyel kivtelbiztos s hatkony trolkat kszthetnk. 5. Megemltjk a kivtelbiztos programozs nhny ltalnos szablyt. A kivtelbiztossg vizsglata rtelemszeren a legrosszabb esetben tapasztalhat viselkedssel foglalkozik. Hol jelentheti egy kivtel a legtbb problmt? Hogyan tudja a standard knyvtr megvdeni magt s a felhasznlt a lehetsges problmktl? Hogyan segthet a felhasznl a problmk elkerlsben? Ne hagyjuk, hogy az itt bemutatott kivtel-

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1261

kezelsi mdszerek elfeledtessk velnk, hogy a kivtelek kivltsa a legjobb mdszer a hibk jelzsre (14.1, 14.9). Az elveket, a mdszereket s a standard knyvtr szolgltatsait a kvetkez rendszerben trgyaljuk: E.2. E.3. E.4. E.5. E.6. Ebben a pontban a kivtelbiztossg fogalmt vizsgljuk meg. Itt mdszereket mutatunk arra, hogyan rhatunk hatkony, kivtelbiztos trolkat s mveleteket. Ebben a rszben krvonalazzuk a standard knyvtr troli s mveletei ltal biztostott garancikat. Itt sszefoglaljuk a kivtelbiztossg problmjt a standard knyvtr nem trolkkal foglalkoz rszben. Vgl jbl ttekintjk a kivtelbiztossg krdst a standard knyvtr felhasznlinak szemszgbl.

Szoks szerint a standard knyvtr pldkat mutat azokra a problmatpusokra, amelyekre egy alkalmazs fejlesztsekor oda kell figyelnnk. A standard knyvtrban a kivtelbiztossg megvalstsra alkalmazott eljrsok szmos ms terleten is felhasznlhatk.

E.2. Kivtelbiztossg
Egy objektumon vgrehajtott mveletet akkor neveznk kivtelbiztosnak, ha a mvelet az objektumot akkor is rvnyes llapotban hagyja, ha kivtel kivltsval rt vget. Ez az rvnyes llapot lehet egy hiballapot is, amit esetleg csak teljes jbli ltrehozssal lehet megszntetni, de mindenkppen pontosan meghatrozottnak kell lennie, hogy az objektumhoz logikus hibakezel eljrst adhassunk meg. A kivtelkezel pldul trlheti az objektumot, kijavthatja azt, megprblkozhat a mvelet egy msik vltozatval vagy megprblhat egyszeren tovbbhaladni a hiba figyelmen kvl hagysval. Ms szavakkal, az objektum rendelkezni fog egy invarinssal (llapotbiztost, nem vltoz llapot 24.3.7.1), melyet konstruktorai lltanak el, s minden tovbbi mveletnek meg tartania az ltala lert llapotot, mg akkor is, ha kivtelek kvetkeztek be. A vgs rendrakst a destruktorok vgzik el. Minden mveletnek figyelnie kell arra, hogy a kivtelek kivltsa eltt visszalltsk az invarinst, hogy az objektum rvnyes llapotban lpjen ki a mveletbl. Termszetesen nagy valsznsggel ez az rvnyes llapot nem az az llapot lesz, amire az alkalmazsnak ppen szksge lenne. Egy karakterlnc egy kivtel kvetkeztben pldul ress vlhat, egy trol pedig rendezetlen maradhat. Teht a kijav-

Forrs: http://www.doksi.hu

1262

Fggelkek s trgymutat

ts csak azt jelenti, hogy az objektumnak olyan rtket adunk, ami elfogadhatbb a program szmra, mint az, amit a flbeszakadt mvelet benne hagyott. A standard knyvtr szempontjbl a legrdekesebb objektumok a trolk. Az albbiakban azt vizsgljuk meg, milyen felttelek mellett nevezhetjk a szabvnyos trolkat feldolgoz mveleteket kivtelbiztosnak. Elvileg mindssze kt egyszer megkzelts kzl vlaszthatunk: 1. Nincs biztosts: Ha kivtel lp fel, a mvelet ltal hasznlt valamennyi trolt valsznleg srltnek ttelezzk fel. 2. Ers biztosts: Ha kivtel lp fel, minden trol pontosan abba az llapotba kerl vissza, mint amiben a standard knyvtr mveletnek megkezdse eltt volt. Sajnos ez a kt megolds annyira egyszer, hogy igazn nem is alkalmazhat. Az els azrt elfogadhatatlan, mert ha ezen megolds mellett egy trolmvelet kivtelt vlt ki, a trolt tbbet nem rhetjk el, st mg nem is trlhetjk anlkl, hogy futsi idej hibktl kne rettegnnk. A msodik lehetsg azrt nem hasznlhat, mert ekkor a visszallts megvalstsnak kltsgei a standard knyvtr minden mveletben jelentkeznnek. A problma megoldsra a C++ standard knyvtra kivtelbiztostsi szinteket knl, melyek a helyes program ksztsnek terheit megosztjk a standard knyvtr megvalsti s felhasznli kztt: 3a Alapbiztosts az sszes mveletre: A standard knyvtr alapvet invarinsai mindenkppen fennmaradnak s semmilyen erforrs (pldul memriatartomny) nem maradhat lefoglalt. 3b Ers biztosts a legfontosabb mveletekhez: Az alapbiztostson tl, a mvelet vagy sikeresen vgrehajtsra kerl, vagy semmilyen vltozst nem eredmnyez. Ilyen szint biztosts csak a knyvtr legfontosabb mveleteit illeti meg, pldul a push_back(),a list osztlyban az egyelem insert(), illetve az uninitialized_copy() fggvnyeket (E.3.1, E.4.1). 3c Nncs kivtel biztosts bizonyos mveletekre Az alapbiztosts mellett nhny mvelet egyltaln nem vlthat ki kivteleket. Ez a tpus biztosts csak nhny egyszer mveletre valsthat meg, pldul a swap() vagy a pop_back() fggvnyre (E.4.1). Az alapbiztosts s az ers biztosts is felttelezi, hogy a felhasznl ltal megadott mveletek (pldul az rtkadsok s a swap() fggvnyek) nem hagyjk a trol elemeit rvnytelen llapotban s minden ltaluk lefoglalt erforrst felszabadtanak. Ezenkvl a destruktoroknak nem szabad kivtelt kivltaniuk. Pldul vizsgljuk meg az albbi osztlyokat (25.7):

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1263

template<class T> class Safe { T* p; // p egy T tpus, new-val lefoglalt objektumra mutat public: Safe() : p(new T) { } ~Safe() { delete p; } Safe& operator=(const Safe& a) { *p = *a.p; return *this; } // ... }; template<class T> class Unsafe { // hanyag s veszlyes kd T* p; // egy T-re mutat public: Unsafe(T* pp) : p(pp) { } ~Unsafe() { if (!p->destructible()) throw E(); delete p; } Unsafe& operator=(const Unsafe& a) { p->~T(); // a rgi rtk trlse (10.4.11) new(p) T(a.p); // T ltrehozsa *p-ben a.p alapjn (10.4.11) return *this; } // ...

};

void f(vector< Safe<Some_type> >&vg, vector< Unsafe<Some_type> >&vb) { vg.at(1) = Safe<Some_type>(); vb.at(1) = Unsafe<Some_type>(new Some_type); // ... }

Ebben a pldban a Safe osztly ltrehozsa csak akkor sikeres, ha a T osztly is sikeresen ltrejn. Egy T objektum ltrehozsa meghisulhat azrt, mert nem sikerl a memriafoglals (ilyenkor std::bad_alloc kivtel jelentkezik), vagy azrt, mert a T konstruktora brmilyen okbl kivtelt vlt ki. Ettl fggetlenl, egy sikeresen ltrehozott Safe esetben a p egy hibtlan T objektumra mutat, mg ha a konstruktor sikertelen, sem T, sem Safe objektum nem jn ltre. Ugyangy kivtelt vlthat ki a T rtkad mvelete is; ekkor a Safe rtkad mvelete (a C++ mkdsnek megfelelen) tovbbdobja a kivtelt. Mindez nem jelent problmt mindaddig, amg a T rtkad opertora sajt operandust megfelel llapotban hagyja. Teht a Safe kvetelmnyeinknek megfelelen mkdik, gy a standard knyvtr mveletei Safe objektumokra alkalmazva logikus s megfelelen meghatrozott eredmnyt fognak adni.

Forrs: http://www.doksi.hu

1264

Fggelkek s trgymutat

Ezzel szemben az Unsafe() konstruktort figyelmetlenl rtuk meg (pontosabban figyelmesen gy rtuk meg, hogy a helytelen formt bemutassa). Egy Unsafe objektum ltrehozsa sohasem fog meghisulni. Ehelyett az objektumot hasznl mveletekre (pldul az rtkadsra s a megsemmistsre) bzzuk, hogy trdjenek sajt beltsuk szerint az sszes lehetsges problmval. Az rtkads meghisulhat gy, hogy a T msol konstruktora kivlt egy kivtelt. Ilyenkor a T tpus objektumot nem meghatrozott llapotban hagyjuk, hiszen a *p rgi rtkt trltk, de nem helyettestettk rvnyes rtkkel. Az ilyen mkds kvetkezmnyei ltalban megjsolhatatlanok. Az Unsafe destruktora tartalmaz egy remnytelen prblkozst az rvnytelen megsemmists elkerlsre, egy kivtel meghvsa egy msik kivtel kezelse kzben azonban a terminate() (14.7) meghvst eredmnyezi, mg a standard knyvtr elvrja, hogy a destruktor szablyosan visszatrjen az objektum trlse utn. A standard knyvtr nem kszlt fel s nem is tud felkszlni arra, hogy garancikat adjon olyan felhasznli objektumokra, melyek nem megfelelen mkdnek. A kivtelkezels szempontjbl a Safe s az Unsafe abban klnbzik, hogy a Safe a konstruktort hasznlja az invarins (24.3.7.1) ltrehozshoz, ami lehetv teszi, hogy mveleteit egyszeren s biztonsgosan valstsuk meg. Ha az llapotbiztost felttel nem elgthet ki, kivtelt kapunk, mieltt mg az rvnytelen objektum ltrejnne. Ugyanakkor az Unsafe nem rendelkezik rtelmes invarinssal s a klnll mveletek mindenfle kivteleket vltanak ki, anlkl, hogy egy kzponti hibakezel eljrs rendelkezskre llna. Termszetesen ez gyakran vezet a standard knyvtr (sszer) felttelezseinek megsrtshez. Az Unsafe esetben pldul rvnytelen elemek maradhatnak egy trolban, miutn a T::operator=() egy kivtelt vltott ki, s a destruktor is eredmnyezhet kivteleket. A standard knyvtr biztostsainak viszonya a rossz viselkeds felhasznli mveletekhez ugyanolyan, mint a nyelv biztostsainak viszonya a tpusrendszer szablyait megsrt mveletekhez. Ha egy alapmveletet nem meghatrozott szablyai szerint hasznlunk, az eredmny nem meghatrozhat lesz. Ha egy vector elemeinek destruktora kivtelt vlt ki, ugyangy nincs jogunk logikus viselkedst elvrni, mint amikor egy kezdrtkknt vletlenszmmal feltlttt mutatval szeretnnk adatokat elrni:
class Bomb { public: // ... ~Bomb() { throw Trouble(); } }; vector<Bomb> b(10); // nem meghatrozhat viselkedshez vezet

void f() { int* p = reinterpret_cast<int*>(rand()); // nem meghatrozhat viselkedshez vezet *p = 7; }

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1265

De nzzk a dolgok j oldalt: ha betartjuk a nyelv s a standard knyvtr alapvet szablyait, a knyvtr jl fog viselkedni gy is, ha kivteleket vltunk ki. Amellett, hogy rendelkezsnkre ll a tiszta kivtelbiztossg, szeretjk elkerlni az erforrsokban elfordul lyukakat; azaz egy olyan mveletnek, amely kivtelt vlt ki, nem elg az operandusait meghatrozott llapotban hagynia, biztostania kell azt is, hogy minden ignyelt erforrs valamikor felszabaduljon. Egy kivtel kivltsnak helyn pldul minden korbban lefoglalt memriaterletet fel kell szabadtanunk vagy azokat valamilyen objektumhoz kell ktnnk, hogy ksbb a memria szablyosan felszabadthat legyen. A standard knyvtr biztostja, hogy nem lesznek erforrs-lyukak, ha azok a felhasznli mveletek, melyeket a knyvtr hasznl, maguk sem hoznak ltre ilyeneket:
void leak(bool abort) { vector<int> v(10); // nincs memria-elszivrgs vector<int>* p = new vector<int>(10); // memria-elszivrgs lehetsges auto_ptr< vector<int> > q(new vector<int>(10)); // nincs memria-elszivrgs (14.4.2) if (abort) throw Up(); // ... delete p;

Ha kivtel kvetkezik be, a v vector s a q ltal mutatott vector helyesen lesz trlve, gy minden ltaluk lefoglalt erforrs felszabadul. Ezzel szemben a p ltal mutatott vector objektumot nem vdjk a kivtelektl, gy az nem fog trldni. Ha a kdrszletet biztonsgoss akarjuk tenni, vagy magunknak kell felszabadtani a p ltal mutatott terletet a kivtel kivltsa eltt, vagy biztostanunk kell, hogy valamilyen objektum pldul egy auto_ptr (14.4.2) birtokolja azt s szablyosan felszabadtsa akkor is, ha kivtel kvetkezett be. Figyeljk meg, hogy a nyelv szablyai a rszleges ltrehozssal, illetve megsemmistssel kapcsolatban biztostjk, hogy a rszobjektumok s az adattagok ltrehozsakor bekvetkez kivteleket a standard knyvtr eljrsai minden kln erfeszts nlkl helyesen kezeljk (14.4.1). Ez minden kivtelekkel kapcsolatos eljrs esetben nagyon fontos alapelv. Gondoljunk arra is, hogy nem a memria az egyetlen olyan erforrs, amelyben lyukak fordulhatnak el. A megnyitott fjlok, zrolsok, hlzati kapcsolatok s a szlak is olyan rendszererforrsok, melyeket egy fggvnynek fel kell szabadtania vagy t kell adnia valamely objektumnak egy kivtel kivltsa eltt.

Forrs: http://www.doksi.hu

1266

Fggelkek s trgymutat

E.3. A kivtelbiztossgot megvalst eljrsok


A standard knyvtr szoks szerint bemutatja azokat a problmkat, amelyek sok ms helyzetben is elfordulhatnak s ezekre olyan megoldst ad, ami szles krben felhasznlhat. A kivtelbiztos programok rsnak alapvet eszkzei a kvetkezk: 1. A try blokk (8.3.1) 2. A kezdeti rtkads az erforrs megszerzsvel eljrs (14.4) A kvetend ltalnos elvek: 3. Soha ne dobjunk ki adatokat gy, hogy nem tudjuk pontosan, mi kerl a helykre. 4. Az objektumokat mindig rvnyes llapotban hagyjuk, amikor kivtelt vltunk ki. Ha betartjuk ezeket a szablyokat, minden hibt megfelelen kezelhetnk. Ezen elvek kvetse a gyakorlatban azrt jelent problmt, mert mg a legrtalmatlanabbnak tn eljrsok (pldul a <, az = vagy a sort()) is kivlthatnak kivteleket. Annak megllaptshoz, hogy egy programban mit kell megvizsglnunk, nagy tapasztalatra van szksg. Ha knyvtrat runk, clszer az ers kivtelbiztossgot (E.2) clul kitznnk s mindenhol meg kell valstanunk az alapbiztostst. Programok rsakor a kivtelbiztossg kevsb fontos szempont. Pldul, amikor egy egyszer adatfeldolgoz programot ksztnk sajt magunknak, ltalban nem baj, ha a program egyszeren befejezdik, amikor a virtulis memria valamilyen hiba miatt elfogy. Ugyanakkor a helyessg s az alapvet kivtelbiztossg szorosan kapcsoldik egymshoz. Az alapvet kivtelbiztossg megvalstsra szolgl eljrsok pldul az llapotbiztostk megadsa s fenntartsa (24.3.7.1) nagyon hasonltanak azokhoz, melyek segtsgvel kicsi s helyesen mkd programokat hozhatunk ltre. Ebbl kvetkezik, hogy az alapvet kivtelbiztossg (az alapbiztosts; E.2) vagy akr az ers biztosts megvalstsa csupn jelentktelen teljestmnyromlssal jr. Lsd: E.8[17] Az albbiakban a vector szabvnyos trol (16.3) egy megvalstst adjuk meg, hogy bemutassuk, milyen feladatokat kell megoldanunk az idelis kivtelbiztossg megvalstshoz s hol rdemes alaposabb felgyeletet biztostanunk.

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1267

E.3.1. Egy egyszer vektor


A vector (16.3) leggyakoribb megvalstsban hrom mutat (vagy az ezzel egyenrtk mutateltols pr) szerepel: egy az els elemre, egy az utols utni elemre s egy az utols utni lefoglalt helyre hivatkozik (17.1.3):

vector:

first space last elemek tartalk hely

Lssuk, mire van szksgnk a vector deklarcijban, ha csak a kivtelbiztossg s az erforrs-lyukak elkerlse szempontjbl vizsgljuk az osztlyt:
template<class T, class A = allocator<T> > class vector { private: T* v; // a lefoglalt terlet eleje T* space; // az elemsorozat vge, bvts szmra tartalkolt terlet kezdete T* last; // a lefoglalt terlet vge A alloc; // memriafoglal public: explicit vector(size_type n, const T& val = T(), const A& = A()); vector(const vector& a); vector& operator=(const vector& a); ~vector(); size_type size() const { return space-v; } size_type capacity() const { return last-v; } void push_back(const T&); // ... // msol konstruktor // msol rtkads

};

Forrs: http://www.doksi.hu

1268

Fggelkek s trgymutat

Vizsgljunk elszr egy meggondolatlan konstruktor-megvalstst:


template<class T, class A> vector<T,A>::vector(size_type n, const T& val, const A& a) // vigyzat: nav megvalsts : alloc(a) // memriafoglal msolsa { v = alloc.allocate(n); // memria lefoglalsa az elemek szmra (19.4.1) space = last = v+n; for (T* p = v; p!=last; ++p) a.construct(p,val); // val ltrehoz msolsa *p-be (19.4.1) }

Itt hrom helyen keletkezhet kivtel: 1. Az allocate() fggvny kivtelt vlthat ki, ha nincs elegend memria. 2. A memriafoglal (alloktor) msol konstruktora is eredmnyezhet kivtelt. 3. A T elemtpus msol konstruktora is kivlthat kivtelt, ha nem tudja lemsolni a val rtket. Egyik esetben sem jn ltre j objektum, teht a vector konstruktora sem fut le (14.4.1). Ha az allocate() vgrehajtsa sikertelen, a throw mr akkor kilp a konstruktorbl, amikor mg semmilyen erforrst nem foglaltunk le, gy ezzel minden rendben. Ha a T msol konstruktora eredmnyez hibt, mr lefoglaltunk valamennyi memrit, gy azt fel kell szabadtanunk, ha el akarjuk kerlni a memria-elszivrgst. Az igazn nagy problmt az jelenti, hogy a T msol konstruktora esetleg akkor vlt ki kivtelt, amikor mr nhny objektumot sikeresen ltrehozott, de mg nem az sszeset. Ezen problma kezelshez nyilvn kell tartanunk, hogy eddig mely elemeket hoztuk ltre, s amikor hiba kvetkezik be, ezeket (s csak ezeket) trlnnk kell:
template<class T, class A> vector<T,A>::vector(size_type n, const T& val, const A& a) // jl kidolgozott megvalsts : alloc(a) // memriafoglal msolsa { v = alloc.allocate(n); // memria lefoglalsa az elemek szmra iterator p; try { iterator end = v+n; for (p=v; p!=end; ++p) alloc.construct(p,val); last = space = p; }

// elemek ltrehozsa (19.4.1)

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1269

catch (...) { for (iterator q = v; q!=p; ++q) alloc.destroy(q); // ltrehozott elemek megsemmistse alloc.deallocate(v,n); // memria felszabadtsa throw; // tovbbdobs }

A pluszkltsg ez esetben is csak a try blokk kltsge. Egy j C++-vltozatban ez elhanyagolhat a memriafoglalshoz s az elemek kezdeti rtkadshoz kpest. Ahol a try blokk kltsge magas, esetleg beilleszthetjk az if(n) vizsglatot a try el, ezzel az res vektort kln esetknt kezelhetjk. A konstruktor nagy rszt az uninitialized_fill() fggvny kivtelbiztos megvalstsa tlti ki:
template<class For, class T> void uninitialized_fill(For beg, For end, const T& x) { For p; try { for (p=beg; p!=end; ++p) new(static_cast<void*>(&*p)) T(x); // x ltrehoz msolsa *p-ben (10.4.11) } catch (...) { // a ltrehozott elemek trlse s tovbbdobs: for (For q = beg; q!=p; ++q) (&*q)->~T(); // (10.4.11) throw; } }

A furcsa &*p kifejezsre azrt volt szksg, hogy a nem mutat bejrkat (itertorokat) is kezelhessk. Ilyenkor ahhoz, hogy mutatt kapjunk, a hivatkozssal meghatrozott elem cmt kell vennnk. A void* talakts biztostja, hogy a standard knyvtr elhelyez fggvnyt hasznljuk (19.4.5), nem a felhasznl ltal a T* tpusra megadott operator new() fggvnyt. Ez az eljrs olyan alacsony szinten mkdik, ahol a teljes ltalnossgot mr elg nehz biztostani. Szerencsre nem kell jrarnunk az uninitialized_fill() fggvnyt, mert a standard knyvtr biztostja hozz a megkvnt ers biztostst (E.2). Gyakran elengedhetetlen , hogy olyan kezdrtk-ad mvelet lljon rendelkezsnkre, amely vagy minden elemet hibtlanul tlt fel kezdrtkkel, vagy hiba esetn egyltaln nem ad vissza elemeket. ppen ezrt a standard knyvtrban szerepl uninitialized_fill(), uninitialized_fill_n() s uninitialized_copy() fggvny (19.4.4) biztostja ezt az ers kivtelbiztossgot (E.4.4).

Forrs: http://www.doksi.hu

1270

Fggelkek s trgymutat

Figyeljk meg, hogy az uninitialized_fill() nem vdekezik az elemek destruktora vagy a bejrmveletek ltal kivltott kivtelek ellen (E.4.4), ez ugyanis elviselhetetlenl nagy kltsget jelentene (lsd E.8[16-17]). Az uninitialized_fill() nagyon sokfle sorozatra alkalmazhat, ezrt csak elre halad bejrkat vesz t (19.2.1), amivel viszont nem tudja garantlni, hogy az elemeket ltrehozsukkal fordtott sorrendben trli. Az uninitialized_fill() felhasznlsval az albbiakat rhatjuk:
template<class T, class A> vector<T,A>::vector(size_type n, const T& val, const A& a) // zavaros megvalsts :alloc(a) // memriafoglal msolsa { v = alloc.allocate(n); // memria lefoglalsa az elemek szmra try { uninitialized_fill(v,v+n,val); // elemek msolsa space = last = v+n; } catch (...) { alloc.deallocate(v,n); // memria felszabadtsa throw; // tovbbdobs } }

Ennek ellenre ezt a programot nem nevezhetjk szpnek. A kvetkezkben bemutatjuk, hogyan tehetjk a programot sokkal egyszerbb. Figyeljk meg, hogy a konstruktor jra kivltja azt a kivtelt, amit elkapott. A cl az, hogy a vector osztlyt tltszv tegyk a kivtelek eltt, mert gy a felhasznl pontosan megllapthatja a hiba okt. A standard knyvtr sszes trolja rendelkezik ezzel a tulajdonsggal. A kivtelekkel szembeni tltszsg gyakran a legjobb lehetsg a sablonok s a hasonl vkony rtegek szmra. Ez ellenttben ll a programrendszerek nagyobb rszeinek (moduljainak) irnyvonalval, hiszen ezeknek ltalban nllan kell kezelnik minden hibt. Pontosabban, az ilyen modulok ksztinek fel kell tudniuk sorolni az sszes kivtelt, amit a modul kivlthat. Ennek megvalstshoz vagy a kivtelek csoportostsra (14.2), vagy az alacsonyszint eljrsok s a modul sajt kivteleinek sszekapcsolsra (14.6.3), esetleg a kivtelek meghatrozsra (14.6) van szksg.

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1271

E.3.2. A memria brzolsa


A tapasztalatok bebizonytottk, hogy helyes, kivtelbiztos programok ksztse try blokkok segtsgvel bonyolultabb annl, mint amit egy tlagos programoz mg elfogad. Valjban feleslegesen bonyolult, hiszen van egy msik lehetsg: a kezdeti rtkads az erforrs megszerzsvel (14.4), melynek segtsgvel cskkenthetjk azon programsorok szmt, melyek egy stlusos program megvalstshoz szksgesek. Esetnkben a legfontosabb erforrs, amire a vector osztlynak szksge van, egyrtelmen a memria, melyben az elemeket troljuk. Ha bevezetnk egy segdosztlyt, amely a vector ltal hasznlt memrit brzolja, leegyszersthetjk programunkat s cskkenthetjk annak eslyt, hogy vletlenl elfelejtjk felszabadtani a memrit.
template<class T, class A = allocator<T> > struct vector_base { A alloc; // memriafoglal T* v; // a lefoglalt terlet eleje T* space; // az elemsorozat vge, bvts szmra tartalkolt terlet kezdete T* last; // a lefoglalt terlet vge vector_base(const A& a, typename A::size_type n) : alloc(a), v(a.allocate(n)), space(v+n), last(v+n) { } ~vector_base() { alloc.deallocate(v,last-v); }

};

Amg a v s a last mutat helyes, a vector_base objektum megsemmisthet. A vector_base osztly a T tpus szmra lefoglalt memrit kezeli s nem T tpus objektumokat, ezrt mieltt egy vector_base objektumot trlnk, minden ennek segtsgvel ltrehozott objektumot is trlnnk kell. Termszetesen magt a vector_base osztlyt is gy kell megrni, hogy ha kivtel kvetkezik be (a memriafoglal msol konstruktorban vagy az allocate() fggvnyben), ne jjjn ltre vector_base objektum, gy memria-elszivrgs sem kvetkezik be. A vector_base felhasznlsval a vector osztlyt a kvetkezkppen hatrozhatjuk meg:
template<class T, class A = allocator<T> > class vector : private vector_base<T,A> { void destroy_elements() { for (T* p = v; p!=space; ++p) p->~T(); } // 10.4.11 public: explicit vector(size_type n, const T& val = T(), const A& = A());

Forrs: http://www.doksi.hu

1272

Fggelkek s trgymutat

vector(const vector& a); // msol konstruktor vector& operator=(const vector& a); // msol rtkads ~vector() { destroy_elements(); } size_type size() const { return space-v; } size_type capacity() const { return last-v; } void push_back(const T&); // ...

};

A vector destruktora az sszes elemre egyms utn meghvja a T tpus destruktort. Ebbl kvetkezik, hogy ha egy elem destruktora kivtelt vlt ki, a vector destruktora sem tud hibtlanul lefutni. Ez igen nagy problmt okoz, ha egy msik kivtel miatt kezdemnyezett verem-visszatekers kzben kvetkezik be, hiszen ekkor a terminate() fggvny fut le (14.7). Ha a destruktor kivtelt vlt ki, ltalban erforrs-lyukak keletkeznek s azok az eljrsok, melyeket szablyos viselkeds objektumokhoz fejlesztettek ki, megjsolhatatlanul fognak mkdni. Nem igazn van hasznlhat megolds a destruktorokban keletkez kivtelek kezelsre, ezrt a knyvtr semmilyen biztostst nem vllal, ha az elemek ilyen viselkedsek lehetnek (E.4). A konstruktort az albbi egyszer formban adhatjuk meg:
template<class T, class A> vector<T,A>::vector(size_type n, const T& val, const A& a) : vector_base<T,A>(a,n) // terlet lefoglalsa n elem szmra { uninitialized_fill(v,v+n,val); // elemek msolsa }

A msol konstruktor mindssze abban klnbzik ettl, hogy az uninitialized_fill() helyett az uninitialized_copy() fggvnyt hasznlja:
template<class T, class A> vector<T,A>::vector(const vector<T,A>& a) : vector_base<T,A>(a.alloc,a.size()) { uninitialized_copy(a.begin(),a.end(),v); }

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1273

Figyeljk meg, hogy az ilyen stlus konstruktor kihasznlja a nyelvnek azon alapszablyt, hogy ha a konstruktorban kivtel keletkezik, azokra a rszobjektumokra (s alapobjektumokra), melyek sikeresen ltrejttek, a destruktor is szablyosan lefut (14.4.1). Az uninitialized_fill() s testvrei (E.4.4) ugyanilyen szolgltatst nyjtanak a flig ltrehozott sorozatok esetben.

E.3.3. rtkads
Szoks szerint az rtkads abban klnbzik a ltrehozstl, hogy a korbbi rtkekre is figyelnnk kell. Vizsgljuk meg az albbi megvalstst:
template<class T, class A> vector<T,A>& vector<T,A>::operator=(const vector& a) // ers biztostst ad (E.2) { vector_base<T,A> b(alloc,a.size()); // memria lefoglalsa uninitialized_copy(a.begin(),a.end(),b.v); // elemek msolsa destroy_elements(); alloc.deallocate(v,last-v); // a rgi memriaterlet felszabadtsa vector_base::operator=(b); // brzols elhelyezse b.v = 0; // felszabadts megelzse return *this; }

Ez az rtkads szp s kivtelbiztos is, de tl sok mindent ismtel meg a konstruktorbl s a destruktorbl. Ennek elkerlsre a kvetkez eljrst rhatjuk:
template<class T, class A> vector<T,A>& vector<T,A>::operator=(const vector& a) // ers biztostst ad (E.2) { vector temp(a); // "a" msolsa swap< vector_base<T,A> >(*this,temp); // brzolsok felcserlse return *this; }

A rgi elemeket a temp destruktora trli, az ltaluk lefoglalt terletet pedig a temp vltoz vector_base objektumnak destruktora szabadtja fel. A kt vltozat teljestmnye szinte teljesen egyenl. Valjban csak kt klnbz formban adjuk meg ugyanazokat a mveleteket. A msodik megvalsts viszont rvidebb s nem ismtli a vector osztly egyb fggvnyeiben mr szerepl kdrszleteket, gy az rtkads ezen vltozata kevesebb hibalehetsget tartalmaz s egyszerbb rendben tartani is.

Forrs: http://www.doksi.hu

1274

Fggelkek s trgymutat

Figyeljk meg, hogy az nrtkads szoksos vizsglata hinyzik az eljrsbl (10.4.4):


if (this == &a) return *this;

Ezek az rtkad mveletek elszr ltrehoznak egy msolatot, majd lecserlik az elemeket. Ez a megolds automatikusan kezeli az nrtkads problmjt. gy dntttem, hogy a ritkn elfordul nrtkads kln vizsglata nem jr annyi haszonnal, hogy rdemes legyen ezzel lasstani az ltalnos esetet, amikor egy msik vector objektumot adunk rtkl. Mindkt esetben kt, esetleg jelents optimalizlsi lehetsg hinyzik: 1. Ha az rtket kap vektor mrete elg nagy ahhoz, hogy az rtkl adott vektort trolja, nincs szksg j memria lefoglalsra. 2. Az elemek kztti rtkads hatkonyabb lehet, mint egy elem trlse, majd kln ltrehozsa. Ha ezeket a javtsokat is beptjk, a kvetkez eredmnyt kapjuk:
template<class T, class A> vector<T,A>& vector<T,A>::operator=(const vector& a) // optimalizlt, alapbiztosts (E.2) { if (capacity() < a.size()) { // j vektorbrzols szmra terlet lefoglalsa vector temp(a); // "a" msolsa swap< vector_base<T,A> >(*this,temp); // az brzolsok felcserlse return *this; } if (this == &a) return *this; // vdelem az nrtkads ellen (10.4.4) // a rgi elemek rtkadsa size_type sz = size(); size_type asz = a.size(); alloc = a.get_allocator(); // memriafoglal msolsa if (asz<=sz) { copy(a.begin(),a.begin()+asz,v); for (T* p = v+asz; p!=space; ++p) p->~T(); // felesleges elemek felszmolsa (10.4.11) } else { copy(a.begin(),a.begin()+sz,v); uninitialized_copy(a.begin()+sz,a.end(),space); // tovbbi elemek ltrehozsa } space = v+asz; return *this;

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1275

Ezek az optimalizcik nem valsthatk meg bntetlenl. A copy() eljrs nem nyjt ers kivtelbiztostst, mert nem garantlja, hogy a cl vltozatlan marad, ha msols kzben kivtel kvetkezik be. Teht ha a T::operator=() kivtelt vlt ki a copy() mvelet kzben, elfordulhat, hogy az rtket kap vektor megvltozik, de nem lesz az rtkl adott vektor pontos msolata. Lehetsges pldul, hogy az els t elemet sikerl lemsolni az rtkl adott vektorbl, de a tbbi vltozatlan marad, st akr az is elkpzelhet, hogy egy elem (az, amelyiket ppen msoltuk, amikor a T::operator=() kivtelt vltott ki) olyan rtket fog tartalmazni, amely nem egyezik meg sem az eredetivel, sem a msolandval. Azt azrt elmondhatjuk, hogy ha a T::operator=() rvnyes llapotban hagyja operandust egy kivtel kivltsakor is, a teljes vector is rvnyes llapotban marad, br nem abban az llapotban, amit szerettnk volna. A fentiekben a memriafoglalt is rtkadssal msoltuk le. Valjban a memriafoglalktl nem mindig kveteljk meg, hogy rendelkezzenek rtkadssal (19.4.3, lsd mg:E.8[9]). A standard knyvtr vector rtkadsnak legutbbi megvalstsa gyengbb kivtelbiztossgot, de nagyobb hatkonysgot biztost. Csak az alapbiztostst nyjtja, ami megfelel a legtbb programoz kivtelbiztossgrl alkotott fogalmnak, de nem ll rendelkezsnkre ers biztosts (E.2). Ha olyan rtkadsra van szksgnk, amely kivtel fellptekor a vector-t vltozatlanul hagyja, olyan knyvtr-megvalstst kell hasznlnunk, amely ers biztostst nyjt az ilyen helyzetekben is vagy sajt magunknak kell megrni az rtkad mveletet:
template<class T, class A> void safe_assign(vector<T,A>& a, const vector<T,A>& b) // "magtl rtend" a = b { vector<T,A> temp(a.get_allocator()); temp.reserve(b.size()); for (typename vector<T,A>::iterator p = b.begin(); p!=b.end(); ++p) temp.push_back(*p); swap(a,temp); }

Ha nincs elegend memria ahhoz, hogy ltrehozzuk a temp vltozt b.size() elem szmra, az std:bad_alloc kivtelt vltjuk ki, mieltt brmilyen vltoztatst vgeznnk az a vektoron. Ehhez hasonlan, ha a push_back() vgrehajtsa nem sikerl, az a akkor is rintetlen marad, hiszen minden push_back() mveletet a temp objektumon hajtunk vgre az a helyett. Ezzel a megoldssal azt is biztostjuk, hogy felszabaduljon a temp minden eleme, amit a push_back() segtsgvel ltrehoztunk, mieltt a hibt okoz kivtelt jra kivltannk.

Forrs: http://www.doksi.hu

1276

Fggelkek s trgymutat

A swap() nem msolja a vektorok elemeit, csupn lecserli a vector adattagjait, gy a vector_base objektumot is. Ennek kvetkeztben a swap() akkor sem vlthat ki kivtelt, ha az elemek mveletei kpesek erre (E.4.3). A safe_assign() nem kszt felesleges msolatokat az elemekrl, teht elg hatkony tud lenni. Szoks szerint vannak ms lehetsgek is a nyilvnval megvalsts mellett, pldul rbzhatjuk magra a knyvtrra, hogy az elemeket az ideiglenes vektorba msolja:
template<class T, class A> void safe_assign(vector<T,A>& a, const vector<T,A>& b) // egyszer a = b { vector<T,A> temp(b); // b elemeinek msolsa az ideiglenes vltozba swap(a,temp); }

St, egyszer rtk szerinti paramtertadst (7.2) is hasznlhatunk:


template<class T, class A> void safe_assign(vector<T,A>& a, vector<T,A> b) // egyszer a = b // (figyelem: b rtk szerint tadva) { swap(a,b); }

A safe_assign() ez utbbi kt vltozata a vector memriafoglaljt nem msolja le, ami megengedett optimalizci (lsd 19.4.3).

E.3.4. A push_back()
A kivtelbiztossg szemszgbl nzve a push_back() nagyon hasonlt az rtkadsra abban, hogy nem szabad a vector objektumot megvltoztatnunk, ha az j elem beillesztse valamilyen problmba tkzik:
template< class T, class A> void vector<T,A>::push_back(const T& x) { if (space == last) { // nincs tbb hely; thelyezs vector_base b(alloc,size()?2*size():2); // a lefoglals megkettozse uninitialized_copy(v,space,b.v); new(b.space) T(x); // x msolatnak *b.space-be helyezse (10.4.11) ++b.space; destroy_elements();

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1277

} new(space) T(x); ++space;

swap<vector_base<T,A> >(b,*this); return;

// az brzolsok felcserlse

// x msolatnak *space-be helyezse (10.4.11)

Termszetesen, a *space kezdeti rtkadsra szolgl msol konstruktor vlthat ki kivtelt. Ha ez trtnik, a vector vltozatlan marad s a space rtkt sem nveljk meg. Ilyenkor a vektor elemeit sem kell thelyeznnk a memriban, teht az eddigi bejrk is rvnyben maradnak. gy ez a megvalsts ers biztostst ad: ha egy memriafoglal vagy egy felhasznli eljrs kivtelt vlt ki, a vector nem vltozik meg. A standard knyvtr ilyen biztostst nyjt a push_back() fggvnyhez (E.4.1). Figyeljk meg, hogy az eljrsban nincs egyetlen try blokk sem (attl eltekintve, ami az uninitialized_copy() fggvnyben, rejtve szerepel). A mdostst a mveletek sorrendjnek pontos megvlasztsval hajtjuk vgre, gy ha kivtel keletkezik, a vektor rtke nem vltozik meg. Ez a megkzelts miszerint az utastsok sorrendje adja a kivtelbiztossgot s a kezdeti rtkads az erforrs megszerzsvel (14.4) alkalmazsa elegnsabb s hatkonyabb megoldst knl, mint a hibk kifejezett kezelse try blokkok segtsgvel. Ha utastsainkat szerencstlen sorrendben rjuk le, sokkal tbb kivtelbiztossgi problmval kell megkzdennk, mint a kivtelkezel eljrsok elhagysa mellett. A sorrend meghatrozsakor az alapszably az, hogy ne semmistsnk meg informcit, mieltt az azt helyettest adatokat ltre nem hoztuk s nem biztostottuk, hogy azokat kivtelek veszlye nlkl trhassuk a helykre. A kivtelkezels egyik kvetkezmnye, hogy a program vgrehajtsa sorn a vezrls meglep helyekre kerlhet. Az olyan egyszer, helyi vezrlssel rendelkez fggvnyekben, mint az operator=(), a safe_assign() vagy a push_back(), meglepetsek ritkbban fordulnak el. Ha rnznk egy kdrszletre, viszonylag egyszeren megllapthatjuk, hogy egy adott sor vlthat-e ki kivtelt s mi lesz annak kvetkezmnye. A nagyobb fggvnyekben, melyekben bonyolult vezrlsi szerkezeteket (pldul bonyolult feltteles utastsokat s egymsba gyazott ciklusokat) hasznlunk, az ilyen krdsekre nehz vlaszt adni. A try blokkok alkalmazsa a helyi vezrlst mg tovbb bonyoltja, gy jabb keveredseket s hibkat eredmnyezhet (14.4). Azt hiszem, az utastsrendezs s a kezdeti rtkads az erforrs megszerzsvel hatkonysga a nagyobb mret try blokkokkal szemben ppen a helyi vezrls egyszerstsben rejlik. Egyszerbben fogalmazva, a stlusos programokat egyszerbb megrteni s egyszerbb helyesen megrni.

Forrs: http://www.doksi.hu

1278

Fggelkek s trgymutat

Gondoljunk r, hogy az itt szerepl vector csak a kivtelek ltal okozott problmk s az ezen problmk megoldsra kidolgozott eljrsok bemutatsra szolgl. A szabvny nem kveteli meg, hogy minden megvalsts pontosan gy nzzen ki, ahogy itt bemutatjuk. A szabvny ltal biztostott garancikat az E.4. pontban trgyaljuk.

E.3.5. Konstruktorok s invarinsok


A kivtelbiztossg szemszgbl nzve a vector tbbi mvelete vagy ugyangy viselkedik, mint az eddig bemutatottak (mivel hasonl mdon foglalnak le s szabadtanak fel erforrsokat), vagy megvalstsuk egyszer (mert nem vgeznek olyan tevkenysget, amelyben az rvnyes llapot fenntartsa problmt okozhatna). A legtbb osztlyban ezek a trivilis fggvnyek jelentik a dnt tbbsget. Az ilyen eljrsok bonyolultsga elssorban attl fgg, hogy a konstruktor milyen mkdsi krnyezetet biztost szmukra. Mskppen fogalmazva, az ltalnos tagfggvnyek bonyolultsga elssorban a j osztlyinvarins megvlasztsn mlik (24.3.7.1). Az egyszer vektormveletek vizsglatval megrthetjk, mitl lesz j egy osztlyinvarins s hogyan kell megrnunk a konstruktorokat ahhoz, hogy ezeket az invarinsokat biztostsk. Az olyan mveleteket, mint a vektorok indexelse (16.3.3) azrt knny megvalstani, mert ersen ptenek arra az invarinsra, amit a konstruktor hoz ltre s az erforrsokat lefoglal, illetve felszabadt fggvnyek tartanak fenn. Az indexel mvelet pldul hivatkozhat a v tmbre, amely az elemeket trolja:
template< class T, class A> T& vector<T,A>::operator[](size_type i) { return v[i]; }

Nagyon fontos s alapvet szably, hogy a konstruktornak kell lefoglalnia az erforrsokat s biztostania kell egy egyszer invarinst. Ahhoz, hogy ennek fontossgt megrtsk, nzzk meg a vector_base defincijnak albbi vltozatt:
template<class T, class A = allocator<T> > // a konstruktor esetlen hasznlata class vector_base { public: A alloc; // memriafoglal T* v; // a lefoglalt terlet eleje T* space; // az elemsorozat vge, bvts szmra tartalkolt terlet kezdete T* last; // a lefoglalt terlet vge

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1279

vector_base(const A& a, typename A::size_type n) : alloc(a), v(0), space(0), last(0) { v = alloc.allocate(n); space = last = v+n; } }; ~vector_base() { if (v) alloc.deallocate(v,last-v); }

Itt a vector_base objektumot kt lpsben hozzuk ltre. Elszr egy biztonsgi llapotot alaktunk ki, amely a v, a space s a last vltozt 0-ra lltja, s csak ezutn prblunk memrit foglalni. Ez arra az indokolatlan flelemre vezethet vissza, hogy az elemek lefoglalsa kzben keletkez kivtelek miatt flig ltrejtt objektumot hozhatunk ltre. A flelem azrt indokolatlan, mert ilyen objektumot egyltaln nem hozhatunk ltre. A szablyok, melyek a statikus, automatikus, illetve tagobjektumok s a standard knyvtr trolinak elemeire vonatkoznak, megakadlyozzk ezt. Azokban a szabvny eltti knyvtrakban azonban, melyek a trolkban elhelyez new opertort (10.4.11) hasznltak (hasznlnak) az objektumok ltrehozsra s nem foglalkoztak (foglalkoznak) a kivtelbiztossggal, ez elfordulhatott (elfordulhat). A megrgztt szoksokon nehz vltoztatni. Figyeljk meg, hogy a biztonsgosabb program ellltsra irnyul prblkozs tovbb bonyoltja az osztlyinvarinst: nem lehetnk biztosak abban, hogy a v egy ltez, lefoglalt memriaterletre mutat, mert szerepelhet benne a 0 rtk is. Ez a hiba azonnal megbosszulja magt. A standard knyvtr nem kveteli meg a memriafoglalktl, hogy egy 0 rtket tartalmaz mutatt biztonsgosan szabadtsanak fel (19.4.1). A memriafoglalk ebben eltrnek a delete mvelettl (6.2.6). Ebbl kvetkezik, hogy a destruktorban kln ellenrzst kell vgeznnk. Ezenkvl minden elemnek kezdrtket adunk s csak ksbb rtelmes rtket. Ezen kltsgek egy olyan elemtpus esetben lehetnek jelentsek, ahol az rtkads bonyolult, pldul a string vagy a list osztlynl. A ktlpses ltrehozs nem szokatlan megolds. Gyakran olyan formban jelenik meg, hogy a konstruktor csak egy egyszer s biztonsgos kezdeti rtkadst vgez, mellyel az objektum trlhet llapotba kerl. A valdi kezdeti rtkadst egy init() fggvny vgzi el, melyet a felhasznlnak kln meg kell hvnia:
template<class T> class vector_base { public: T* v; T* space; T* last; // rgies (szabvny s kivtelkezels eltti) stlus // a lefoglalt terlet eleje // az elemsorozat vge, bvts szmra tartalkolt terlet kezdete // a lefoglalt terlet vge

Forrs: http://www.doksi.hu

1280

Fggelkek s trgymutat

vector_base() : v(0), space(0), last(0) { } ~vector_base() { free(v); } bool init(size_t n) // igazat ad vissza, ha a kezdeti rtkads sikerlt { if (v = (T*)malloc(sizeof(T)*n)) { uninitialized_fill(v,v+n,T()); space = last = v+n; return true; } return false; }

};

Ezen stlus lthat elnyei a kvetkezk: 1. A konstruktor nem vlthat ki kivtelt s az init() segtsgvel megvalstott kezdeti rtkads sikeressgt a szoksos mdszerekkel (azaz nem kivtelekkel) ellenrizhetjk. 2. rvnyes llapot ll rendelkezsnkre, melyet brmely mvelet komoly problma esetn is biztostani tud. 3. Az erforrsok lefoglalst mindaddig halaszthatjuk, amg tnylegesen szksgnk nincs kezdrtkkel rendelkez objektumokra. A kvetkez rszfejezetekben ezeket a szempontokat vizsgljuk meg s bemutatjuk, hogy a ktlpses ltrehozs mirt nem biztostja az elvrt elnyket, amellett, hogy ms problmkat is felvet. E.3.5.1. Az init() fggvny hasznlata Az els pont (az init() eljrs hasznlata a konstruktor helyett) valjban nem is elny. A konstruktorok s kivtelek hasznlata sokkal ltalnosabb s rendezettebb megolds az erforrs-lefoglalsi hibk s a kezdeti rtkadssal kapcsolatos problmk kezelsre (14.1, 14.4). Ez a stlus a kivtelek nlkli C++ maradvnya. Ha a kt stlusban ugyanolyan figyelmesen runk meg egy programot, azt tapasztalhatjuk, hogy kt, szinte teljesen egyenrtk eredmnyt kapunk. Az egyik:
int f1(int n) { vector<X> v; // ...

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1281

if (v.init(n)) { // "v" n elem vektora } else { // a problma kezelse }

Mg msik vltozat:
int f2(int n) try { vector v<X> v(n); // ... // "v" n elem vektora } catch (...) { // a problma kezelse }

A kln init() fggvny hasznlata viszont lehetsget ad az albbi hibk elkvetsre: 1. 2. 3. 4. Elfelejtjk meghvni az init() fggvnyt (10.2.3). Elfelejtjk megvizsglni, hogy az init() sikerrel jrt-e. Elfelejtjk, hogy az init() kivteleket vlthat ki. Hasznlni kezdnk egy objektumot, mieltt meghvnnk az init() eljrst.

A vector<T>::init() defincija a [3] pontra mutat pldt. Egy j C++-vltozatban az f2() egy kicsit gyorsabb is, mint az f1(), mert az ltalnos esetben nem vgez ellenrzst. E.3.5.2. Alaprtelmezett rvnyes llapot A msodik pont (miszerint egy knnyen elllthat, alaprtelmezett rvnyes llapot ll rendelkezsnkre) ltalban tnyleg elny, de a vector esetben ez felesleges kltsgeket jelent, ugyanis elkpzelhet olyan vector_base, ahol v==0 s a vector megvalstsnak mindenhol vdekeznie kell ez ellen:
template< class T> T& vector<T>::operator[](size_t i) {

Forrs: http://www.doksi.hu

1282

Fggelkek s trgymutat

if (v) return v[i]; // hibakezels

Ha megengedjk a v==0 llapotot, a tartomny-ellenrzs nlkli indexels ugyanolyan lass lesz, mint az ellenrztt hozzfrs:
template< class T> T& vector<T>::at(size_t i) { if (i<v.size()) return v[i]; throw out_of_range("vector index"); }

Itt alapjban vve annyi trtnt, hogy a vector_base eredeti invarinst tlbonyoltottuk, azzal, hogy bevezettk a v==0 lehetsget. Ennek kvetkeztben a vector eredeti invarinst is ugyangy kellett mdostanunk, teht a vector s a vector_base minden eljrst bonyolultabban kell megfogalmaznunk. Ez szmtalan hiba forrsa lehet, pldul nehezebb lesz a kd mdostsa s a program lassabban fog futni. Gondoljunk arra, hogy a modern kipts szmtgpeknl a feltteles utastsok meglepen kltsgesek lehetnek. Ha fontos a hatkonysg, a kulcsmveletek pldul a vektorindexels feltteles utastsok nlkli megvalstsa elsrend kvetelmny lehet. rdekes mdon, mr a vector_base eredeti meghatrozsa is biztost egy knnyen ltrehozhat rvnyes llapotot. Csak akkor ltezhet egy vector_base objektum, ha a kezdeti helyfoglals sikeres volt. Ebbl kvetkezik, hogy a vector rjnak biztostania kell egy vszkijrat fggvnyt, pldul a kvetkez formban:
template< class T, class A> void vector<T,A>::emergency_exit() { space = v; // *this mretnek 0-ra lltsa throw Total_failure(); }

Ez a megolds tlsgosan drasztikus, mert nem hvja meg az elemek destruktorait s nem szabadtja fel a vector_base objektumban az elemek ltal elfoglalt terletet. Rviden fogalmazva, nem nyjt alapbiztostst (E.2). Ha figyelnk a v s a space adattag tartalmra s az elemek destruktoraira, elkerlhetjk az erforrs-lyukak kialakulst:
template< class T, class A> void vector<T,A>::emergency_exit() {

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1283

destroy_elements(); // takarts throw Total_failure();

Figyeljk meg, hogy a szabvnyos vector olyan egyszer szerkezet, amely a lehet legkisebbre cskkenti a ktlpses ltrehozs miatt jelentkez problmk lehetsgt. Az init() fggvny szinte egyenrtk a resize() eljrssal, a v==0 lehetsget pedig a legtbb esetben a size()==0 vizsglat elvgzsvel is kezelhetjk. A ktlpses ltrehozs eddig bemutatott negatv hatsai mg ersebben jelentkeznek, ha programunkban szerepel egy olyan osztly, amelynek jelents erforrsokra pldul hlzati kapcsolatra vagy kls llomnyokra van szksge. Ezek az osztlyok ritkn kpezik egy olyan keretrendszer rszt, amely felgyeli hasznlatukat s megvalstsukat, olyan formban, ahogy a standard knyvtr kvetelmnyei felgyelik a vector hasznlatt. A problmk szma mg tovbb nvekszik, ha az alkalmazs cljai s a megvalstsukhoz szksges erforrsok kapcsolata bonyolult. Nagyon kevs olyan osztly van, amely annyira kzvetlenl kapcsoldik a rendszer erforrsaihoz, mint a vector. Az egyszer biztonsgos llapot ltezsnek elve alapjban vve nagyon hasznos. Ha egy objektumot nem tudunk rvnyes llapotba lltani anlkl, hogy kivtelektl kellene tartanunk a mvelet befejezse eltt, valban problmink lehetnek. A biztonsgos llapotnak viszont az osztly szerephez termszetesen kell kapcsoldnia, nem erltetett mdon, az osztly invarinst bonyoltva. E.3.5.3. Az erforrs-lefoglals ksleltetse A msodik ponthoz hasonlan (E.3.5.2) a harmadik is egy j tlet rossz megvalstsa, ami nyeresg helyett inkbb vesztesget eredmnyez. A legtbb esetben klnsen az olyan trolkban, mint a vector az erforrs-lefoglals ksleltetsnek legjobb mdja a programoz szmra az, hogy magt az objektumot hozza ltre, amikor szksge van r. Nzzk meg pldul a vector objektum albbi felhasznlst:
void f(int n) { vector<X> v(n); // n darab alaprtelmezett X tpus objektum ltrehozsa // ... v[3] = X(99); // v[3] igazi "kezdeti rtkadsa" // ... }

Forrs: http://www.doksi.hu

1284

Fggelkek s trgymutat

Nagy pazarls egy X tpus objektumot csak azrt ltrehozni, mert valamikor, ksbb rtket fogunk adni neki. Klnsen nagy a vesztesg, ha az X osztlyra az rtkads kltsges mvelet. Ezrt az X ktlpses ltrehozsa elnysnek tnhet. Az X maga is lehet egy vector, ezrt a vector ktlpses ltrehozstl az res vektorok ltrehozsi kltsgeinek cskkentst remlhetjk, az alaprtelmezett (res) vektorok ltrehozsa azonban mr egybknt is elg hatkony, ezrt felesleges a megvalstst azzal bonyoltanunk, hogy az res vektort kln esetknt kezeljk. ltalnosabban fogalmazva, a felesleges kezdeti rtkadsok elkerlsre ritkn jelent tkletes megoldst az, hogy a konstruktorbl kiemeljk az sszetettebb kezdeti rtkadsokat:
void f2(int n) { vector<X> v; // ... v.push_back(X(99)); // ... }

// res vektor ltrehozsa // elemek ltrehozsa, amikor szksges

sszefoglalva: a ktlpses ltrehozs sokkal bonyolultabb osztlyinvarinshoz s ltalban kevsb elegns, tbb hibalehetsget tartalmaz s nehezebben kezelhet programhoz vezet. Ezrt a nyelv ltal tmogatott konstruktor elv jobban hasznlhat, mint az init() fggvnyes megolds". Teht az erforrsokat mindig a konstruktorban foglaljuk le, ha a ksleltetett erforrs-lefoglalst nem teszi ktelezv maga az osztly termszete.

E.4. A szabvnyos trolk garancii


Ha a knyvtr valamelyik mvelete nmaga vlt ki kivtelt, akkor biztostani tudja s biztostja is , hogy az ltala hasznlt objektumok rvnyes llapotban maradnak. A vector esetben pldul az at() fggvny (16.3.3) kpes kivltani egy out_of_range kivtelt, ez azonban nem jelent problmt a vektor kivtelbiztossga szempontjbl. Az at() fggvny megrjnak nem jelent problmt, hogy a vektort rvnyes llapotba lltsa a kivtel kivltsa eltt. Problmk csak akkor jelentkeznek a knyvtr megvalsti, a knyvtr felhasznli, illetve azok szmra, akik megprbljk megrteni a programot , amikor felhasznli eljrsok vltanak ki kivtelt. A standard knyvtr troli alapbiztostst nyjtanak (E.2): a knyvtr alap invarinsai mindig megmaradnak s ha a felhasznl a kvetelmnyeknek megfelelen jr el, nem keletkeznek erforrs-lyukak sem. A felhasznli eljrsoktl azt kveteljk meg, hogy ne

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1285

hagyjk a trolk elemeit rvnytelen llapotban s a destruktorok ne vltsanak ki kivtelt. Az eljrsokon most azokat a fggvnyeket rtjk, melyeket a standard knyvtr megvalstsban felhasznlunk, teht a konstruktorokat, az rtkadsokat, a destruktorokat, illetve a bejrk mveleteit (E.4.4). A programoz ezeket a mveleteket knnyen megrhatja a knyvtr elvrsainak megfelelen. A kvetelmnyeket ltalban akkor is kielgtik eljrsaink, ha nem tudatosan figyelnk rjuk. A kvetkez tpusok biztosan kielgtik a standard knyvtr kvetelmnyeit a trolk elemtpusaira vonatkozan: 1. A beptett tpusok, kztk a mutatk 2. Azok a tpusok, melyek nem tartalmaznak felhasznli mveleteket 3. Az olyan mveletekkel rendelkez osztlyok, melyek nem vltanak ki kivteleket s nem hagyjk operandusaikat rvnytelen llapotban 4. Azok az osztlyok, melyek destruktora nem vlt ki kivtelt, s amelyeknl knnyen ellenrizhet, hogy a standard knyvtr ltal hasznlt eljrsok (a konstruktorok, az rtkadsok, a <, az == s a swap() fggvny) nem hagyjk operandusaikat rvnytelen llapotban Azt is ellenriznnk kell minden esetben, hogy a mveletek ne hozzanak ltre erforrslyukakat:
void f(Circle* pc, Triangle* pt, vector<Shape*>& v2) { vector<Shape*> v(10); // vektor ltrehozsa vagy bad_alloc kivtel kivltsa v[3] = pc; // nem vlt ki kivtelt v.insert(v.begin()+4,pt); // vagy beszrja a pt elemet, vagy nincs hatsa v-re v2.erase(v2.begin()+3); // vagy trli v2[3]-t, vagy nincs hatsa v2-re v2 = v; // vagy tmsolja v-t, vagy nincs hatsa v2-re // ... }

Amikor az f() futsa vget r, v szablyosan trldni fog, mg v2 rvnyes llapotban lesz. A fenti rszlet nem mutatja, ki felel a pc s a pt trlsrt. Ha f() a felels, akkor vagy el kell kapnia a kivteleket s gy kezelni a szksges trlseket, vagy a mutatkat loklis auto_ptr vltozkhoz kell ktnie. Ennl rdekesebb krds, mikor ad a knyvtr ers biztostst, azaz mely mveletek mkdnek gy, hogy vagy sikeresen futnak le, vagy semmilyen vltoztatst nem hajtanak vgre operandusaikon.

Forrs: http://www.doksi.hu

1286

Fggelkek s trgymutat

Pldul:
void f(vector<X>& vx) { vx.insert(vx.begin()+4,X(7)); // elem hozzadsa }

ltalban az X mveletei s a vector<X> osztly memriafoglalja vlthat ki kivtelt. Mit mondhatunk a vx elemeirl, ha az f() fggvny futsa kivtel kvetkeztben szakad meg? Az alapbiztosts garantlja, hogy erforrs-lyukak nem keletkeznek s a vx elemei rvnyes llapotban maradnak. De pontosan milyen elemekrl van sz? Elkpzelhet, hogy egy elem azrt trldik, mert az insert() csak gy tudja az alapbiztosts kvetelmnyeit visszalltani? Gyakran nem elg annyit tudnunk, hogy a trol j llapotban van, pontosan tudni akarjuk azt is, milyen llapotrl van sz. A kivtel kezelse utn ltalban tisztban szeretnnk lenni azzal, hogy milyen elemek szerepelnek a vektorban, mert ellenkez esetben komolyabb hibakezelst kellene vgeznnk.

E.4.1. Elemek beszrsa s trlse


Az elemek beszrsa egy trolba, illetve az elemek trlse onnan nyilvnval pldja azon mveleteknek, melyek a trolt megjsolhatatlan llapotban hagyhatnk egy kivtel bekvetkezsekor. Ennek oka leginkbb az, hogy a beszrs s a trls sorn sok olyan mveletet hajtunk vgre, amely kivtelt vlthat ki: 1. 2. 3. 4. 5. 6. j rtket msolunk a trolba. A trolbl eltvoltott elemet meg is kell semmistennk. Az j elem trolshoz nha memrit is kell foglalnunk. A vector s a deque elemeit nha j helyre kell thelyeznnk. Az asszociatv trolk sszehasonlt eljrsokat alkalmaznak az elemekre. Sok beszrs s trls esetben bejr mveleteket is vgre kell hajtanunk.

Ezek a mveletek mind okozhatnak kivteleket, ezt semmilyen biztosts (E.2) nem akadlyozza meg. Ahhoz, hogy ezekre az esetekre valamilyen biztostst nyjtsunk, elviselhetetlenl kltsges eljrsokra lenne szksg. Ennek ellenre a knyvtr vdi magt s a felhasznlkat a tbbi, felhasznli fggvny kivteleitl. Amikor lncolt adatszerkezeteken vgznk mveleteket (pldul egy list-en vagy map-en), gy szrhatunk be s tvolthatunk el elemeket, hogy a trol tbbi elemre nem vagyunk

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1287

hatssal. Ugyanez nem valsthat meg az olyan trolkban, ahol tbb elem szmra egyetlen, folytonos memriaterletet foglalunk le (pldul a vector s a deque esetben). Ilyenkor az elemeket nha j helyre kell mozgatnunk. Az alapbiztostson tl a standard knyvtr ers biztostst is nyjt nhny olyan mvelethez, amely elemeket szr be vagy trl. Mivel a lncolt adatszerkezetekkel megvalsthat trolk ebbl a szempontbl jelentsen eltrnek az elemek trolshoz folytonos memriaterletet hasznlktl, a standard knyvtr teljesen ms garancikat ad a klnbz trolfajtkhoz: 1. Garancik a vector (16.3) s a deque (17.2.3) osztlyra: Ha egy push_back() vagy egy push_front() mvelet okoz kivtelt, akkor az nem vltoztatja meg operandusait. Ha egy insert() utasts vlt ki kivtelt s azt nem egy elem msol konstruktora vagy rtkad mvelete okozta, akkor az sem vltoztat operandusain. Az erase() mvelet csak akkor vlt ki kivtelt, ha azt az elemek msol konstruktora vagy rtkad mvelete okozza. A pop_back() s a pop_front() nem okoz kivtelt. 2. A list (17.2.2) garancii: Ha egy push_back() vagy push_front() mvelet vlt ki kivtelt, akkor a fggvny hatstalan. Ha az insert() okoz kivtelt, akkor az nem vltoztatja meg operandusait. Az erase(), a pop_back(), a pop_front(), a splice() s a reverse() sohasem vlt ki kivtelt. Ha a prediktumok s az sszehasonlt fggvnyek nem okoznak kivtelt, akkor a list osztly remove(), remove_if(), unique(), sort() s merge() eljrsai sem vlthatnak ki kivtelt. 3. Garancik asszociatv trolkra (17.4): Ha egy elem beszrsa kzben az insert() kivtelt vlt ki, akkor a fggvny hatstalan. Az erase() nem okozhat kivtelt. Jegyezzk meg, hogy ha ers biztosts ll rendelkezsnkre egy trol valamelyik mveletben, akkor minden bejr, az elemekre hivatkoz sszes mutat s hivatkozs (referencia) rvnyes marad kivtel bekvetkezse esetn is. A szablyokat egy tblzatban foglalhatjuk ssze:

Forrs: http://www.doksi.hu

1288

Fggelkek s trgymutat

A trolmveletek garancii vector clear() erase() nem lehet kivtel (msols) nem lehet kivtel (msols) 1 elem insert() ers (msols) N elem insert() ers (msols) merge() push_back() push_front() pop_back() pop_front() remove() remove_if() reverse() splice() swap() ers nem lehet kivtel nem lehet kivtel deque list map nem lehet kivtel nem lehet kivtel ers alap nem lehet kivtel (sszehasonlts msolsa) unique() nem lehet kivtel (sszehasonlts)

nem lehet kivtel nem lehet kivtel (msols) nem lehet kivtel nem lehet kivtel (msols) ers (msols) ers (msols) ers ers nem lehet kivtel (sszehasonlts) ers ers ers ers

nem lehet kivtel nem lehet kivtel nem lehet kivtel nem lehet kivtel nem lehet kivtel (sszehasonlts) nem lehet kivtel (prediktum) nem lehet kivtel nem lehet kivtel

nem lehet kivtel nem lehet kivtel

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1289

A tblzat elemeinek jelentse: alap ers nem lehet kivtel A mvelet csak alapbiztostst nyjt (E.2). A mvelet ers biztostst nyjt (E.2). A mvelet nem vlthat ki kivtelt (E.2). A mvelet ebben a trolban nem szerepel tagfggvnyknt.

Ahol a biztosts megkveteli, hogy a felhasznl ltal megadott bizonyos mveletek ne vltsanak ki kivtelt, ott a biztosts alatt zrjelben feltntettk, milyen mveletekre kell figyelnnk. Ezek a kvetelmnyek pontosan megegyeznek a tblzat eltt, szvegesen megfogalmazott felttelekkel. A swap() fggvnyek abban klnbznek a tbbi eljrstl, hogy nem tagfggvnyek. A clear() fggvnyre vonatkoz garancia az erase() biztostsbl kvetkezik. (16.3.6) A tblzatban az alapbiztostson tli szolgltatsokat tntettk fel, teht nem szerepelnek azok az eljrsok (pldul a reverse() vagy a unique() a vector osztlyra), melyek tovbbi biztosts nlkl valstanak meg valamilyen algoritmust az sszes sorozatra. A majdnem-trol basic_string (17.5, 20.3) minden mveletre garantlja az alapbiztostst (E.5.1). A szabvny azt is biztostja, hogy a basic_string osztly erase() s swap() eljrsa nem okoz kivtelt, az insert() s a push_back() fggvnyre pedig ers biztostst kapunk. Az ers biztostst nyjt eljrsokban amellett, hogy a trol vltozatlan marad, az sszes bejr, mutat s referencia is rvnyes marad:
void update(map<string,X>& m, map<string,X>::iterator current) { X x; string s; while (cin>>s>>x) try { current = m.insert(current,make_pair(s,x)); } catch(...) { // itt a "current" mg mindig az aktulis elemet jelli } }

Forrs: http://www.doksi.hu

1290

Fggelkek s trgymutat

E.4.2. Garancik s kompromisszumok


Az alapbiztostson tli szolgltatsok sszevisszasgai a megvalstsi lehetsgekkel magyarzhatk. A programozk azt szeretnk leginkbb, hogy mindenhol ers biztosts lljon rendelkezskre a lehet legkevesebb korltozs mellett, de ugyanakkor azt is elvrjk, hogy a standard knyvtr minden mvelete optimlisan hatkony legyen. Mindkt elvrs jogos, de sok mvelet esetben lehetetlen egymssal prhuzamosan megvalstani. Ahhoz, hogy jobban megvilgtsuk az elkerlhetetlen kompromisszumokat, megvizsgljuk, milyen mdokon lehet egy vagy tbb elemet felvenni egy listba, vektorba vagy map-be. Nzzk elszr, hogy egy elemet hogyan vihetnk be egy listba vagy egy vektorba. Szoks szerint, a push_back() nyjtja a legegyszerbb lehetsget:
void f(list<X>& lst, vector<X>& vec, const X& x) { try { lst.push_back(x); // hozzads a listhoz } catch (...) { // lst vltozatlan return; } try { vec.push_back(x); // hozzads a vektorhoz } catch (...) { // vec vltozatlan return; } // lst s vec egy-egy x rtk j elemmel rendelkezik }

Az ers biztosts megvalstsa ez esetben egyszer s olcs. Az eljrs azrt is hasznos, mert teljesen kivtelbiztos megoldst ad az elemek felvtelre. A push_back() azonban asszociatv trolkra nem meghatrozott: a map osztlyban nincs back(). Egy asszociatv trol esetben az utols elemet a rendezs hatrozza meg, nem a pozci. Az insert() fggvny garancii mr kicsit bonyolultabbak. A gondot az jelenti, hogy az insert() mveletnek gyakran kell egy elemet a trol kzepn elhelyeznie. Lncolt adatszerkezeteknl ez nem jelent problmt, teht a list s a map egyszeren megvalsthat, a vector esetben azonban elre lefoglalt terlet ll rendelkezsnkre, a vector<X>::insert() fggvny egy tlagos megvalstsa pedig a beszrsi pont utni elemeket thelyezi, hogy helyet csinljon az j elem szmra. Ez az optimlis megolds, de arra nincs egyszer md-

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1291

szer, hogy a vektort visszalltsuk eredeti llapotba, ha valamelyik elem msol rtkadsa vagy msol konstruktora kivtelt vlt ki (lsd E.8[10-11]), ezrt a vector azzal a felttellel ad biztostsokat, hogy az elemek msol konstruktora nem vlt ki kivtelt. A list s a map osztlynak nincs szksge ilyen korltozsra, ezek knnyedn be tudjk illeszteni az j elemet a szksges msolsok elvgzse utn. Pldakppen ttelezzk fel, hogy az X msol konstruktora s msol rtkadsa egy X::cannot_copy kivtelt vlt ki, ha valamilyen okbl nem sikerl ltrehoznia a msolatot:
void f(list<X>& lst, vector<X>& vec, map<string,X>& m, const X& x, const string& s) { try { lst.insert(lst.begin(),x); // hozzads a listhoz } catch (...) { // lst vltozatlan return; } try { vec.insert(vec.begin(),x); // hozzads a vektorhoz } catch (X::cannot_copy) { // hopp: vec vagy rendelkezik, vagy nem rendelkezik j elemmel return; } catch (...) { // vec vltozatlan return; } try { m.insert(make_pair(s,x)); // hozzads az asszociatv tmbhz } catch (...) { // m vltozatlan return; } // lst s vec egy-egy x rtk j elemmel rendelkezik // m egy j (s,x) rtk elemmel rendelkezik }

Ha X::cannot_copy kivtelt kapunk, nem tudhatjuk, hogy az j elem bekerlt-e a vec trolba. Ha sikerlt beilleszteni az elemet, az rvnyes llapotban lesz, de pontos rtkt nem ismerjk. Az is elkpzelhet, hogy egy X::cannot_copy kivtel utn nhny elem titokza-

Forrs: http://www.doksi.hu

1292

Fggelkek s trgymutat

tosan megkettzdik (lsd E.8[11]), msik megvalstst alkalmazva pedig a vektor vgn lv elemek tnhetnek el, mert csak gy lehet biztostani, hogy a trol rvnyes llapotban maradjon s ne szerepeljenek benne rvnytelen elemek. Sajnos az ers biztosts megvalstsa a vector osztly insert() fggvnye esetben lehetetlen, ha megengedjk, hogy az elemek msol konstruktora kivtelt vltson ki. Ha egy vektorban teljesen meg akarnnk vdeni magunkat az elemek thelyezse kzben keletkez kivtelektl, a kltsgek elviselhetetlenl megnnnek az egyszer, alapbiztostst nyjt megoldshoz kpest. Sajnos nem ritkk az olyan elemtpusok, melyek msol konstruktora kivtelt eredmnyezhet. Mr a standard knyvtrban is tallhatunk pldt: a vector<string>, a vector< vector<double> > s a map<string, int> is ilyen. A list s a vector trol ugyanolyan biztostst ad az insert() egyelem s tbbelem vltozathoz, mert azok megvalstsi mdja azonos. A map viszont ers biztostst ad az egyelem beszrshoz, mg a tbbelemhz csak alapbiztostst. Az egyelem insert() a map esetben knnyen elkszthet ers biztostssal, a tbbelem vltozat egyetlen logikus megvalstsi mdja azonban a map esetben az, hogy az j elemeket egyms utn szrjuk be, s ehhez mr nagyon nehz lenne ers garancikat adni. A gondot itt az jelenti, hogy nincs egyszer visszalpsi lehetsg (nem tudunk korbbi sikeres beszrsokat visszavonni), ha valamelyik elem beszrsa nem sikerl. Ha olyan tbbelem beszr mveletre van szksgnk, amely ers biztostst ad, azaz vagy minden elemet hibtlanul beilleszt, vagy egyltaln nem vltoztatja meg a trolt, legegyszerbben gy valsthatjuk meg, hogy egy teljesen j trolt ksztnk, majd ennek sikeres ltrehozsa utn egy swap() mveletet alkalmazunk:
template<class C, class Iter> void safe_insert(C& c, typename C::const_iterator i, Iter begin, Iter end) { C tmp(c.begin(),i); // az ell lev elemek msolsa ideiglenes vltozba copy(begin,end,inserter(tmp,tmp.end())); // j elemek msolsa copy(i,c.end(),inserter(tmp,tmp.end())); // a zr elemek msolsa swap(c,tmp); }

Szoks szerint, ez a fggvny is hibsan viselkedhet, ha az elemek destruktora kivtelt vlt ki, ha viszont az elemek msol konstruktora okoz hibt, a paramterben megadott trol vltozatlan marad.

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1293

E.4.3. A swap()
A msol konstruktorokhoz s rtkadsokhoz hasonlan a swap() eljrsok is nagyon fontos szerepet jtszanak sok szabvnyos algoritmusban s kzvetlenl is gyakran hasznljk a felhasznlk. A sort() s a stable_sort() pldul ltalban a swap() segtsgvel rendezi t az elemeket. Teht ha a swap() kivtelt vlt ki, mikzben a trolban szerepl rtkeket cserlgeti, akkor a trol elemei a csere helyett vagy vltozatlanok maradnak, vagy megkettzdnek. Vizsgljuk meg a standard knyvtr swap() fggvnynek albbi, egyszer megvalstst (18.6.8):
template<class T> void swap(T& a, T& b) { T tmp = a; a = b; b = tmp; }

Erre teljesl, hogy a swap() csak akkor eredmnyezhet kivtelt, ha azt az elemek msol konstruktora vagy msol rtkadsa vltja ki. Az asszociatv trolktl eltekintve a szabvnyos trolk biztostjk, hogy a swap() fggvny ne vltson ki kivteleket. A trolkban ltalban gy is meg tudjuk valstani a swap() fggvnyt, hogy csak az adatszerkezeteket cserljk fel, melyek mutatknt szolglnak a tnyleges elemekhez (13.5, 17.1.3). Mivel gy magukat az elemeket nem kell mozgatnunk, azok konstruktorra vagy rtkad mveletre nincs szksgnk, teht azok nem kapnak lehetsget kivtel kivltsra. Ezenkvl a szabvny biztostja, hogy a knyvtr swap() fggvnye nem tesz rvnytelenn egyetlen hivatkozst, mutatt s bejrt sem azok kzl, melyek a felcserlt trolk elemeire hivatkoznak. Ennek kvetkeztben kivtelek egyetlen ponton lphetnek fel: az asszociatv trolk sszehasonlt objektumaiban, melyeket az adatszerkezet lerjnak rszeknt kell msolnunk. Teht az egyetlen kivtel, amit a szabvnyos trolk swap() eljrsa eredmnyezhet, az sszehasonlt objektum msol konstruktorbl vagy rtkad mveletbl szrmazik (17.1.4.1). Szerencsre az sszehasonlt objektumoknak ltalban annyira egyszer msol mveleteik vannak, hogy nincs lehetsgk kivtel kivltsra. A felhasznli swap() fggvnyek viszonylag egyszeren nyjthatnak ugyanilyen biztostsokat, ha gondolunk r, hogy mutatkkal brzolt adatok esetben elegend csak a mutatkat felcserlnnk, ahelyett, hogy lassan s preczen lemsolnnk a mutatk ltal kijellt tnyleges adatokat (13.5, 16.3.9, 17.1.3).

Forrs: http://www.doksi.hu

1294

Fggelkek s trgymutat

E.4.4. A kezdeti rtkads s a bejrk


Az elemek szmra val memriafoglals s a memriaterletek kezdeti rtkadsa alapvet rsze minden trolnak (E.3). Ebbl kvetkezik, hogy a fel nem tlttt (elksztetlen) memriaterleten objektumot ltrehoz szabvnyos eljrsok az uninitialized_fill(), az uninitialized_fill_n() s az uninitialized_copy() (19.4.4) semmikppen sem hagyhatnak ltrehozott objektumokat a memriban, ha kivtelt vltanak ki. Ezek az algoritmusok ers biztostst valstanak meg (E.2), amihez gyakran kell elemeket trlni, teht az a kvetelmny, miszerint a destruktoroknak tilos kivtelt kivltaniuk, elengedhetetlen ezeknl a fggvnyeknl is (lsd E.8[14]). Ezenkvl azoknak a bejrknak is megfelelen kell viselkednik, melyeket paramterknt adunk t ezeknek az eljrsoknak. Teht rvnyes bejrknak kell lennik, rvnyes sorozatokra kell hivatkozniuk, s a bejr mveleteknek (pldul a ++, a != vagy a * opertornak) nem szabad kivtelt kivltaniuk, ha rvnyes bejrkra alkalmazzuk azokat. A bejrk (itertorok) olyan objektumok, melyeket a szabvnyos algoritmusok s a szabvnyos trolk mveletei szabadon lemsolhatnak, teht ezek msol konstruktora s msol rtkadsa nem eredmnyezhet kivtelt. A szabvny garantlja, hogy a szabvnyos trolk ltal visszaadott bejrk msol konstruktora s msol rtkadsa nem vlt ki kivtelt, gy a vector<T>::begin() ltal visszaadott bejrt pldul nyugodtan lemsolhatjuk, nem kell kivteltl tartanunk. Figyeljnk r, hogy a bejrkra alkalmazott ++ vagy -- mvelet eredmnyezhet kivtelt. Pldul egy istreambuf_iterator (19.2.6) egy bemenethibt (logikusan) egy kivtel kivltsval jelezhet, egy tartomnyellenrztt bejr pedig teljesen szablyosan jelezheti kivtellel azt, hogy megprbltunk kilpni a megengedett tartomnybl (19.3). Akkor azonban nem eredmnyezhetnek kivtelt, ha a bejrt gy irnytjuk t egy sorozat egyik elemrl a msikra, hogy kzben a ++ vagy a -- egyetlen szablyt sem srtjk meg. Teht az uninitialized_fill(), az uninitialized_fill_n() s az uninitialized_copy() felttelezi, hogy a bejrkra alkalmazott ++ s -- mvelet nem okoz kivtelt. Ha ezt mgis megteszik, a szabvny megfogalmazsa szerint ezek nem is igazn bejrk, vagy az ltaluk megadott sorozat nem rtelmezhet sorozatknt. Most is igaz, hogy a szabvny nem kpes megvdeni a felhasznlt a sajt maga ltal okozott nem meghatrozhat viselkedstl (E.2).

E.4.5. Hivatkozsok elemekre


Ha elemre hivatkoz mutatt, referencit vagy bejrt adunk t egy eljrsnak, az tnkreteheti a listt azzal, hogy az adott elemet rvnytelenn teszi:

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1295

void f(const X& x) { list<X> lst; lst.push_back(x); list<X>::iterator i = lst.begin(); *i = x; // x listba msolsa // ... }

Ha az x vltozban rvnytelen rtk szerepel, a list destruktora nem kpes hibtlanul megsemmisteni az lst objektumot:
struct X { int* p; X() { p = new int; } ~X() { delete p; } // ...

};

void malicious() { X x; x.p = reinterpret_cast<int*>(7); f(x); }

// hibs x // idztett bomba

Az f() vgrehajtsnak befejeztvel meghvdik a list<X> destruktora, amely viszont meghvja az X destruktort egy rvnytelen rtkre. Ha megprbljuk a delete p parancsot vgrehajtani egy olyan p rtkre, amely nem 0 s nem is ltez X tpus rtkre mutat, az eredmny nem meghatrozhat lesz s akr a rendszer azonnali sszeomlst okozhatja. Egy msik lehetsg, hogy a memria rvnytelen llapotba kerl, ami sokkal ksbb, a program olyan rszben okoz megmagyarzhatatlan hibkat, amely teljesen fggetlen a tnyleges problmtl. Ez a hibalehetsg nem gtolja meg a programozkat abban, hogy referencikat s bejrkat hasznljanak a trolk elemeinek kezelsre, hiszen mindenkppen ez az egyik legegyszerbb s leghatkonyabb mdszer az ilyen feladatok elvgzshez. Mindenesetre rdemes klnsen elvigyzatosnak lennnk a trolk elemeire val hivatkozsokkal kapcsolatban. Ha egy trol psge veszlybe kerlhet, rdemes a kevsb gyakorlott felhasznlk szmra biztonsgosabb, ellenrztt vltozatokat is ksztennk, pldul megadhatunk egy olyan eljrst, amely ellenrzi, hogy az j elem rvnyes-e, mieltt beszrja azt a fontos trolba. Termszetesen ilyen ellenrzseket csak akkor vgezhetnk, ha pontosan ismerjk a trolban trolt elemek tpust.

Forrs: http://www.doksi.hu

1296

Fggelkek s trgymutat

ltalban, ha egy trol valamelyik eleme rvnytelenn vlik, a trolra alkalmazott minden tovbbi mvelet hibkat eredmnyezhet. Ez nem csak a trolk sajtja: brmely objektum, amely valamilyen szempontbl hibs llapotba kerl, a ksbbiekben brmikor okozhat problmkat.

E.4.6. Prediktumok
Szmos szabvnyos algoritmus s trol hasznl olyan prediktumokat, melyeket a felhasznlk adhatnak meg. Az asszociatv trolk esetben ezek klnsen fontos szerepet tltenek be: az elemek keresse s beszrsa is ezen alapul. A szabvnyos trolk mveletei ltal hasznlt prediktumok is okozhatnak kivteleket, s ha ez bekvetkezik, a standard knyvtr mveletei legalbb alapbiztostst nyjtanak, de sok esetben (pldul az egyelem insert() mveletnl) ers biztosts ll rendelkezsnkre (E.4.1). Ha egy troljn vgzett mvelet kzben egy prediktum kivtelt vlt ki, elkpzelhet, hogy az ott trolt elemek nem pontosan azok lesznek, amelyeket szeretnnk, de mindenkppen rvnyes elemek. Pldul ha az == okoz kivtelt a list::unique() (17.2.2.3) mvelet vgrehajtsa kzben, nem vrhatjuk el, hogy minden rtkismtlds eltnjn. A felhasznl mindssze annyit felttelezhet, hogy a listban szerepl rtkek rvnyesek maradnak (lsd E.5.3). Szerencsre a prediktumok ritkn csinlnak olyasmit, ami kivtelt eredmnyezhet. Ennek ellenre a felhasznli <, ==, s != prediktumokat figyelembe kell vennnk, amikor kivtelbiztossgrl beszlnk. Az asszociatv trolk sszehasonlt objektumairl a swap() mvelet vgrehajtsa sorn msolat kszl (E.4.3), ezrt rdemes biztostanunk, hogy azon prediktumok msol mveletei, melyeket felhasznlhatunk sszehasonlt objektumokknt, ne vlthassanak ki kivtelt.

E.5. A standard knyvtr tovbbi rszei


A kivtelbiztossg legfontosabb clja, hogy fenntartsuk az objektumok psgt s kvetkezetessgt, azaz az nll objektumok alap-invarinsa mindig igaz maradjon s az egymssal kapcsolatban ll objektumok se srljenek. A standard knyvtr szemszgbl nzve

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1297

a kivtelbiztossg fenntartsa a trolk esetben a legbonyolultabb. Ha a kivtelbiztossgra sszpontostunk, a standard knyvtr tbbi rsze nem tl rdekes, de a kivtelbiztossg szempontjbl a beptett tmb is egy trol, melyet feleltlen mveletekkel knnyen tnkretehetnk. A standard knyvtr fggvnyei ltalban csak olyan kivteleket vlthatnak ki, melyeket meghatroznak vagy amelyeket az ltaluk meghvott felhasznli mveletek eredmnyezhetnek. Emellett azok az eljrsok, melyek (kzvetve vagy kzvetlenl) memrit foglalnak le, a memria elfogyst kivtellel jelezhetik (ltalban az std::bad_alloc kivtellel).

E.5.1. Karakterlncok
A string objektumokon vgzett mveletek sokfle kivtelt okozhatnak, a basic_string viszont karaktereit a char_traits (20.2) osztly ltal biztostott fggvnyekkel kezeli s ezeknek nem szabad kivtelt okozniuk. A standard knyvtr char_traits objektumai nem vltanak ki kivteleket, s ha egy felhasznli char_traits valamelyik eljrsa eredmnyez ilyet, azrt a standard knyvtr semmilyen felelssget nem vllal. Klnsen fontos, hogy a basic_string osztlyban elemknt (karakterknt) hasznlt tpus nem rendelkezhet felhasznli msol konstruktorral s rtkadssal, mert gy nagyon sok kivtel-lehetsgtl szabadulunk meg. A basic_string nagyon hasonlt a szabvnyos trolkra (17.5, 20.3), elemei valjban egy egyszer sorozatot alkotnak, melyet a basic_string<Ch,Tr,A>::iterator vagy a basic_string<Ch,Tr,A>::const_iterator objektumokkal rhetnk el. Ennek kvetkeztben a string alapbiztostst (E.2) ad s az erase(), az insert(), a push_back() s a swap() (E.4.1) fggvny garancii a basic_string osztly esetben is rvnyesek. A basic_string<Ch,Tr,A>::push_back() pldul ers biztostst nyjt.

E.5.2. Adatfolyamok
Ha egy adatfolyamot megfelelen lltunk be, annak fggvnyei az llapotvltozsokat kivtelekkel jelzik (21.3.6). Ezek jelentse pontosan meghatrozott s nem okoznak kivtelbiztossgi problmkat. Ha egy felhasznli operator<<() vagy operator>>() eljrs okoz kivtelt, az gy jelenhet meg a programoz szmra, mintha azt az iostream knyvtr okozta volna. Ennek ellenre ezek a kivtelek nem hatnak az adatfolyam llapotra (21.3.3). Az adatfolyam ksbbi mveletei esetleg nem talljk meg az ltaluk vrt adatokat mert egy korbbi mvelet kivtelt vltott ki a szablyos befejezds helyett , de ma-

Forrs: http://www.doksi.hu

1298

Fggelkek s trgymutat

ga az adatfolyam nem vlik rvnytelenn. Szoks szerint az I/O problmk utn szksg lehet a clear() fggvny meghvsra, mieltt tovbbi rst vagy olvasst kezdemnyeznnk (21.3.3, 21.3.5). A basic_string osztlyhoz hasonlan az iostream is egy char_traits objektumra hivatkozik a karakterkezels megvalstshoz (20.2.1, E.5.1), teht felttelezheti, hogy a karaktereken vgzett mveletek nem okoznak kivtelt, illetve semmilyen biztostst nem kell adnia, ha a felhasznl megsrti ezt a kiktst. Ahhoz, hogy a standard knyvtr kellen hatkony optimalizlst alkalmazhasson, felttelezzk, hogy a locale (D.2) s a facet (D.3) objektumok sem okozhatnak kivtelt. Ha mgis gy mkdnek, akkor az azokat hasznl adatfolyamok rvnytelenn vlhatnak. Ennek ellenre a leggyakoribb ilyen kivtel az std::bad_cast a use_facet (D.3.1) fggvnyben csak olyan, felhasznl ltal rt programrszletekben fordul el, melyek fggetlenek a szabvnyos adatfolyamoktl, gy a legrosszabb esetben is csak a kirs flbeszakadst vagy hibs beolvasst eredmnyez, az adatfolyam (legyen az akr istream, akr ostream) rvnyes marad.

E.5.3. Algoritmusok
Eltekintve az uninitialized_copy(), az uninitialized_fill() s az uninitialized_fill_n() fggvnytl (E.4.4) a standard knyvtr az algoritmusokhoz alapbiztostst (E.2) ad. Ez azt jelenti, hogy ha a felhasznl ltal megadott objektumok a kvetelmnyeknek megfelelen viselkednek, az algoritmusok fenntartjk a standard knyvtr invarinsait s elkerlik az erforrs-lyukakat. A nem meghatrozott viselkeds elkerlse rdekben a felhasznli mveleteknek mindig rvnyes llapotban kell hagyniuk paramtereiket s a destruktoroknak nem szabad kivteleket kivltaniuk. Az algoritmusok maguk nem okoznak kivteleket, ehelyett visszatrsi rtkkn keresztl jelzik a problmkat. A keres algoritmusok pldul tbbnyire a sorozat vgt adjk vissza annak jelzsre, hogy nem talltk meg a keresett elemet (18.2). Teht a szabvnyos algoritmusokban keletkez kivtelek valjban mindig egy felhasznli eljrsbl szrmaznak. Ez azt jelenti, hogy a kivtel vagy az egyik elemen vgzett mvelet prediktum (18.4), rtkads vagy swap() kzben jtt ltre, vagy egy memriafoglal (19.4) okozta. Ha egy ilyen mvelet kivtelt okoz, az algoritmusok azonnal befejezik mkdsket s az algoritmust elindt fggvny feladata lesz, hogy a kivtelt kezelje. Nhny algoritmus esetben elfordulhat, hogy a kivtel akkor kvetkezik be, amikor a trol llapota a felhasznl szempontjbl elfogadhatatlan. Nhny rendez eljrs pldul az elemeket ideigle-

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1299

nesen egy tmeneti trba msolja s ksbb innen teszi azokat vissza az eredeti trolba. Egy ilyen sort() eljrs esetleg sikeresen kimsolja az elemeket a trolbl (azt tervezve, hogy hamarosan a megfelel sorrendben rja azokat vissza), helyesen vgzi el a trlst is, de ezutn azonnal kivtel kvetkezik be. A felhasznl szempontjbl a trol teljesen megsemmisl, ennek ellenre minden elem rvnyes llapotban van, teht az alapbiztosts megvalstsa egyszer feladat. Gondoljunk r, hogy a szabvnyos algoritmusok a sorozatokat bejrkon keresztl rik el, sohasem kzvetlenl a trolkon dolgoznak, hanem azok elemein. A tny, hogy ezek az algoritmusok soha nem kzvetlenl vesznek fel elemeket egy trolba vagy trlnek elemeket onnan, leegyszersti annak vizsglatt, hogy egy kivtelnek milyen kvetkezmnyei lehetnek. Ha egy adatszerkezetet csak konstans bejrkon, mutatkon vagy referencikon (pldul const Rec*) keresztl rhetnk el, ltalban nagyon egyszeren ellenrizhetjk, hogy a kivtelek mvelnek-e valamilyen veszlyes dolgot.

E.5.4. A valarray s a complex


A szmkezel fggvnyek sem okoznak kifejezetten kivteleket (22. fejezet), de a valarray osztlynak memrit kell foglalnia, gy hasznlatakor elfordulhat std::bad_alloc kivtel. Ezenkvl a valarray s a complex kaphat olyan elemtpust is (skalrokat), amely kivteleket vlthat ki. Szoks szerint a szabvny alapbiztostst (E.2) nyjt, de a kivtelek ltal megszakadt szmtsok eredmnyrl semmit sem felttelezhetnk. A basic_string osztlyhoz hasonlan (E.5.1) a valarray s a complex is felttelezheti, hogy a sablonparamterben megadott tpus nem rendelkezik felhasznli msol mveletekkel, teht egyszeren, bjtonknt msolhat. A standard knyvtr numerikus tpusainak tbbsge a sebessgre optimalizlt, gy felttelezi, hogy elemtpusai nem okoznak kivteleket.

E.5.5. A C standard knyvtra


A standard knyvtr kivtel-meghatrozs nlkli mveletei az adott C++-vltozattl fggen vlthatnak ki kivteleket, a C standard knyvtrnak fggvnyeinl azonban biztosak lehetnk abban, hogy csak akkor okoznak kivteleket, ha a nekik paramterknt tadott eljrsok kivtelt okoznak, hiszen vgeredmnyben ezeket a fggvnyeket C programok is hasznljk s a C-ben nincsenek kivtelek. Egy szp megvalsts a szabvnyos C fggvnyeket res kivtel-meghatrozssal adhatja meg (throw()), ezzel lehetsget adhat a fordtnak jobb kd ellltsra.

Forrs: http://www.doksi.hu

1300

Fggelkek s trgymutat

Az olyan fggvnyek, mint a qsort() vagy a bsearch(), egy fggvnyre hivatkoz mutatt vesznek t paramterknt, gy okozhatnak kivtelt, ha paramterk kpes erre. Az alapbiztosts (E.2) ezekre a fggvnyekre is kiterjed.

E.6. Javaslatok a knyvtr felhasznli szmra


A standard knyvtr vizsglatakor a kivtelbiztossgra gy tekinthetnk, mint egy problmamentest eszkzre, amely sok mindentl megvd minket, ha nem okozunk sajt magunknak kellemetlensgeket. A knyvtr mindaddig helyesen fog mkdni, amg a felhasznli eljrsok teljestik az alapkvetelmnyeket (E.2). A szabvnyos trolk mveletei ltal kivltott kivtelek tbbnyire nem okoznak memria-elszivrgst s a trolt rvnyes llapotban hagyjk. Teht a knyvtr hasznlinak a legfontosabb krds a kvetkez: hogyan hatrozzuk meg sajt tpusainkat ahhoz, hogy elkerljk a kiszmthatatlan viselkedst s a memria-lyukak keletkezst? Az alapszablyok a kvetkezk: 1. Amikor egy objektumot frisstnk, soha ne mdostsuk az eredeti brzolst addig, amg az j rtket teljesen ltre nem hoztuk s nem biztostottuk, hogy kivtel veszlye nlkl le tudjuk cserlni az rtket. Pldakppen nzzk meg a vector::operator=(), a safe_assign() vagy a vector::push_back() fggvny megvalstst az E.3 pontban. 2. Mieltt kivtelt vltunk ki, szabadtsunk fel minden olyan lefoglalt erforrst, amelyet nem ktttnk (ms) objektumhoz. 2a A kezdeti rtkads az erforrs megszerzsvel mdszer (14.4) s a nyelv szablyai, melyek szerint a rszben ltrehozott objektumok olyan mrtkben trldnek, amennyire ltrejttek (14.4.1), nagyban elsegtik ezt a clt. Pldakppen nzzk meg a leak() fggvnyt. (E.2). 2b Az uninitialized_copy() s testvrei automatikus erforrs-felszabadtst tesznek lehetv, ha egy objektumhalmaz ltrehozsa nem sikerl (E.4.4). 3. Mieltt kivtelt vltunk ki, ellenrizzk, hogy minden operandus rvnyes llapotban van-e, azaz minden objektumot olyan llapotban kell hagynunk, hogy az ksbb szablyosan elrhet s trlhet legyen anlkl, hogy nem meghatrozhat eredmnyeket kapnnk s a destruktornak kivtelt kellene kivltania. Pldakppen a vector rtkadst emlthetjk (E.3.2).

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1301

3a A konstruktorok abban is eltrnek az tlagos eljrsoktl, hogy ha ezekben keletkezik kivtel, nem jn ltre objektum, amelyet ksbb trlnnk kellene. Ebbl kvetkezik, hogy ha egy konstruktorban kell kivtelt kivltanunk, akkor nem kell invarinst helyrelltanunk, de minden erforrst fel kell szabadtanunk, amit a konstruktor megszakadsa eltt lefoglaltunk. 3b A destruktorok abban klnbznek a tbbi mvelettl, hogy ha itt kivtel keletkezik, szinte biztosan elrontunk valamilyen invarinst s akr a terminate() fggvny azonnali meghvst is elidzhetjk. A gyakorlatban ezeket a szablyokat meglepen nehz betartani. Ennek legfbb oka az, hogy a kivtelek gyakran ott kvetkeznek be, ahol egyltaln nem vrjuk azokat. Egy j plda erre az std::bad_alloc. Brmely fggvny okozhatja ezt a kivtelt, amely kzvetve vagy kzvetlenl hasznlja a new opertort vagy egy allocator objektumot memria lefoglalshoz. Bizonyos programokban ezt a hibt elkerlhetjk, ha nem ignylnk a lehetsgesnl tbb memrit, az olyan programok esetben azonban, amelyek elg sokig futnak vagy jelents mennyisg adatot kell feldolgozniuk, fel kell kszlnnk a legklnbzbb hibkra az erforrs-foglalsokkal kapcsolatban. Ez azt jelenti, hogy feltteleznnk kell, hogy minden fggvny kpes brmely kivtel kivltsra, amg mst nem bizonytottunk rjuk. A meglepetsek elkerlsnek egyik mdja az, hogy csak olyan elemekbl ptnk trolkat, melyek nem hasznlnak kivteleket (pldul mutatkbl vagy egyszer, konkrt tpusokbl) vagy lncolt trolkat (pldul list) hasznlunk, melyek ers biztostst nyjtanak (E.4). A msik, ellenttes megkzelts, hogy elssorban az olyan mveletekre szmtunk, melyek ers biztostst nyjtanak (pldul a push_back()). Ezek vagy sikeresen befejezdnek, vagy egyltaln nincs hatsuk (E.2), de nmagukban nem elegendek az erforrslyukak elkerlsre s csak rendezetlen, pesszimista hibakezelst s helyrelltst tesznek lehetv. A vector<T*> pldul tpusbiztos, ha a T tpuson vgzett mveletek nem okoznak kivteleket, de ha kivtel kvetkezik be a vector objektumban s nem gondoskodunk valahol a mutatott objektumok trlsrl, azonnal memria-lyukak keletkeznek. Ebbl kvetkezik, hogy be kell vezetnnk egy Handle osztlyt, amely mindig elvgzi a szksges felszabadtsokat (25.7), s az egyszer vector<T*> helyett a vector< Handle<T> > szerkezetet kell hasznlnunk. Ez a megolds az egsz programot rugalmasabb teszi. Amikor j programot ksztnk, lehetsgnk van arra, hogy tgondoltabb megkzeltst talljunk s biztostsuk, hogy erforrsainkat olyan osztlyokkal brzoljuk, melyek invarinsa alapbiztostst nyjt (E.2). Egy ilyen rendszerben lehetsg nylik arra, hogy kivlasszuk a ltfontossg objektumokat s ezek mveleteihez visszagrgetsi mdszereket alkalmazzunk (azaz ers biztostst adhatunk nhny egyedi felttel mellett).

Forrs: http://www.doksi.hu

1302

Fggelkek s trgymutat

A legtbb program tartalmaz olyan adatszerkezeteket s programrszeket, melyeket a kivtelbiztossgra nem gondolva rtak meg. Ha szksg van r, ezek a rszek egy kivtelbiztos keretbe gyazhatk. Az egyik lehetsg, hogy biztostjuk, hogy kivtelek ne kvetkezzenek be (ez trtnt a C standard knyvtrval, E.5.5), a msik megolds pedig az, hogy felletosztlyokat hasznlunk, melyekben a kivtelek viselkedse s az erforrsok kezelse pontosan meghatrozhat. Amikor olyan j tpusokat terveznk, amelyek kivtelbiztos krnyezetben futnak majd, kln figyelmet kell szentelnnk azoknak az eljrsoknak, melyeket a standard knyvtr hasznlni fog: a konstruktoroknak, a destruktoroknak, az rtkadsoknak, sszehasonltsoknak, swap fggvnyeknek, a prediktumknt hasznlt fggvnyeknek s a bejrkat kezel eljrsoknak. Ezt legknnyebben gy valsthatjuk meg, hogy egy j osztlyinvarinst hatrozunk meg, amelyet minden konstruktor knnyedn biztosthat. Nha gy kell megterveznnk az osztlyinvarinst, hogy az objektumoknak legyen egy olyan llapota, melyben egyszeren trlhetk, ha egy mvelet kellemetlen helyen tkzik hibba. Idelis esetben ez az llapot nem egy mestersgesen megadott rtk, amit csak a kivtelkezels miatt kellett bevezetni, hanem az osztly termszetbl kvetkez llapot (E.3.5). Amikor kivtelbiztossggal foglalkozunk, a f hangslyt az objektumok rvnyes llapotainak (invarinsainak) meghatrozsra s az erforrsok megfelel felszabadtsra kell helyeznnk. Ezrt nagyon fontos, hogy az erforrsokat kzvetlenl osztlyokkal brzoljuk. A vector_base (E.3.2) ennek egyszer pldja. Az ilyen erforrs-osztlyok konstruktora alacsonyszint erforrsokat foglal le (pldul egy memriatartomnyt a vector_base esetben), s invarinsokat llt be (pldul a mutatkat a megfelel helyekre lltja a vector_base osztlyban). Ezen osztlyok destruktora egyszeren felszabadtja a lefoglalt erforrst. A rszleges ltrehozs szablyai (14.4.1) s a kezdeti rtkads az erforrs lefoglalsval mdszer (14.4) alkalmazsa lehetv teszi, hogy az erforrsokat gy kezeljk. Egy jl megrt konstruktor minden objektum esetben belltja a megfelel invarinst (24.3.7.1), teht a konstruktor olyan rtket ad az objektumnak, amely lehetv teszi, hogy a tovbbi mveleteket egyszeren meg tudjuk rni s sikeresen vgre tudjuk hajtani. Ebbl kvetkezik, hogy a konstruktoroknak gyakran kell erforrst lefoglalniuk. Ha ezt nem tudjk elvgezni, kivtelt vlthatnak ki, gy az objektum ltrehozsa eltt foglalkozhatunk a jelentkez problmkkal. Ezt a megkzeltst a nyelv s a standard knyvtr kzvetlenl tmogatja (E.3.5). Az a kvetelmny, hogy az erforrsokat fel kell szabadtanunk s az operandusokat rvnyes llapotban kell hagynunk a kivtel kivltsa eltt, azt jelenti, hogy a kivtelkezels terheit megosztjuk a kivtelt kivlt fggvny, a hvsi lncban lev fggvnyek s a kivtelt tnylegesen kezel eljrs kztt. Egy kivtel kivltsa nem azt a hibakezelsi stlust jelen-

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1303

ti, hogy hagyjuk az egszet valaki msra. Minden fggvnynek, amely kivtelt vlt ki vagy ad tovbb, ktelessge felszabadtani azokat az erforrsokat, melyek hatskrbe tartoznak, operandusait pedig megfelel rtkre kell lltania. Ha az eljrsok ezt a feladatot nem kpesek vgrehajtani, a kivtelkezel nemigen tehet mst, minthogy megprblja szpen befejezni a program mkdst.

E.7. Tancsok
[1] Legynk tisztban azzal, milyen szint kivtelbiztossgra van szksgnk. E.2. [2] A kivtelbiztossgnak egy teljes kr hibatrsi stratgia rsznek kell lennie. E.2. [3] Az alapbiztostst minden osztlyhoz rdemes megvalstani, azaz az invarinsokat mindig tartsuk meg s az erforrs-lyukakat mindig kerljk el. E.2, E.3.2, E.4. [4] Ahol lehetsg s szksg van r, valstsunk meg ers biztostst, azaz egy mvelet vagy sikeresen hajtdjon vgre, vagy minden operandust hagyja vltozatlanul. E.2, E.3. [5] Destruktorokban ne fordulhasson el kivtel. E.2, E.3.2, E.4. [6] Ne vltson ki kivtelt egy rvnyes sorozatban mozg bejr. E.4.1, E.4.4. [7] A kivtelbiztossg foglalja magban az nll mveletek alapos vizsglatt. E.3. [8] A sablon osztlyokat gy tervezzk meg, hogy azok tltszak legyenek a kivtelek szmra. E.3.1. [9] Az init() fggvny helyett hasznljunk konstruktort az erforrsok lefoglalshoz. E.3.5. [10] Adjunk meg invarinst minden osztlyhoz, hogy ezzel pontosan meghatrozzuk rvnyes llapotaikat. E.2, E.6. [11] Gyzdjnk meg rla, hogy objektumaink mindig rvnyes llapotba llthatk anlkl, hogy kivtelektl kellene tartanunk. E.3.2, E.6. [12] Az invarinsok mindig legyenek egyszerek. E.3.5. [13] Kivtel kivltsa eltt minden objektumot lltsunk rvnyes llapotba. E.2, E.6. [14] Kerljk el az erforrs-lyukakat. E.2, E.3.1, E.6. [15] Az erforrsokat kzvetlenl brzoljuk. E.3.2, E.6. [16] Gondoljunk r, hogy a swap() fggvny gyakran hasznlhat az elemek msolsa helyett. E.3.3.

Forrs: http://www.doksi.hu

1304

Fggelkek s trgymutat

[17] Ha lehetsg van r, a try blokkok hasznlata helyett a mveletek sorrendjnek j megvlasztsval kezeljk a problmkat. E.3.4. [18] Ne trljk a rgi informcikat addig, amg a helyettest adatok nem vlnak biztonsgosan elrhetv. E.3.3, E.6. [19] Hasznljuk a kezdeti rtkads az erforrs megszerzsvel mdszert. E.3, E.3.2, E.6. [20] Vizsgljuk meg, hogy asszociatv trolinkban az sszehasonlt mveletek msolhatk-e. E.3.3. [21] Keressk meg a ltfontossg adatszerkezeteket s ezekhez adjunk meg olyan mveleteket, melyek ers biztostst adnak. E.6.

E.8. Gyakorlatok
1. (*1) Soroljuk fel az sszes kivtelt, amely elfordulhat az E.1 pont f() fggvnyben. 2. (*1) Vlaszoljunk az E.1 pontban, a plda utn szerepl krdsekre. 3. (*1) Ksztsnk egy Tester osztlyt, amely idnknt a legalapvetbb mveletekben okoz kivtelt, pldul a msol konstruktorban. A Tester osztly segtsgvel prbljuk ki sajt standard knyvtrunk trolit. 4. (*1) Keressk meg a hibt az E.3.1 pontban szerepl vector konstruktornak rendezetlen vltozatban s rjunk programot, amely tnkreteszi az osztlyt. Ajnls: elszr rjuk meg a vector destruktort. 5. (*2) Ksztsnk egyszer listt, amely alapbiztostst nyjt. llaptsuk meg nagyon pontosan, milyen kvetelmnyeket kell a felhasznlnak teljestenie a biztosts megvalstshoz. 6. (*3) Ksztsnk egyszer listt, amely ers biztostst nyjt. Alaposan ellenrizzk az osztly mkdst. Indokoljuk meg, mirt tartjuk ezt a megoldst biztonsgosabbnak. 7. (*2.5)rjuk jra a 11.12 String osztlyt gy, hogy ugyanolyan biztonsgos legyen, mint a szabvnyos trolk. 8. (*2) Hasonltsuk ssze a vector osztlyban meghatrozott rtkads s a safe_assign() fggvny klnbz vltozatait a futsi id szempontjbl. (E.3.3) 9. (*1.5) Msoljunk le egy memriafoglalt az rtkad opertor hasznlata nlkl (hiszen az operator=() megvalstshoz erre van szksgnk az E.3.3 pontban).

Forrs: http://www.doksi.hu

E. Kivtelbiztossg a standard knyvtrban

1305

10. (*2) rjunk a vector osztlyhoz alapbiztostssal egy egyelem s egy tbbelem erase(), illetve insert() fggvnyt.E.3.2. 11. (*2) rjunk a vector osztlyhoz ers biztostssal egy egyelem s egy tbbelem erase(), illetve insert() fggvnyt (E.3.2). Hasonltsuk ssze ezen fggvnyek kltsgt s bonyolultsgt az elz feladatban szerepl fggvnyekvel. 12. (*2) Ksztsnk egy safe_insert() fggvnyt (E.4.2), amely egy ltez vector objektumba szr be elemet (nem pedig egy ideiglenes vltozt msol le). Milyen kiktseket kell tennnk a mveletekre? 13. (*2.5) Hasonltsuk ssze mret, bonyolultsg s hatkonysg szempontjbl a 12. s a 13. feladatban szerepl safe_insert() fggvnyt az E.4.2 pontban bemutatott safe_insert() fggvnnyel. 14. (*2.5) rjunk egy jobb (gyorsabb s egyszerbb) safe_insert() fggvnyt, kifejezetten asszociatv trolkhoz. Hasznljuk a traits eljrst egy olyan safe_insert() megvalstshoz, amely automatikusan kivlasztja az adott trolhoz optimlis megvalstst. Ajnls: 19.2.3. 15. (*2.5) Prbljuk megrni az uninitialized_fill() fggvnyt (19.4.4, E.3.1) gy, hogy az megfelelen kezelje a kivteleket kivlt destruktorokat is. Lehetsges ez? Ha igen, milyen ron? Ha nem, mirt nem? 16. (*2.5) Keressnk egy trolt egy olyan knyvtrban, amely nem tartozik a szabvnyhoz. Nzzk t dokumentcijt s llaptsuk meg, milyen kivtelbiztossgi lehetsgek llnak rendelkezsnkre. Vgezznk nhny tesztet, hogy megllaptsuk, mennyire rugalmas a trol a memriafoglalsbl vagy a felhasznl ltal megadott programrszekbl szrmaz kivtelekkel szemben. Hasonltsuk ssze a tapasztaltakat a standard knyvtr megfelel troljnak szolgltatsaival. 17. (*3) Prbljuk optimalizlni az E.3 pontban szerepl vector osztlyt a kivtelek lehetsgnek figyelmen kvl hagysval. Pldul trljnk minden try blokkot. Hasonltsuk ssze az gy kapott vltozat hatkonysgt a standard knyvtr vector osztlynak hatkonysgval. Hasonltsuk ssze a kt vltozatot mret s bonyolultsg szempontjbl is. 18. (*1) Adjunk meg invarinst a vector osztly (E.3) szmra gy, hogy megengedjk, illetve megtiltjuk a v==0 esetet (E.3.5). 19. (*2.5) Nzzk vgig egy vector osztly megvalstsnak forrskdjt. Milyen biztosts ll rendelkezsnkre az rtkadsban, a tbbelem insert() utastsban s a resize() fggvnyben? 20. (*3) rjuk meg a hash_map (17.6) olyan vltozatt, amely ugyanolyan biztonsgos, mint a szabvnyos trolk.

You might also like