Heterogén kollekció

Dobra Gábor · 2019.02.27.

A mai óra célja az öröklés ismeretének elmélyítése, és a heterogén kollekció megismerése.

Felkészülés a gyakorlatra

1. Kompatibilitás fogalma

A gyakorlatvezető segítségével ismételjétek át, hogy mit jelent a kompatibilitás! Az ősosztály-leszármazott viszonyban hogyan jelenik meg?

(A válasz a jegyzet 7. fejezetében megtalálható.)

2. Heterogén kollekció fogalma

A gyakorlatvezető segítségével járjátok körül a heterogén kollekció jelentését! Miért mindig pointert tárolunk heterogén kollekcióban?

Megoldás
  • Érték szerint nem tárolhatjuk őket csak simán, mert fordítási időben ki kellene derülnie a méretüknek. Különben is, az egyes leszármazottak eltérő méretűek lehetnek, egy sima tömb ezért biztos nem lesz jó.
  • Ha az ősosztály ráadásul absztrakt, akkor abból egy példány sem lehet, tömb meg pláne nem. És szinte mindig absztakt, különben valószínűleg tervezési hiba van.
  • Referenciákat nem tehetünk tömbbe.
  • Marad tehát a pointer. Általános ökölszabály, hogy a heterogén kollekció mindig (valamilyen) pointer(eke)t tárol. Később látni fogjuk, hogy ezek jellemzően okos pointerek lesznek.
  • A pointer azt is jelenti, hogy dinamikusan foglalt objektumokra mutató pointereket tárolunk, tehát nekünk kell foglalkozni a memóriakezeléssel. (Egészen addig, amíg nem okos pointereket használunk.)
  • A pointereket tartalmazó tároló lehet egy sima tömb is, de általában valamilyen STL tárolót használunk. Sőt, olykor előfordul, hogy mindig 1 db elemet tárolunk a "kollekcióban".
heterogen

3. Virtuális destruktor

Mikor van szükség virtuális destruktorra? Heterogén kollekcióban tárolt objektumoknál miért muszáj virtuálissá tennünk a destruktort?

Megoldás

Dinamikus memóriakezelés kell a heterogén kollekció miatt. A felszabadításnál (delete) viszont az egyes leszármazottakra vonatkozó destruktoroknak kell lefutnia, de nekünk csak ősosztályra mutató pointerünk van. Tehát a destruktort virtuálissá kell tennünk, az pont ezt csinálja. Ökölszabály, hogy ha van legalább egy virtuális függvényünk, akkor az ősosztály destruktorát virtuálissá kell tennünk. Hiszen ha van virtuális függvény, akkor direkt polimorf felhasználásra készült az ősosztály, tehát valaki valamikor heterogén kollekcióba akarja majd tenni az osztályunkat. Különben is, öröklést is csak akkor kell használnunk, ha heterogén kollekciót akarunk csinálni, minden másra más eszközök (pl. template) is elegendőek.

4. Pacman: kiindulás

Írjunk Pacman játékot! Legyen paraméterezhető a szörnyek stratégiája! Az eredeti játékban is a négy szörny mind különböző stratégiával ment, ehhez alkalmas viselkedésre készítsük fel a játékot.

pacman

Töltsd le a kiindulási alapot! Nézzük meg az elemeit, ezeknek ismerősnek kell lennie az előző két gyakorlat anyagából: Matrix és tic-tac-toe.

pacman.cpp letöltése

  • enum Irany: a mozgás irányát (melyik szomszédos cellába akar menni) jelképezi. Egy helyben is maradhatnak, az a SEMMI. Az értékek egészosztásos trükk miatt azok, amik: így könnyű irányból elmozdulásvektort (x, y) számolni. Van irany_random is.
  • struct Pont: ismerős a tic-tac-toe-ból. Az elmozdulást az Irany-t átvevő konstruktora számítja.
  • class Palya: ez egy átalakított Matrix. Az erőforráskezelést rábíztuk egy létező dinamikus tömb-implementációra, az std::vector-ra. A furcsa (függvényhívás operátoros) indexelés helyett lett rendes is, ami Pont paramétert vár. Van konstruktora, ami sztringtömböt tud átvenni, a méretet template trükkel találja ki. Vannak tic-tac-toe-ból ismerős valid és szabad tagfüggvényei, amiknek már az előző gyakorlaton tagfüggvénynek kellett volna lenniük. A kovetkezo megmondja, hogy azon a pályán melyik ponttól milyen irányban melyik pont van. A figurák körbe-körbe mászkálása miatt kell. Van kiíró operátor is hozzá.

