Egy kis extra: hibakezelés, final, öröklődés vs. tárolók, többszörös öröklődés
Kivételkezelés folytatás...¶
Nem osztály típusú hibák¶
A hibakezelés elején említésre került, hogy általában osztály típusúak a hibák, azonban ez C++ esetében nem feltétlenül kell így legyen (Java esetén csak így lehetett, ráadásul ott az sem volt mindegy, melyik osztályból származik a kivétel objektumok osztálya). C++-ban bármit el lehet dobni, bármilyen értéket; legyen az const char*
, std::string
, int
, stb. Természetesen ezeket is elkaphatjuk referencia szerint, azonban primitív típusok esetén ez nem szükséges. Ami azonban fontos, hogyha a dobott kivétel típusa nem származik az std::exception
osztályból, akkor természetesen nem is tudjuk elkapni az ennek megfelelő ágban ezeket a kivételeket.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Kimenet
A hiba: 42
Ez egy const char* tipusu literal
Minden hiba kezelése¶
Mivel egyes hibák nem tartoznak az std::exception
alá, így általánosan nem kezelhetők. Azonban ha szeretnénk megoldani, hogy biztosan elkapjunk minden hibát, minden lehetséges hibatípusra kell catch ágat írnunk. Azonban ez nem biztos, hogy minden esetben lehetséges (vagy azért mert rengeteg féle-fajta hiba van, amiket azonosan akarunk kezelni, vagy pedig azért mert nem is tudjuk pontosan, hogy milyen hibákra kell felkészíteni a programunkat). Azonban ezt is megoldhatjuk, ha elkapáskor a típusok helyett egyszerűen csak ...
(három pontot) írunk. Ezzel bármit el tudunk kapni a catch ágban.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Kimenet
Ismeretlen hiba 1
Ismeretlen hiba 2
Final osztályok, metódusok¶
A Java nyelvben lehetőség van arra, hogy a final
kulcsszóval biztosítsuk, hogy adott esetben egy metódus ne legyen felülírható egy leszármazott osztályban, vagy ugyanezzel jelezhettük azt is, ha nem akarjuk azt, hogy egy osztályból lehessen másik osztályt származtatni.
A C++ 11 szabvány a C++ nyelvben is lehetőséget teremt erre, ugyanakkor a final
C++ esetében nem lesz kulcsszó. Ez azt jelenti, hogy a megfelelő környezetben (a metódus vagy osztály neve után) azt a szerepet tölti be, mint Java esetében, minden egyéb esetben csak egy egyszerű azonosítónak számít.
Öröklődés vs. tárolók¶
Elképzelhető, hogy polimorfikus típus elemeit szeretnénk valamilyen tárolóban tárolni. Mondjuk különböző típusú Hangszer
objektumokat szeretnénk eltárolni, amelyek között lehet Zongora
, Fuvola
, Dob
, stb. Minden hanszer meg tud szólalni valamilyen hangon, de egy tetszőleges Hangszer
objektumra nyilván nehéz megmondani, hogyan is fog megszólalni.
Ebben az esetben tehát érdemes a Hangszer
szolj()
metódusát virtuálisan definiálni, így amikor megszólaltatunk egy-egy hangszert, az mind a saját hangját tudja visszaadni.
Egy zenekarban sok-sok hangszer lehet, különböző típusúak, ezeket valahogy hozzá kell rendelnünk a zenekarhoz. Nyilván a legpraktikusabb az, ha ezt valami tároló segítségével oldjuk meg.
A következő példában csak egyetlen speciális hangszert emelünk ki, és a zenekart sem hozzuk létre, csak a main
metódusban kipróbáljuk, mi történik akkor, ha érték szerint próbáljuk eltárolni egy vektorban a meghatározott hangszerünket:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Kimenet
???
A probléma látszik. A containerbe érték szerint bemásolva a Fuvola
objektumot hasonló helyzetet eredményez, mint az érték szerinti paraméterátadás, ahol az objektum elvesztette a speciális tulajdonságait, és a polimorfikus hívás így nem tudott érvényesülni.
Ehelyett nyilván megoldás lehet, ha valahogy az objektumra mutató referenciát tudnánk eltárolni, de ezt nem tudjuk megtenni.
std::reference_wrapper
¶
A fenti problémára megoldás lehet az std::reference_wrapper
használata, amely gyakorlatilag egy változóba csomagolja nekünk a Fuvola
objektumra vonatkozó referenciát. Módosítsuk a main
függvényt az alábbiak szerint:
1 2 3 4 5 6 |
|
Ekkor megszólaltatva a tároló elemét (ha több is lenne, akkor elemeit), már a kívánt eredményt kapjuk:
Kimenet
Trilla-trilla
Fontos! A tárolóba a példában lokálisan létrehozott objektum került. Azaz az erre vonatkozó referencia is addig létezik, ameddig maga a lokális objektum él. Az ilyen módon létrehozott tároló nem lenne alkalmas arra, hogy globálisan kezelje a hangszereket. Ahhoz, hogy ezt megtehessük, más eszközöket kell majd alkalmazzunk.
Többszörös öröklődés¶
C++-ban lehetőségünk van arra, hogy egy osztály több másik osztályból származzon. Az egyetlen "probléma" ilyenkor a névütközések kezelése, azaz az, ha az osztály több helyről is örököl ugyanolyan szignatúrájú elemet. Amennyiben a duplán örökölt tagok nem virtuálisak, akkor egyszerűen a szkópolt nevükkel tudunk rájuk a gyerek osztályban hivatkozni, legyen akár adat tagról, vagy metódusról szó.
Virtuális metódusok esetében a virtuális táblája öröklődik az ősöknek. Amennyiben nem akarjuk direkt a gyerek típusra hívni az adott metódust, csak az ősön keresztül, akkor egyértelmű, hogy melyik táblát kell használni a hívás feloldásához. Ha azonban a többszörösen származtatott osztály objektumán keresztül szeretnénk a duplán örökölt metódust hívni, azt csak akkor tehetjük meg, ha a származtatott osztályban felülírtuk a metódust.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
|
Kimenet
A nem virtualis metodusa - value: 5
B nem virtualis metodusa - value: 1
C virtualis metodusa - A::value: 5; B::value 1
C virtualis metodusa - A::value: 5; B::value 1
C virtualis metodusa - A::value: 5; B::value 1
Létrehozva: 2024-07-24