9. előadás
Letöltés
- Példaprogramok
- Nyomtatáshoz, 3 dia/lap (282K)
- Nyomtatáshoz, 6 dia/lap (276K)
- Képernyőre, 1 dia/lap (színes) (261K)
- E-könyv olvasóhoz, 1 dia/lap (283K)
1.
Programozás alapjai II. (9. ea) C++ többszörös öröklés, cast, perzisztencia, kivételek Szeberényi Imre, Somogyi Péter BME IIT <szebi@iit.bme.hu> MŰE GYET E M 1 782 C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. -1 -
2.
Öröklés ism. • Egy osztályból olyan újabb osztályokat származtatunk, amelyek rendelkeznek az eredeti osztályban már definiált tulajdonságokkal, viselkedéssel. • Analitikus - Korlátozó √ • Egyszeres - Többszörös C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. -2 -
3.
Többszörös öröklés • Ha két osztály merőben különbözik, de mindkettőben valamit meg kell valósítani a másik számára. • Az új osztálynak többféle arcot kell mutatnia (mutatókonverzió). • Sokszor kiváltható barát függvényekkel, de nem a legjobb megoldás. • Objektum és a környezet (pl. grafikus) kapcsolata. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. -3 -
4.
Többszörös öröklés/2 • Két vagy több bázisosztályból származtatunk. • Több OO nyelv nem támogatja, mert bonyolult implementálni. • Ezekben a nyelvekben interfésszel váltják ki a többszörös öröklést. • Leggyakrabban grafikus interfész és a modell kapcsolatánál használjuk. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. -4 -
5.
Példa: Nyomógomb és callback fv. A grafikus rendszer kezeli a felhasználói eseményeket. Ha megnyomják (áthaladnak fölötte, elengedik, stb.), meghívja az alkalmazás megfelelő függvényét (callback), amit korábban az alkalmazás közölt a gombbal. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. -5 -
6.
Példa: Kapcsoló Gombnyomásra ki-be lehessen kapcsolni – A (grafikus) felhasználói felületen megvalósított gomb megkap minden felhasználói inputot. Amikor azt kapja, hogy "megnyomták", akkor szól a kapcsolónak. • Hogyan szól neki, ha nem ismeri? • Próbálkozzunk származtatással! C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. -6 -
7.
Kapcsoló a grafikus felületből? Gomb callBack() Kapcsolo Virtuális fv. • A grafikának semmi köze a funkcionalitáshoz! • Eddig úgy tudtuk, hogy a Kapcsoló a Drot-ból származik! callBack() Megvalósítás C++ programozási nyelv Felhasználói felület (UI) eleme © BME-IIT Sz.I. • A grafikus felülettől függ a működés? 2021.04.19. -7 -
8.
Kapcsoló többszörös örökléssel Drot GombCallback Gomb set callBack() cb.obj.ref. Kapcsolo UI Virtuális fv. callBack() Funkciójában ebből származik Könnyű ilyenné konvertálni Megvalósítás C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. -8 -
9.
Kapcsoló megvalósítása class GombCallback { // callback funkcióhoz public: virtual void callBack() = 0; // virtuális cb. függvény }; class Gomb { // felhasználói felület objektuma GombCallback &cb; // objektum referencia public: Gomb (GombCallback &t) :cb(t) {}// referencia inic. void Nyom() { cb.callBack(); } // megnyomták .... }; C++ programozási nyelv © BME-IIT Sz.I. GombCallback lehetne funktor mintájú, amitől talán elegánsabb a kód, de könnyen összeütközhet a modell függvényhívás operátorával. 2021.04.19. -9 -
10.
Kapcsoló megvalósítása/2 class Kapcsolo :public Drot, public GombCallback { int be; // állapot public: Az osztály kompatibilis a GombCallback void ki(); osztállyal, amin keresztül a Gomb osztály eléri a callBack függvényt. void be(); .... void callBack() { if (be) ki(); else be(); } // callback }; class Gomb { GombCallback &cb; Kapcsolo k1; public: Gomb g1(k1); Gomb (GombCallback &t) :cb(t) {} void Nyom() { cb.callBack(); } }; https://git.ik.bme.hu/Prog2/eloadas_peldak/ea_09 C++ programozási nyelv © BME-IIT Sz.I. nyomogomb 2021.04.19. - 10 -
11.
Töbsz. öröklés + fv. overload struct A { void f() {} void f(int) {} }; struct B { void f(double){} }; Melyik f? struct AB : public A, public B { using A::f; using B::f; void f() { f(1); f(1.2); } }; ... AB ab; ab.f(); ab.f(5); ab.f(6.3); Feltételezés: A többszörös öröklésnél merőben eltérőek az alap-osztályok, az azonos nevű függvények más-más funkciót látnak el. https://git.ik.bme.hu/Prog2/eloadas_peldak/ea_09 C++ programozási nyelv © BME-IIT Sz.I. polimorf 2021.04.19. - 11 -
12.
Többszörös öröklés problémái • Többszörös öröklés különös figyelmet igényel, ha előfordulhat, hogy egy alaposztály különböző leszármazottjai "összetalálkoznak". • Ekkor ún. rombusz v. "diamond" struktúra alakul ki. • Példa: irodai hierarchia C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 12 -
13.
Irodai hierarchia Alkalmazott nev, fizetes Csop.vez. csoport Hat.ideju ido Hat.ideju.cs.v. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 13 -
14.
Irodai hierarchia /2 class Alkalmazott { protected: String nev; // név double fizetes; // fizetés public: Alkalmazott(String n, double fiz); }; class CsopVez :public Alkalmazott { csop_t csoport; // csoport azon. public: CsopVez(String n, double f, csop_t cs) :Alkalmazott(n, f), csoport(cs) { } }; C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 14 -
15.
Irodai hierarchia /3 class HatIdeju :public Alkalmazott { time_t ido; // szerződése lejár ekkor public: HatIdeju(String n, double f, time_t t) :Alkalmazott(n, f), ido(t) { } }; class HatIdCsV :public CsopVez, public HatIdeju { public: HatIdCsV(String n, double f, csop_t cs, time_t t) :CsopVez(n, f, cs), HatIdeju(n, f, t) { } }; Két neve és két fizetése van ? C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 15 -
16.
Elérhetők ezek a mezők? class HatIdCsV :public CsopVez, public HatIdeju { public: HatIdCsV(String n, double f, csop_t cs, time_t t) :CsopVez(n, f, cs), HatIdeju(n, f, t) { } void Kiir() { cout << CsopVez :: nev << endl; cout << HatIdeju :: nev << endl; }; C++ programozási nyelv } A scope operátorral kiválasztható, tehát önmagában ez még nem lenne baj, de baj lehet belőle. © BME-IIT Sz.I. 2021.04.19. - 16 -
17.
Memóriakép Alkalmazott nev fizetes CsopVez nev fizetes csoport C++ programozási nyelv HatIdCsV nev fizetes csoport nev fizetes ido © BME-IIT Sz.I. HatIdeju nev fizetes ido 2021.04.19. - 17 -
18.
Miből fakad a probléma ? • Többszörös elérés az öröklési gráfon. Miért nem vonja össze a fordító ? • A nevek ütközése az öröklés megismert szabályai alapján még nem jelent bajt. • Lehet hogy szándékos az ütközés. • Automatikus összevonás esetén a kompatibilitás veszélybe kerülhet. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 18 -
19.
Megoldás: Virtuális alaposztály class CsopVez :virtual public Alkalmazott { .... Csak az öröklési lánc legvégén hívódik public: meg az alaposztály konstruktora. CsopVez(String n, double f, csop_t cs) :Alkalmazott(n, f),// csak lánc végén csoport(cs) { } }; class HatIdeju :virtual public Alkalmazott{ .... }; class HatIdCsV :public CsopVez, public HatIdeju { public: HatIdCsV(String n, double f, csop_t cs, time_t t) :Alkalmazott (n, f), // csak ha a lánc vége CsopVez(NULL, 0, cs), // tudjuk, hogy az ős konstr. HatIdeju(NULL, 0, t) { } // nem itt hívódik, ezért null https://git.ik.bme.hu/Prog2/eloadas_peldak/ea_09 C++ programozási nyelv © BME-IIT Sz.I. iroda 2021.04.19. - 19 -
20.
Virtuális alaposztály 1. Alaposztály adattagjai nem épülnek be a származtatott osztály adattagjaiba. A virtuális függvényekhez hasonlóan indirekt elérésűek lesznek. 2. Az alaposztály konstruktorát nem az első származtatott osztály konstruktora fogja hívni, hanem az öröklési lánc legvégén szereplő osztály konstruktora. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 20 -
21.
Memóriakép virt. alaposztállyal Alkalmazott nev fizetes CsopVez csoport virtual &Alkalmazott HatIdCsV csoport nev fizetes &Alkalmazott ido nev fizetes C++ programozási nyelv © BME-IIT Sz.I. HatIdeju ido &Alkalmazott nev fizetes &Alkalmazott 2021.04.19. - 21 -
22.
Irodai példa virt. alaposztállyal class Alkalmazott { ... }; class HatIdeju :virtual public Alkalmazott{ ... }; class CsopVez :virtual public Alkalmazott { ... }; class HatIdCsV :public CsopVez, public HatIdeju { ... }; class HatIdCsVH :public HatIdCsV { ... }; Melyik konstruktor hívja az Alkalmazott konstruktorát ? Alkalmazott melos("Lusta Dick", 100); // Alkalmazott HatIdeju grabo("Grabovszki", 300); // HatIdeju CsopVez fonok("Mr. Gatto ", 5000); // CsopVez HatIdCsV gore("Mr. Tejfel", 3000); // HatIdCsV HatIdCsVH ("Safranek", 500); // HatIdCsVH Aki a lánc végen van. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 22 -
23.
És, ha nem lenne virtuális ? class Alkalmazott { ... }; class HatIdeju :public Alkalmazott{ ... }; class CsopVez :public Alkalmazott { ... }; class HatIdCsV :public CsopVez, public HatIdeju { ... }; class HatIdCsVH :public HatIdCsV { ... }; Melyik konstruktor hívja az Alkalmazott konstruktorát ? Alkalmazott melos("Lusta Dick", 100); // Alkalmazott HatIdeju grabo("Grabovszki", 300); // HatIdeju CsopVez fonok("Mr. Gatto ", 5000); // CsopVez HatIdCsV gore("Mr. Tejfel", 3000); // CsopVez, HatIdeju HatIdCsVH ("Safranek", 500); // CsopVez, HatIdeju Aki az első a láncban. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 23 -
24.
Elkerülhető a többsz. öröklés? • Egyes OO nyelvekben nincs többszörös öröklés, de van helyette interfész, amivel pótolható a hiánya. C++ -ban ilyen nincs, ezért teljesen nem kerülhető el. • Biztosan nem kerülhető el, ha – mindkét osztály "arcát" mutatni kell (pl. ny.gomb) – mindkét osztályt valamiért alaposztállyá kell konvertálni (upcast) C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 24 -
25.
Konstruktor feladatai • Öröklési lánc végén hívja a virtuális alaposztályok konstruktorait. • Hívja a közvetlen, nem virtuális alaposztályok konstruktorait. • Létrehozza a saját részt: – beállítja a virtuális alaposztály mutatóit – beállítja a virtuális függvények mutatóit – hívja a tartalmazott objektumok konstruktorait – végrehajtja a programozott törzset C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 25 -
26.
Destruktor feladatai • Megszünteti a saját részt: – végrehajtja a programozott törzset – tartalmazott objektumok destruktorainak hívása – virtuális függvénymutatók visszaállítása – virtuális alaposztály mutatóinak visszaállítása • Hívja a közvetlen, nem virtuális alaposztályok destruktorait. • Öröklési lánc végén hívja a virtuális alaposztályok destruktorait. https://git.ik.bme.hu/Prog2/eloadas_peldak/ea_09 C++ programozási nyelv © BME-IIT Sz.I. ctor_dtor 2021.04.19. - 26 -
27.
Hívható-e konstruktorból saját virt. fv? • Hivható, de nem az történik, amit várunk. Az alaposztály konstruktora a származtatott obj. konstruktora előtt fut le, így a virtuális függvénypointerek beállítása nem történik meg. B konstr. még nem futott. struct A { A() { f(); } virtual void f() { cout << "A::f";} }; struct B :public A { B() { } void f() { cout << "B::f"; } }; C++ programozási nyelv © BME-IIT Sz.I. Kiírás: A::f 2021.04.19. - 27 -
28.
Hívható-e destruktorból saját virt. fv? • Hívható, de nem az történik, amit várunk. Az alaposztály destruktora a származtatott obj. destruktora után fut le, így a virtuális függvénypointerek már visszaálltak. B destr. már lefutott. struct A { ~A() { f(); } virtual void f() { cout << "A::f";} }; struct B :public A { ~B() { } void f() { cout << "B::f"; } }; C++ programozási nyelv © BME-IIT Sz.I. Kiírás: A::f 2021.04.19. - 28 -
29.
Konstruktor/destruktor, mint őrszem • Gyakori, hogy erőforrásként kell kezelni valamit (memória, fájl, eszköz, stb.): – lefoglalás – feldolgozás – felszabadítás • Az ilyen esetekben külön figyelmet kell fordítani arra, hogy a feldolgozás közben észlelt hiba esetén is gondoskodjunk a felszabadításról. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 29 -
30.
Konstruktor/destruktor, mint őrszem/2 { // Kódrészlet ... std::ifstream inp(f); // megnyitás ... // file feldolgozása if (inp.fail()) throw "baj_van"; // !!! nincs lezárva a fájl !!! inp.close(); // lezárás normál esetben } A példa sántít, mert az ifstream a példány megszűnésekor bezárja a fájlt. Köszönöm Anonymus előadáson tett megjegyzését! Így azzal a hamis feltételezéssel nézzék a példát, minta nem zárná be, vagy ifstream helyett valami eszközt (pl. hálózat) használunk, amit külön be kell zárni. A lényeg a konstruktor/destruktor őrszem jellegű felhasználásában van. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 30 -
31.
Konstruktor/destruktor, mint őrszem/3 // Adapter, ami mindent tud, amit az ifstream + megszűnéskor bezár struct Inputstream : std::ifstream { Inputstream(const char *f) :std::ifstream(f) {} ~Inputstream() { close(); } atomatikusan }; bezár { // Kódrészlet Inputstream inp(f); // megnyitás ... // file feldolgozása } destruktor megszüntet (bezár) C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 31 -
32.
Mutatókonverzió újra • Származtatott osztály a kompatibilitás révén könnyen konvertálható alaposztályra. • Ez hívjuk "upcast"-nak. • Leggyakrabban pointereket konvertálunk (heterogén gyűjtemény). • Néha referenciát. (copy konstruktor hívása) • Többszörös öröklésnél a konverzió nagy figyelmet igényel. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 32 -
33.
Mut. konv. többszörös öröklésnél Alap1 * pB1 Alap2 * pB2 alap1 alap2 új rész MulDer md; memóriakép kompatibilis class Alap1 { ... }; class Alap2 { ... }; class MulDer: public Alap1, public Alap2 { ... }; MulDer md; Alap1 * pB1 = &md; Alap2 * pB2 = &md; // mutató értékmódosítás! C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 33 -
34.
Konv. többsz. öröklésnél szárm.-ra MulDer * pMd alap1 alap2 b2 új rész Alap2 b2; MulDer *pmd = (MulDer *) &b2; // mutató értékmódosítás! // nem létező adatmezőket és üzeneteket el lehet érni // explicit konverzió mindig veszélyes! C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 34 -
35.
Megbocsátható down cast • Néha előfordul, hogy tudjuk, hogy egy adott mutató egy származtatott osztályból származik, de valamiért alappá konvertáltuk (pl. heterogén kollekció) • Ekkor biztonságos a dynamic_cast használata, ami futási időben értékelődik ki. Ha MulDer* típusú, akkor OK, egyébként NULL MulDer md; Alap *p = &md; MulDer *pa = dynamic_cast<MulDer*>(p); C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 35 -
36.
cast átértékelése C: (típus) Explicit típuskonverziót végez. Használata körültekintést igényel. C programokban leginkább a malloc környékén fordul elő joggal. Eredménye nem lvalue. C++: Sokkal többször és sokkal több okból fordulhat elő joggal. A veszélyek csökkentésére több változata létezik: (dynamic_cast, static_cast, const_cast, reinterpret_cast, (tipus), tipus()) C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 36 -
37.
dynamic_cast szintaxis: dynamic_cast<T>(v) v – alaposztályra hivatkozik, vagy pointer T – származtatott osztályra hivatkozik, vagy void pointer struct A { virtual ~A(){} }; struct B :A {}; struct C :A {}; NULL C++ programozási nyelv polimorf A* apB = new B; A* apC = new C; B* bp1 = dynamic_cast<B*>(apB); B* bp2 = dynamic_cast<B*>(apC); © BME-IIT Sz.I. 2021.04.19. - 37 -
38.
static_cast szintaxis: static_cast<T>(v) v – alaposztályra hivatkozik, vagy pointer T – származtatott osztályra hivatkozik, vagy void pointer struct A { }; struct B :A {}; struct C :A {}; A* apB = new B; A* apC = new C; B* bp1 = static_cast<B*>(apB); B* bp2 = static_cast<B*>(apC); Fordítási időben történik, nincs futás idejű ellenőrzés! Jelzi, ami fordítási időben tilos. (pl. int* -> B*) C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 38 -
39.
const_cast szintaxis: const_cast<T>(v) Csak a const vagy a volatile minősítő eltávolítására vagy előírására használható. const int a = 10; const int* b = &a; int* c = const_cast<int*>(b); *c = 30; //nincs ford.hiba. !!Nem definit!! int a1 = 40; const int* b1 = &a1; int* c1 = const_cast<int*>(b1); *c1 = 50; // minden OK, mert a1 nem konst. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 39 -
40.
reinterpret_cast szintaxis: reinterpret_cast<T>(v) Ellenőrzés és változtatás nélkül átalakítja v-t a megadott típusúvá. Igen veszélyes, de legalább könnyen felismerhető a forrásban. A const ill. volatile minősítés nem módosítható vele. struct B {}; B* p = new B; long l = reinterpret_cast<long>(p); C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 40 -
41.
(típus), típus( ) A C stílusú cast mellett használható még az úgynevezett funkció stílusú cast is. Lehetőleg az úgynevezett nevesített (dynamic_cast, static_cast, const_cast, reinterpret_cast) változatokat kell használni. Használatukkal a konverziókból adódó problémák sokkal könnyebben felfedezhetők. int i, j; double d; d = (double)i/j; d = double(i)/j; // funkció stílusú cast https://git.ik.bme.hu/Prog2/eloadas_peldak/ea_09 C++ programozási nyelv © BME-IIT Sz.I. polimorf 2021.04.19. - 41 -
42.
Explicit konstruktor (ism.) • Az egyparaméterű konstruktorok egyben automatikus konverziót is jelentenek: pl: String a = "hello"; String a = String("hello"); • Ez kényelmes, de zavaró is lehet: – tfh: van String(int) – konstruktor, ami megadja a string hosszát, de nincs String(char) konstruktor; – ekkor: String b = 'x'; String b =String(int('x')); nem biztos, hogy kívánatos. • Az aut. konverzió az explicit kulcsszóval kapcsolható ki. (pl: explicit String(int i);) C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 42 -
43.
Explicit konstruktor példa: class Komplex { double re, im; public: Komplex(double re = 0, double im = 0) :re(re), im(im){ } }; void valamifv(class Komplex); int main() { Komplex k1 = 12; // OK. ... valamifv(12); //Lehet, hogy nem ezt akarta, esetleg // elfelejtette megvalósítani az int-es változatot. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 43 -
44.
Perzisztencia Obj Obj Attr. Attr. • A perzisztens objektumok állapota elmenthető és visszatölthető egy későbbi időben, esetleg másik gépen létrehozott objektumba. • A visszatöltött objektum "folytatja" a működést. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 44 -
45.
Perzisztencia/2 • Az objektum a saját állapotát képes kiírni egy adatfolyamba szerializáció • Az objektum a saját állapotát képes beolvasni egy adatfolyamból deszerializáció • Szempont lehet a hordozható külső formátum is. Ez különösen fontos elosztott rendszereknél. • A perzisztenciát gyakran többszörös örökléssel vagy interfésszel oldják meg. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 45 -
46.
Perzisztencia örökléssel I./1 class Serialize { int size; // kírandó adat mérete public: Serialize(int s) :size(s) {} // méret beállítása void write(ostream& os) const { os.write((char *)this, size); // kiírás } void read(istream& is) const { is.read((char *)this, size); // beolvasás } }; C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 46 -
47.
Perzisztencia örökléssel I./2 class Complex { Képes kiírni ill. visszatölteni double re, im; .... } class PComplex : public Serialize, public Complex { public: PComplex(double re, double im) : Complex(re, im), Serialize(sizeof(PComplex)) {} }; Származtatott mérete int main() { int main() { ifstream f1("f1.dat"); ofstream f1("f1.dat"); PComplex k1(1, 8); PComplex k1; k1.read(f1); k1.write(f1); return 0; return 0; } } C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 47 -
48.
A sorrendre érzékeny! class PComplex : public Serialize, public Complex { ... } Complex Serialize re im size write((char*)this,size) PComplex:: this Serialize:: this PComplex size re im Complex:: this re im size class PComplex : public Complex, public Serialize { ... } C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 48 -
49.
Problémák • Pointerek visszatöltésének nincs értelme. • Veszélyes a kód: A öröklés sorrendjére érzékeny. A Serialize osztálynak kell elsőnek szerepelni. • Mi van a virtuális függvények mutatóival? • Külső reprezentáció nem hordozható. • Ötletnek nem rossz, de a gyakorlatban használhatatlan! C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 49 -
50.
Van megoldás? • C++-ban nehéz automatizálni a szerializációt. Néhány könyvtár ad támogatást (pl. boost, MFC, s11n) • Fapados, de működő megoldás: – Magára az objektumra kell bízni az adatfolyammá történő alakítást ill. visszaalakítást. – Ehhez célszerű egy olyan absztrakt osztályból származtatni, ami megfelel az elvárt interfésznek („arcnak”). ld: Serializable • Kellő körültekintéssel el kell készíteni a megfelelő virtuális függvényeket. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 50 -
51.
Használható megoldás class Serializable { Absztrakt osztály a sorosításhoz public: virtual void write(ostream& os) const = 0; // kiíró virtual void read(istream& is) = 0; // beolvasó virtual ~Serializable() {} // ne legyen probléma az upcast }; class String { Szebb lenne protected nélkül. protected: Hogyan? Pl: getter/setter, vagy char *p; inserter/exractor (ld. laboron) int len; public: String(char *s = "" ) { len = strlen(s); p = new char[len+1]; strncpy(p, s, len+1); } ..... }; C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 51 -
52.
Használható megoldás /2 class PString : public Serializable, public String { Így két „arca” lesz public: void write(ostream& os) const; Világos, hogy ezek kellenek void read(istream& is); ... // Milyen tagfüggvény kell még, ha azt akarjuk, hogy // PString String helyett használható legyen ? }; • String összes létrehozási lehetősége • Kell másoló? Kell op=? Kell minden konstruktor – Van dinamikus adattagja a PStringnek? Nincs. Így nem kell. • Mi van, ha valamilyen String művelet eredményét PString-re kellene konvertálni? – Kell String PString konverzió Konstruktorok megoldják • Mi van, ha valamilyen PString művelet eredményét String-re kellene konvertálni? Kompatibilitás megoldja • Destruktor? Jó az örökölt C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 52 -
53.
Használható megoldás /3 class PString : public Serializable, public String { public: PString() : String() {}; PString(char ch) :String(ch) {} PString(const char *p) :String(p) {} PString(const String& s) :String(s) {} void write(ostream& os) const; void read(istream& is); }; // Már csak a write és a read megvalósítása hiányzik! C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 53 -
54.
write és read megvalósítása // Feltételezzük, hogy String::p és String::len protected tag, // de később belátjuk, hogy nem is kell! void PString::write(ostream& os) const { os << len << ','; // szeparátor os.write(p, len); // a szöveg kiírása } void PString::read(istream& is) { delete[] p; (is >> len).ignore(1); // len és szeparátor beolvasás p = new char [len+1]; // új terület kell is.read(p, len); // len db karaktert olvasunk p[len] = 0; // lezáró nulla } C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 54 -
55.
write és read megvalósítása /2 void PString::write(ostream& os) const { Protected nélkül. os << size() << ','; // méret + szeparátor os.write(c_str(), size());// a szöveg kiírása os << '\n'; // csak a könnyebb debug miatt } Miért read és nem >> ? void PString::read(istream& is) { size_t len; (is >> len).ignore(1);// len és szeparátor beolvasása char *p = new char [len+1]; // új terület is.read(p, len).ignore(1); // len db karaktert olvasunk + a \n p[len] = 0; // lezáró nulla *this = String(p); // kihasználjuk, hogy az op= működik delete [] p; // p már nem kell } https://git.ik.bme.hu/Prog2/eloadas_peldak/ea_09 C++ programozási nyelv © BME-IIT Sz.I. pkomplex (a pstring házi feladat) 2021.04.19. - 55 -
56.
Kérdések, megjegyzések • Fontos, hogy a read() pontos tükörképe legyen a write()-nak. • Fontos a megfelelő reprezentáció kiválasztása. • Kézenfekvő mindent szöveggé alakítani. Ekkor numerikus kiírások után kell szeparátor, amit a beolvasáskor el kell dobni. • Célszerű binárisan megnyitott adatfolyamot (stream) használni, így elkerülhetők a \n \r\n konverzióból adódó problémák a különböző rendszerek között. • Pointerek és referenciák kezelése külön figyelmet igényel. • Miért kell többszörös öröklés? – ha módosítható az osztály – megoldható többsz. nélkül – ha nincs kezünkben az osztály – csak ez a lehetőség C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 56 -
57.
Kivételes esetek kezelése (ism.) • Kinek kell jelezni? – felhasználó, másik programozó, másik program – saját magunknak • A kivételes eset kezelése gyakran nem annak keletkezési helyén történik. (Legtöbbször nem tudjuk, hogy mit kell tenni. Megállni, kiírni valami csúnyát, stb.) C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 57 -
58.
Kivétel kezelés (ism.) • C++ típus orientált kivételkezelést támogat, amivel a kivételes esetek kezelésének szinte minden formája megvalósítható. • A kivételkezeléshez tartozó tevékenységek: – figyelendő kódrészlet kijelölése (try) – kivétel továbbítása (throw) – esemény lekezelése (catch) C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 58 -
59.
Kivételkezelés = globális goto (ism) Fv. try Kivételes eset: throw A Felügyelt műveletek beleértve az innen hívott függvényeket is. catch Kivétel elkapása, típus alapján, és kezelése. Kivételes eset: throw B C: setjmp( ) longjmp( ) C++ programozási nyelv © BME-IIT Sz.I. További műv. A: A típusú érték B: B típusú érték 2021.04.19. - 59 -
60.
Kivételkezelés/2 (ism) try { ..... Kritikus művelet1 pl. egy függvény hívása, aminek a belsejében: if (hiba) throw kifejezés_tipus1; ..... Kritikus művelet2 pl. egy másik függvény hívása, aminek a belsejében: if (hiba) throw kifejezés_tipus2; } catch (típus1 param) { ..... Kivételkezelés1 } catch (típus2 param) { ..... Kivételkezelés2 } A hiba tetszőleges mélységben keletkezhet. Közvetlenül a try-catch blokkban a throw-nak nincs sok értelme, hiszen akkor már kezelni is tudnánk a hibát. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 60 -
61.
Kivételkezelés példa (ism) Hiba/kivétel észlelése Felügyelt szakasz. Ennek a működése során fordulhat elő a kivételes eset. C++ programozási nyelv © BME-IIT Sz.I. double osztas(int y) { if (y == 0) throw "Osztas nullaval"; return((5.0/y); } A kivételt azonosító érték eldobása. Típus azonosít (köt össze). int main() { try { cout << "5/2 =" << osztas(2) << endl; cout << "5/0 =" << osztas(0) << endl; } catch (const char *p) { cout << p << endl; Kivétel elkapása és } kezelése. } 2021.04.19. - 61 -
62.
Újdonságok a korábbiakhoz • A dobott kivétel alap ill. származtatott objektum is lehet. try { throw E(); } catch(H) { // mikor jut ide ? } 1. 2. 3. 4. H és E azonos típusú, H bázisosztálya E-nek, H és E mutató és teljesül rájuk 1. vagy 2., H és E referencia és teljesül rájuk 1. vagy 2. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 62 -
63.
Következtetések • Célszerű kivétel osztályokat alkalmazni, (pl. std::exceptions) amiből származtatással újabb kivételeket lehet létrehozni. • A dobás értékparamétert dob, ezért az elkapáskor számolni kell az alaposztályra történő konverzióval (adatvesztés). Célszerű pointert, vagy referenciát alkalmazni. Kell másoló konstruktor. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 63 -
64.
Továbbdobás try { throw E(); } catch(H) { if (le_tudjuk kezelni) { Az eredeti dobódik tovább, nem csak az elkapott .... változat. } else { throw; } Paraméter nélkül } C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 64 -
65.
Minden elkapása try { throw E(); } catch(...) { // szükséges feladatok throw; } Minden kivétel A kezelők sorrendje fontos! C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 65 -
66.
Rollback (stack unwinding) try { Minden a blokkban deklarált, "létező" A a; objektum destruktora meghívódik. B b; C *cp = new C; if (hiba) throw "Baj van"; delete cp; } catch(const char *p) { Hiba esetén C példánya nem // A létezik ? szabadul fel, de cp megszűnik. // B létezik ? // *cp által mutatott obj. létezik ? } C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 66 -
67.
Melyik obj. létezik ? • Csak az az objektum számít létezőnek, amelynek a konstruktora lefutott. • Ha a konstruktor nem fut le, akkor a rollback során a destruktor sem fog végrehajtódni. • Előző példában C konstruktora lefutott ugyan, de nem deklarációval hoztuk létre, hanem dinamikusan. https://git.ik.bme.hu/Prog2/eloadas_peldak/ea_9 C++ programozási nyelv © BME-IIT Sz.I. kodreszletek 2021.04.19. - 67 -
68.
Kivétel a konstruktorban • Lényegében a kivételkezelés az egyetlen mód arra, hogy a konstruktor hibát jelezzen. • Hiba esetén gondoskodni kell a megfelelő obj. állapot előállításáról. Inicializáló listán keletkező kivétel elfogása: struct A { B b; A() try :b() { // konstruktor programozott része } catch (...) { ... // kivételkezelés // ha eljut idáig itt továbbdob } }; C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 68 -
69.
Kivétel a destruktorban Destruktor hívás oka: 1. Normál meghívás 2. Kivételkezelés (rollback) miatti meghívás. Ekkor a kivétel nem léphet ki a destruktorból. Destruktorban keletkező kivétel elfogása: A::~A() try { // destruktor programozott törzse } catch (...) { ... // kivételkezelés // ha eljut idáig, továbbdob } C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 69 -
70.
Kivételek specifikálása • Függvény deklarálásakor/definíciójakor megadható, hogy milyen kivételeket generál az adott függvény. • Ha mást is generálna, akkor az automatikusan meghívja az unexpected() handlert. void f1() throw (E1, E2); // csak E1, E2 void f2() throw(); // semmi void f3(); // bármi void f3() noexcept(true) // semmi (C++11-től) C++17-től megszűnik a függvények din. kivétel specifikációja. Csak dob/nem dob. C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 70 -
71.
Kapcsolódó függvények • std::terminate_handler set_terminate(std::terminate_handler) throw(); – beállítja a terminate handlert • void terminate(); – meghívja a terminate handlert • bool uncaught_exception() throw(); – kivételkezelés folyamatban van, még nem talált rá a megfelelő handlerre (nem kapták el). Destruktorban lenne szerepe, de … C++11-től a szignatúra változik C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 71 -
72.
Összefoglalás • Többszörös öröklés: Ha egy osztálynak több „arcot” kell mutatnia. pl. – UI és modell kapcsolata – perzisztencia • Sorosítás fontos eszköz a kommunikációban. C++-ban nehéz automatizálni, de egyedi megoldásokra van működő recept. • Explicit cast veszélyes. Könnyebb felismerhetőséghez több eszközünk van (pl. dynamic_cast, const_cast, ...) • Kivételes esetek kezelését átértékeltük az öröklés ismeretében C++ programozási nyelv © BME-IIT Sz.I. 2021.04.19. - 72 -