Professional Documents
Culture Documents
C++ Könyv (2005, 1291 Oldal)
C++ Könyv (2005, 1291 Oldal)
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.
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
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.
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.
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].
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].
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.
Forrs: http://www.doksi.hu
1. Megjegyzsek az olvashoz
13
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.
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.
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.
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.
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
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.
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
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
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
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
// rtket vissza nem ad fggvny // lebegpontos szm kezdeti rtkadsa // egsz kezdeti rtkadsa // sszeg rtkadsa // szorzat rtkadsa
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;
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;
} 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
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.
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
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.
a stack.h nev fjlba kerl, a felhasznlk pedig ezt az gynevezett fejllomnyt (header) beptik (#include):
#include "stack.h" // a fellet beptse
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
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
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 {
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.
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);
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
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
};
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
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.
Forrs: http://www.doksi.hu
44
Bevezets
};
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
A complex s Stack tpusokat konkrt tpusnak nevezzk, ellenttben az absztrakt tpusokkal, ahol a fellet tkletesebben elszigeteli a felhasznlt a megvalsts rszleteitl.
// kivtel // kivtel
Forrs: http://www.doksi.hu
46
Bevezets
};
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
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)
};
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); }
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:
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.
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
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.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]; }
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.
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
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.
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
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.
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
// a szabvnyos karakterlnc-szolgltatsok elrhetv ttele // std nevek elrhetv ttele az std:: eltag nlkl // rendben: a string jelentse std::string
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; }
Forrs: http://www.doksi.hu
61
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'; }
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
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"); }
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
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.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)
// cm
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
65
cout << "rja be a nevt!\n"; cin >> str; cout << "Hell, " << str << "!\n";
Ha begpeljk a kvetkezt
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!
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
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; // ... }
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
69
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
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 }
Forrs: http://www.doksi.hu
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.
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
73
// 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()); }
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
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'); }
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
77
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
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
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
79
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); // ... }
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
81
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
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
83
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); // ... }
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]; // ... }
Forrs: http://www.doksi.hu
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
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.
// = 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.
Forrs: http://www.doksi.hu
96
Alapok
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.
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.
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
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.
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
Forrs: http://www.doksi.hu
104
Alapok
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"; }
(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.
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 & [] ()
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.
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
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 ___
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;
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; // ... }
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; }
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
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; // ... }
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
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'
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
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); }
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.
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
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
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'; }
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"; // ... }
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
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[ ].
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
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.
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
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
127
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
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
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;
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; }
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:
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
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 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
// 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
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
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.
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
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; }
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
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
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.
Forrs: http://www.doksi.hu
139
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
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=')'
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
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:
+ * / % & | ^ << >>
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
A v referencit hasznljuk arra, hogy a radius-hoz tartoz double rtkre hivatkozzunk, amg az expr() a bemeneti karakterekbl kiszmtja a 6378.388 rtket.
// 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 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:
Egy opertor talaktsa az opertornak megfelel szimblumra magtl rtetd, mivel az opertorok token_value rtkt az opertor egsz rtkeknt hatroztuk meg (4.8).
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).
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]).
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.
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
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.
azt jelenti, hogy ha i kisebb vagy egyenl 0-nl VAGY max kisebb i-nl. Ez egyenrtk az albbival:
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.
Forrs: http://www.doksi.hu
166
Alapok
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.
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
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
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; }
// csompont visszaadsa
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
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; }
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 {
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"; }
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.).
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); // ... }
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(); // ... }
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
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.
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.";
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
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) // ...
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;
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"; }
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 }
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.
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';
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.
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.
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
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
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).
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
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
// 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.
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.
// 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.
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.
// 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); }
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
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
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
Hibalehetsget rejt magban, ha egy nevet gy adunk meg egy begyazott hatkrben, hogy a nv elfedi ugyanannak a nvnek egy kls hatkrben lev deklarcijt.
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
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.
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");
// 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, ©, &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
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
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 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(;;)
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)();
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
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; // ... };
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
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() { /* ... */ }
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 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).
// ...
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.
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
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.
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.
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
brval:
Parser'
Parser
Driver
Parser megvalsts
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
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); }
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
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.
Forrs: http://www.doksi.hu
238
Alapok
bool operator==(const Date&, const std::string&); std::string format(const Date&); // ... // string brzols
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 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.
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()
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+; }
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.
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');
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');
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
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 // ...
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
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.
// 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.
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 {
Forrs: http://www.doksi.hu
8. Nvterek s kivtelek
253
} // ...
Ilyen gyakran rossz stlusra utal egymsba gyazott kivtelkezelket azonban ritkn runk.
Forrs: http://www.doksi.hu
254
Alapok
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(); } // ...
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
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
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() { }
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:
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.
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.
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) { /* ... */ }
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
// deklarci
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); }
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"); }
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
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
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
#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;
#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
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 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
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
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
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
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;
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.
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
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);
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).
};
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
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.
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); };
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); }
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();
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).
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
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; }
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();
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; };
// 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; }
// 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.
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.
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; }
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
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;
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
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.
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
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.
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.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.
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
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&); };
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
Forrs: http://www.doksi.hu
10. Osztlyok
325
} return *this;
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.
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
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;
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]; // ... }
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
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.
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
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
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
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); // ... }
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
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"; }
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
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
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 ().
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); }
A complex elz definicijt adottnak vve a fenti kt kezdeti rtkads jelentse azonos.
Forrs: http://www.doksi.hu
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.
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
Forrs: http://www.doksi.hu
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.
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:
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
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.
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.
Forrs: http://www.doksi.hu
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.
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+(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 }
Forrs: http://www.doksi.hu
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;
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
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
Forrs: http://www.doksi.hu
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).
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
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.
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
// d hozzrendelse z.im-hez
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 /=
};
Forrs: http://www.doksi.hu
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
Felhasznli szemszgbl nzve az itt bemutatott complex osztly szinte azonos a complex<double>-lal (lsd a standard knyvtrbeli <complex>-et, 22.5).
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
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 ? }
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); }
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
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; }
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;
Az elemzs itt is alulrl felfel trtnik, egyszerre csak egy opertornak s paramtereinek figyelembe vtelvel.
Forrs: http://www.doksi.hu
366
Absztrakcis mdszerek
Forrs: http://www.doksi.hu
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).
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
Forrs: http://www.doksi.hu
369
};
A tagfggvnyeket csak az adott osztly objektumaira alkalmazhatjuk; felhasznli talaktst a fordt nem vgez:
void g() { 99.m1(); 99.m2(); }
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
371
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
373
// 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)
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).
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
375
String* p1 = new String("Eric"); String* p2 = new String(10); } return 10; // hiba: nincs automatikus int ->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
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
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'; }
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); }
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
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->(); }
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
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
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;
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.
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
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
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.
Forrs: http://www.doksi.hu
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&); };
// msols, ha szksges
// a msols megakadlyozsa
Forrs: http://www.doksi.hu
386
Absztrakcis mdszerek
String(); String(const char*); String(const String&); String& operator=(const char *); String& operator=(const String&); ~String(); }; // ...
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
Forrs: http://www.doksi.hu
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; }
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; }
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
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
389
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
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
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
397
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:
Forrs: http://www.doksi.hu
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 = ⅇ 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
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'; // ... }
// hiba!
Forrs: http://www.doksi.hu
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
Forrs: http://www.doksi.hu
402
Absztrakcis mdszerek
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
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
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; } } }
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
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.
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
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
// 2.5.4
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
411
// 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
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);
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
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.
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.
};
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
415
else { } // ...
void some_fct() { Ival_box* p1 = new Ival_slider(0,5); interact(p1); Ival_box* p2 = new Ival_dial(1,12); interact(p2);
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();
};
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 { /* ... */ };
Ival_slider
Ival_dial
Popup_ival_slider
Flashing_ival_slider
Forrs: http://www.doksi.hu
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.
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
419
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 { /* ... */ };
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
421
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 { /* ... */ };
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
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 { /* ... */ }; // ...
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.
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
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
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); }
// BB hasznlata // LS hasznlata
Forrs: http://www.doksi.hu
426
Absztrakcis mdszerek
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
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
429
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
};
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 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;
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
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
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.
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; }
Megfordtva, a nem tpusba tartoz paramterek a sablonon bell llandk, gy a paramter rtknek mdostsra tett ksrlet hibnak szmt.
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. )
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; }
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).
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>(); }
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.
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)
// max<int>(int('a'),1) // max<double>(2.7,double(4))
Forrs: http://www.doksi.hu
13. Sablonok
445
// 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 }
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]) };
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).
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);
gy mr lerhatjuk a kvetkezt:
void f(String<char> swede1, String<char> swede2) { compare(swede1,swede2); compare<char,Literate>(swede1,swede2); }
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); }
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.
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.
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 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
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
};
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.
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;
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; };
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
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
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; }
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; }
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); }
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.
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.
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.
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
// 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.
Forrs: http://www.doksi.hu
14. Kivtelkezels
475
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).
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.
// 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.
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) }
// ... } 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
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.
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) {} // ... };
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
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; } }
};
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.
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.
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(); } }
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
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
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
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
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
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.
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
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
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.
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(); }
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
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.
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
A kivtel-specifikci nem rsze a fggvny tpusnak, a typedef-ek pedig nem is tartalmazhatnak ilyet:
typedef void (*PF)() throw(X); // hiba
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
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
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); } };
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 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.
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.
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.
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 {
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.
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: // ... };
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
// 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
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.
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.
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
};
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(); } };
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.
Forrs: http://www.doksi.hu
516
Absztrakcis mdszerek
class Task { // ... void debug(double p); }; class Displayed { // ... void debug(int v); };
class Satellite : public Task, public Displayed { // ... }; void g(Satellite* p) { p->debug(1); p->Task::debug(1); p->Displayed::debug(1);
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
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).
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
};
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
};
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
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 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.
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 { /* ... */ };
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
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
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_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
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).
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.
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
// 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
Forrs: http://www.doksi.hu
15. Osztlyhierarchik
535
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)
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); }
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:
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_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); }
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 {
// 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]).
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
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).
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.
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&); // ... };
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 }
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
Ez nagyon fontos, mert annak valsznsge, hogy valaki informciknak olyan halmazval tud elllni, amely egymagban minden felhasznl ignyeit kielgti, a nullval egyenl.
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; }
Forrs: http://www.doksi.hu
15. Osztlyhierarchik
549
else { } }
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(); }
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
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
};
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; }
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(); // ... };
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) }
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
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.
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&);
};
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(); // ... }
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
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
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
565
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.
Forrs: http://www.doksi.hu
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).
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).
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
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
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
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.
Forrs: http://www.doksi.hu
572
A standard knyvtr
};
template<class T> class List { public: class Link { /* ... */ }; List(); void put(T*); T* get(); }; // ...
// optimlis
Forrs: http://www.doksi.hu
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
};
Forrs: http://www.doksi.hu
574
A standard knyvtr
Grafikus formban (szaggatott vonallal jelezve a megvalsts a felhasznlsval kapcsolatot): Vector List
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
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]).
Forrs: http://www.doksi.hu
577
class List { Link* head; Link* curr; public: Link* get(); void put(Link*); // ... };
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
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
581
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
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*
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
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
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
587
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
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();
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
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); }
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());
Forrs: http://www.doksi.hu
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
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
593
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
};
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
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
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
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.
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.
// 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
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]++; }
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
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
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.
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
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; }
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(); // ... }
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
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
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.
Forrs: http://www.doksi.hu
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
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
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?
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.
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
615
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
konstans+
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
617
vektor:
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
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
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
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
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
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
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
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);
Forrs: http://www.doksi.hu
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
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
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
631
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(); // ... }
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
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(); } }
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
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)
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
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
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
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
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
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
645
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
647
} else {
// "Pali"-t beszrtuk
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
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
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
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
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;
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";
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
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
// 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
657
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.
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
659
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
// 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
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.
rtk
trlt
kvetkez
rtk
trlt
kvetkez
Forrs: http://www.doksi.hu
662
A standard knyvtr
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
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)
};
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
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
// 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
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.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
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
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); };
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.
Forrs: http://www.doksi.hu
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
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
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
681
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
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
683
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; }
Forrs: http://www.doksi.hu
685
};
// 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'; }
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.
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
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.
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")); // ... }
Forrs: http://www.doksi.hu
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.
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
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; } };
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
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); }
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 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
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
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.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)); }
};
Forrs: http://www.doksi.hu
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.)
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);
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
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'); }
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.
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
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
// 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.
Forrs: http://www.doksi.hu
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; }
Forrs: http://www.doksi.hu
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
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
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.
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
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
711
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()); }
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
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
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"); }
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).
Forrs: http://www.doksi.hu
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());
// 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.
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
717
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.
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
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).
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
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.
Forrs: http://www.doksi.hu
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
725
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
727
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.
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
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
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.
Forrs: http://www.doksi.hu
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.
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= ++ -++ -- + - += -= == != == != < > <= >=
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
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
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
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
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
// 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.
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
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); }
};
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).
Forrs: http://www.doksi.hu
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.
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
};
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
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
749
// eltag // uttag
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; } };
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
Forrs: http://www.doksi.hu
751
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
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
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
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
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); }
A rgi aktulis pozci rvnytelenn vlik, ezrt szksgnk van az index() fggvnyre, amely egy Checked_iter trolsra s visszalltsra hasznlhat.
Forrs: http://www.doksi.hu
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.
Forrs: http://www.doksi.hu
759
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
761
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
} }; // ...
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
763
};
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; }
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
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; } // ... }
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.
Forrs: http://www.doksi.hu
767
};
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.
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
// 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
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
771
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];
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.
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
773
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
// 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;
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
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
// 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.
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"
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()); // ... }
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; }
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]).
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()); // ... }
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; }
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
Forrs: http://www.doksi.hu
800
A standard knyvtr
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
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>&);
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.
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); } // ...
// 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.
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
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
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
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
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 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;
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 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
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
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
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.
Forrs: http://www.doksi.hu
824
A standard knyvtr
// olvass u-ba
// olvass f-be
Forrs: http://www.doksi.hu
21. Adatfolyamok
825
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 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.
Forrs: http://www.doksi.hu
828
A standard knyvtr
// 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 }; // ...
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
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'); // ... }
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];
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; }
Az is.putback(c) fggvnyhvsra azrt van szksg, hogy a c legyen a kvetkez karakter, amit az is adatfolyambl beolvasunk (21.6.4).
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 {
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.
Forrs: http://www.doksi.hu
21. Adatfolyamok
837
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;
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
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
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).
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.
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)
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';
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).
Forrs: http://www.doksi.hu
846
A standard knyvtr
};
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.
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 }
Forrs: http://www.doksi.hu
21. Adatfolyamok
849
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
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); }
// 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& 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;
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
cout << d << ' ' << gen4(d) << ' ' << sci8(d) << ' ' << d << '\n';
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.
Forrs: http://www.doksi.hu
856
A standard knyvtr
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;
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
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 */ } }
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;
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
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.
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.
Forrs: http://www.doksi.hu
864
A standard knyvtr
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 // ...
};
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.
// 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).
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
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.
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
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
// 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;
Forrs: http://www.doksi.hu
874
A standard knyvtr
// 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.
// 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");
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
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;
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.
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
};
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;
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
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
Forrs: http://www.doksi.hu
22. Szmok
891
// 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
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).
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.
// 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
};
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); }
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"); } // ...
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
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
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)); }
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
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;
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;
};
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)];
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;
Forrs: http://www.doksi.hu
22. Szmok
909
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.
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; } // ...
};
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.
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
// 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.
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;
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 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
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); // ... }
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.
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());
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 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
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
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
23.4 23.5
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
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
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
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.
Forrs: http://www.doksi.hu
936
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
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
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
939
Forrs: http://www.doksi.hu
940
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
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.
Forrs: http://www.doksi.hu
942
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
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
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).
Forrs: http://www.doksi.hu
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
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).
Forrs: http://www.doksi.hu
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
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
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
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
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
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
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
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
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
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
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.
Forrs: http://www.doksi.hu
958
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
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
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
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.
Forrs: http://www.doksi.hu
962
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
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
Forrs: http://www.doksi.hu
964
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
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.
Forrs: http://www.doksi.hu
966
(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
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
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
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.
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
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
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
[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
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
[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
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.
Forrs: http://www.doksi.hu
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
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.
Forrs: http://www.doksi.hu
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
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
981
Forrs: http://www.doksi.hu
982
Forrs: http://www.doksi.hu
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
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
985
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.
Forrs: http://www.doksi.hu
986
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
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.
Forrs: http://www.doksi.hu
988
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
989
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).
Forrs: http://www.doksi.hu
990
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
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
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
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
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
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).
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; } };
Forrs: http://www.doksi.hu
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
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.
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
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
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);
Termszetesen a kzsen hasznlt objektumok kezelse kln vatossgot kvn, fleg a prhuzamos feldolgozst tmogat rendszerekben.
Forrs: http://www.doksi.hu
999
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
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
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
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
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.
Forrs: http://www.doksi.hu
1004
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
1005
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
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.
Annak meghatrozsa, hogy A osztly az A::p mutatn keresztl truhzza (deleglja) a feladatot B-re, ilyen kdot eredmnyezne:
Forrs: http://www.doksi.hu
1007
class A { B* p; // ... void f(); void ff(); void g() { p->g(); } void h() { p->h(); };
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
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.
Forrs: http://www.doksi.hu
1009
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
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; } }; // ...
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]; }
Forrs: http://www.doksi.hu
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); // ... }
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
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 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); // ... }
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
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); // ... }
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
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);
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)); // ... }
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
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.
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
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
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
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
Forrs: http://www.doksi.hu
1019
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
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
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
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.
Forrs: http://www.doksi.hu
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
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 };
Forrs: http://www.doksi.hu
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).
Forrs: http://www.doksi.hu
1026
};
virtual T*& operator[](int) { throw Bad_oper("Container::[](int)"); } virtual T*& operator[](const char*) { throw Bad_oper("Container::[](char*)"); } // ...
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
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
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
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
Forrs: http://www.doksi.hu
1032
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.)
Forrs: http://www.doksi.hu
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
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
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.
Forrs: http://www.doksi.hu
1036
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
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].)
Forrs: http://www.doksi.hu
1038
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
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
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
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
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
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
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
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
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();
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
};
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;
};
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
1047
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
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; }
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
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
class Window { // ... virtual void draw(); }; class Cowboy { // ... virtual void draw(); };
// kp megjelentse
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
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.
Forrs: http://www.doksi.hu
1052
// [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 // ... }
};
// [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
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);
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
Ler
brzols
Forrs: http://www.doksi.hu
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
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; } } }; // ...
Forrs: http://www.doksi.hu
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; } }
};
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
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.
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
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
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
1061
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
[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
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
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
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
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 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
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.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.
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
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
// 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
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
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.
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() { }
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 */
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 */
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
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 */ }
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
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
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).
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"; }
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.
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.
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
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
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
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.
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
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'
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.
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
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.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 }
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; }
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); // ... }
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
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; } }
m:
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'; } }
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
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
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.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; };
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; };
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; // ... }
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'; }
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.
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.
// 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++;
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
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(); // ... } }
// hiba: nincs globlis f() // hiba: nincs globlis Y // hiba: nincs f() X-ben // rendben
Forrs: http://www.doksi.hu
C. Technikai rszletek
1147
C.11. Hozzfrs-szablyozs
Ebben a pontban a hozzfrs-szablyozs olyan vonatkozsaira mutatunk pldkat, melyekrl a 15.3 pontban nem volt sz.
Forrs: http://www.doksi.hu
1148
Fggelkek s trgymutat
// hiba: priv privt // hiba: prot vdett s f() se nem bart, se nem X vagy Y tagja // rendben: publ nyilvnos
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
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
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
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;
};
class Inner { int x; T y; // rendben: Inner egy "bart" public: void f(Outer* p, int v); };
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
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.
Forrs: http://www.doksi.hu
1154
Fggelkek s trgymutat
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
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.
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
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!"); }
Forrs: http://www.doksi.hu
C. Technikai rszletek
1157
template<class T> class list { class link { /* ... */ }; public: typedef link* iterator; iterator begin(); iterator end(); }; // ...
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(); // ... }
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
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; // ... }
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.
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
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.
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
Forrs: http://www.doksi.hu
C. Technikai rszletek
1171
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
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.
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
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).
// 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
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
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:
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
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.
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:
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.
// 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.
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 };
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
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).
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
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
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]).
Forrs: http://www.doksi.hu
D. Helyi sajtossgok
1197
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
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
Szabvnyos facet-ek Kategria D.4.1 D.4.2 collate numeric Rendeltets Karakterlncok sszehasonltsa Facet-ek collate_byname<Ch>
D.4.3
Id I/O zenet-visszakeress
time_put_byname<Ch> messages_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).
Forrs: http://www.doksi.hu
D. Helyi sajtossgok
1201
};
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.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
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
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)
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]).
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]; }; };
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
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
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)
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
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.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"
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));
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 }; };
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)
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
cout << d << endl; Date dd; while (cin >> dd) cout << dd << endl; // az %x formtummal ltrehozott // dtumok olvassa
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
Forrs: http://www.doksi.hu
1232
Fggelkek s trgymutat
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
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);
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
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; }
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.
// 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
};
};
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)
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);
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
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;
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)
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;
Forrs: http://www.doksi.hu
1254
Fggelkek s trgymutat
Ha a katalgus
<<< hell viszlt >>> <<< igen nem taln >>>
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
Forrs: http://www.doksi.hu
D. Helyi sajtossgok
1255
};
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
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
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
Forrs: http://www.doksi.hu
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
Forrs: http://www.doksi.hu
1267
vector:
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
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; }
Forrs: http://www.doksi.hu
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
1271
};
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
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
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
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); }
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
1277
// az brzolsok felcserlse
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.
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
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
1281
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
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
1283
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)); // ... }
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.
Forrs: http://www.doksi.hu
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.
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
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
Forrs: http://www.doksi.hu
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
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
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
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
Forrs: http://www.doksi.hu
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; } // ...
};
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.
Forrs: http://www.doksi.hu
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
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.
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.
Forrs: http://www.doksi.hu
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
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
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.