5. Pacman: osztályhierarchia

Milyen osztályokra van még szükség? Közöttük milyen hierarchiát érdemes felállítani?

Megoldás

Van négyféle szörny, amelyeknek a viselkedése különbözik: Blinky, Pinky, Inky, Clyde. A szörnyek viselkedésében gyárilag az a közös, hogy

  • Alakjuk egyforma, csak a színük más. Tehát ugyanúgy kell őket kirajzolni, csak más adatokkal. Legyen tehát egy eltárolt karakter, amit a konstruktorában lehet megadni, és a figura mindig azt adja vissza. Vegyük észre, hogy adattag == adat, virtuális függvény == viselkedés. Itt a kettő közötti transzformáció történt.
  • A pacmannel mindegyik ugyanúgy ütközik. Ez terjedelemben macerás lenne, így most nem modellezzük.
  • Mindegyiknek van állapota, ami megváltozik a Power Pellet hatására, vagy ha megeszik őket. Ezeket sem modellezzük most.
  • A pacman és a szörnyek viselkedésében biztos van közös: mindkettő tud mozogni, és mindkettő kirajzolható.
  • Adattagok: hol áll, és merre néz, legyenek protectedek. Ezeket be is kell állítani, legyen rá konstruktor. Alapból minden bábu sehova néz.
  • Hogy ne lehessen csalni, a pályára nem ők írják be magukat, hanem a játék osztály. Tehát a lep legyen const, és adja vissza a lépési szándékot (Irany).
  • Mivel itt heterogén kollekció lesz, ezért kell virtuális destruktor. Igazából már egy virtuális függvény megléte miatt is kell.
  • A Babu és a Szorny biztosan absztrakt, hiszen nem adtuk meg, hogyan mozognak, de valahogy kell nekik.

Egy játék pedig egy pályából, egy játékosból és négy szörnyből áll. Az osztálydiagramon csak az elkészítendő osztályok szerepelnek, metódusok és adattagok nélkül.

class

6. Pacman: szörnyek

Írjuk meg a Babu és Szorny osztályokat, és egy konkrét szörny osztályt: Clyde-ot, aki random mozog a pályán! Ha helytelent lépne, a Jatek felelőssége, hogy ne léptesse.

7. Pacman: játékos

Írjuk meg a játékost is, aminek a figurája tényleg az iránytól függ. Karakteres megjelenítésnél limitáltak a lehetőségeink: <^>v. A lépéshez előbb kiírjuk a felhasználónak a táblát, majd bekérünk egy irányt (j, b, f, l).

8. Pacman: Jatek osztály

Írjunk osztályt, ami egy játék minden elemét tárolja:

  • pálya
  • pacman
  • 4 db szörny
Megoldás

Ez tárol egy üres kezdőpályát, és egy játékost, amiknek az inicializálását konstruktorból kell megadnunk. Plusz tárol max. 4 szörnyet, amit úgy a legegyszerűbb, ha van egy 4-elemű pointertömb és a darabszám, így menet közben lehet hozzáadni szörnyeket (max. 4-et). Ezzel elvileg az is kezelhető, hogy meghalnak a szörnyek.

A hozzáadás dinamikusan foglalt szörnyeket vár, tehát a destruktorban fel kell szabadítani őket. Na de a hármas szabály miatt akkor kell copy ctor és operator= is, de azt nem tudjuk megírni, mert nem tudjuk őket típushelyesen másolni. Ne is írjuk meg a másolást, inkább tiltsuk le (tegyük private-ba, vagy =delete).

9. Pacman: lépés és bábuk mozgása

Megoldás

Fejezzük be a demót: lépjenek a szörnyek és a pacman! A Jatek vezérelje és ellenőrizze a lépéseket!

A lépés úgy történik, hogy

  • rárajzoljuk a bábukat a pályára (ha még nem lennének ott)
  • sorban léptetjük a bábukat

Egy bábu léptetése pedig úgy néz ki, hogy

  • megkérdezzük a bábut, merre akar lépni
  • megnézzük a pályát, hogy szabad-e oda lépni
  • ha igen, akkor a pályáról letöröljük a bábut, átrajzoljuk az új helyére, és megmondjuk neki, hogy hova lépett és merre nézzen.

Az utóbbi kettő miatt bele kell írni egy-egy gettert és settert a Babu osztályba, és akkor a protected adattagnak nincs értelme, lehet private. Általánosságban véve, a protected adattag egy code smell.

pacman_minta.cpp letöltése