Útmutató a 6. laborhoz

A laborgyakorlat a tartalmazott objektumok és ősök létrehozásával, inicializálásával, valamint megszüntetésével kapcsolatos mechanizmusok és automatizmusok bemutatását, jobb megértését célozza. Sok apró feladatot kell megoldania, melyek közül felkészültsége alapján ki is hagyhat néhányat. Önellenőrzés céljából csak a 3. feladatcsoport megoldásaként elkészülő bicikli.h fájlt kell feltöltenie a Jporta rendszerbe.

Válaszoljon az alábbi kérdésekre, majd ellenőrizze válaszát. Ha nem sikerült mindenre hibátlanul válaszolni, akkor feltétlenül oldja meg az első két feladatcsoport minden feladatát!

Ellenőrző kérdések:

  • Tételezze fel, hogy B osztály egyik adattagja A típusú, ami szintén osztály!
    1. B implicit deklarált másoló konstruktora meghívja-e A másoló konstruktorát?
    2. B implicit deklarált másoló konstruktora nem felel meg a feladat elvárásainak, ezért Ön készíti el B másoló konstruktorát, de az inicializáló listát üresen hagyja. Meghívódik-e A valamelyik konstruktora. Ha igen, melyik?
  • Tételezze fel, hogy BB osztály ősosztálya AA osztály!
    1. BB osztályból dinamikus adatterületen létrehozott példány címét az AA *ap pointer tárolja.
      A delete ap; utasítás hatására mikor fut le ~BB()?
    2. Amennyiben BB-nek csak implicit deklarált konstruktora van. AA-nak melyik konstruktora hívódik meg a
      new BB; utasítás hatására?

Laborfeladatok megoldásokkal

Az objektumosztályok kapcsolatát osztálydiagramok segítségével írjuk le, amit legtöbbször UML (Unified Modelling Language) ábrák segítségével adunk meg. Az UML szintaktikájával, és lehetőségeivel a következő félévben fognak részletesen megismerkedni. Ebben a félévben csak néhány egyszerű jelölést használunk, mint pl. a láthatóság és default értékek jelölése. Az UML ábrákon a tagfüggvények és adattagok láthatóságát a nevük előtt álló +, -, vagy # jellel jelöljük. A - jel a privát, + jel a publikus, a # pedig a védett (protected) láthatóságot jelenti. A default értékek és argumentumok a C++ szintaxisához nagyon hasonlóan jelennek meg.

1. feladatcsoport. (~35')

Egy egykerekű (Monocikli) egyszerűsített modelljét építjük fel, melyben a monocikli csak egyetlen komponensből áll: egy kerékből. A komponens relációnak (kompozíció) megfelelően elkészítettük a modell osztálydiagramját:

Feladatok:

    A feladat egyszerűsége miatt nem szükséges osztályonként külön fájlokat létrehoznia. A deklarációt és az implementációt (külön *.h és *.cpp állományok) sem kell szeparálnia, mindent írhat egyetlen fájlba (pl. monocikli.cpp)! A tagfüggvények mindegyike legyen inline megvalósítású, így kevesebbet kell gépelnie.
  1. Valósítsa meg a Kerek és Monocikli osztályokat C++ nyelven! Az egyszerűség kedvért az osztályoknak az alapértelmezett tagfüggvényeken kívül ne legyen más metódusa!
  2. Ezután egészítse ki az osztályokat az alábbiaknak megfelelő tagfüggvényekkel, melyek egyszerű üzeneteket írnak a standard kimenetre. Minden kiírást egy tabulátorjellel (\t) kezdjen!
    • A Kerek osztály konstruktora írja ki, hogy: \tKerek ctor\n!
    • A Monocikli osztály konstruktora írja ki, hogy: \tMonocikli ctor\n!
    • A Kerek osztály destruktora írja ki, hogy: \tKerek dtor\n!
    • A Monocikli osztály destruktora írja ki, hogy: \tMonocikli dtor\n!

  3. Próbálja ki az alábbi főprogrammal a létrehozott osztályokat:
    #include <iostream>
    /// Utasítást kiíró és végrehajtó makró
    #define _(x)  std::cout << #x << std::endl; x
    
    int main() {
    _(    Monocikli m1_obj;          )
    _(    Monocikli m2_obj = m1_obj; )
    _(    return 0;                  )
    }

    Ha jól csinált mindent, akkor az alábbiakat kell látnia:

    Monocikli m1_obj;
        Kerek ctor
        Monocikli ctor
    Monocikli m2_obj = m1_obj;
    return 0;
        Monocikli dtor
        Kerek dtor
        Monocikli dtor
        Kerek dtor
    
    Ha nem ezt kapta, próbálja lépésenként végrehajtani a programot! Ne lépjen addig a következő feladatra, amíg nem érti mi miért történt! Kérjen segítséget a laborvetetőtől, ha elakadt!

    A programot elemezve látható, hogy az _( makró kiírja magát az utasítást és egy soremelést. Ezt követően az Ön kiírásai jelennek meg a tabulator miatt kicsit beljebb. Így jól felismerhető, hogy melyik utasításból milyen output keletkezett.

  4. Bővítse a Kerek osztályt egy publikus kiir() tagfüggvénnyel, ami kiírja a kerék átmérőjét:
        void kiir()         { std::cout << "atmero=" << atmero << std::endl; }
    

    A Monocikli osztályt is bővítse egy kiír tagfüggvénnyel, ami meghívja az általa tartalmazott kerék objektum kiíró tagfüggvényét:

        void kiir()         { std::cout << "\tk."; k.kiir(); }
    

    A main függvényt pedig alakítsa át az alábbiaknak megfelelően:

    int main() {
    _(    Monocikli m1_obj;          )
    _(    m1_obj.kiir();             )
    _(    Monocikli m2_obj = m1_obj; )
    _(    m2_obj.kiir();             )
    _(    return 0;                  )
    }

    Majd futtassa a programot!

  5. Milyen értéket kapott atmero-re? Ezt várta? Egyezik a két érték? Ha jól oldotta meg a feladatokat, akkor az alábbi kiíráshoz hasonlót kell látnia:
    Monocikli m1_obj;
        Kerek ctor
        Moncikli ctor
    m1_obj.kiir();
        k.atmero=123456789
    Monocikli m2_obj = m1_obj;
    m2_obj.kiir();
        k.atmero=123456789
    return 0;
        Monocikli dtor
        Kerek dtor
        Monocikli dtor
        Kerek dtor
    

  6. Készítsen a Kerek osztályhoz másoló konstruktort, ami csupán kiírja a szabványos kimenetre, hogy: \tKerek copy\n!

    Futtassa ismét a programot! Vesse össze a kiírásokat az előző futás eredményével! Mit tapasztal? Lemásolódott az atmero adattag?
  7. Módosítsa a Kerek osztály másoló konstruktorát, hogy az adattag is másolódjon le (inicializáló lista)! Majd futtassa ismét a programot!

  8. Azt kell látnia, hogy most a Kerek osztály másoló konstruktora nem hívódott meg automatikusan. Milyen tagfüggvény hívódik meg helyette? Miért?
  9. Készítsen Monocikli osztályhoz is másoló konstruktort, ami csupán kiírja a szabványos kimenetre, hogy: \tMonocikli copy\n! Futtassa ismét a programot! Vesse össze a kiírásokat az előző futás eredményével! Mit tapasztal?

  10. Oldja meg, hogy akkor is hívódjon a Kerek osztály másoló konstruktora, amikor a Monocikli osztálynak is van explicit módon megadott másoló konstruktora (inicializáló lista)! Ha jól oldotta meg a feladatot, akkor a két objektum (m1, m2) adattagja azonos értéket (memóriaszemetet) tartalmaz.
  11. A kódot kiegészítve meg tudjuk hívni a tartalmazott objektum másolóját:

  12. A Kerek osztály konstruktora vegyen át egy int paramétert, amivel inicializálja az atmero adattagot! A paraméternek ne legyen default értéke! Így a Kerek osztálynak most nincs paraméter nélkül hívható (default) konstruktora. Fordítsa le a programot! Mit tapasztal? Lefordul?
  13. A Monocikli osztály paraméter nélkül hívható konstruktorának inicializáló listáján inicializálja a k adattagot 16-tal! Így már lefordul a kód, pedig most sincs paraméter nélkül hívható konstruktora a Kerek osztálynak.
  14. Törölje a Monocikli osztály destruktorát!, és futtassa ismét a programot! Vegye észre, hogy a Kerek osztály destruktora akkor is meghívódik, ha van a Monocikli osztálynak explicit módon megadott destruktora!

2. feladatcsoport. (~35')

Járművek modellezéséhez készítünk egyszerű objektummodellt, melyben közös attribútumnak a jármű maximális sebességét választottuk. Modellünkkel kezdetben csak kutyákkal húzott szánkót (Szan) modellezünk. Az alábbi ábra ezen egyszerű modell osztálydiagramját szemlélteti.

Feladatok:

    A feladat egyszerűsége miatt itt sem szükséges osztályonként külön fájlokat létrehoznia. A deklarációt és az implementációt (külön *.h és *.cpp állományok) sem kell szeparálnia, mindent írhat egyetlen fájlba (pl. monocikli.cpp)! A tagfüggvények mindegyike legyen inline megvalósítású, így kevesebbet kell gépelnie
  1. Valósítsa meg a Jarmu és a Szan osztályt C++ nyelven úgy, hogy az adattagokat a konstruktorok a paramétereknek megfelelően inicializálják. (Vigyázzon, mindkét adattag privát!)
  2. Ezután egészítse ki az osztályokat az alábbiaknak megfelelően! Minden kiírást egy tabulátorjellel (\t) kezdjen!
    • A Jarmu osztály konstruktora írja ki, hogy: \tJarmu ctor vMax=, majd ezt követően a vMax tagváltozó értékét, majd egy soremelést!
    • A Szan osztály konstruktora írja ki, hogy: \tSzan ctor kutyakSzama=, majd ezt követően a kutyakSzama tagváltozó értékét, majd egy soremelést!

  3. Próbálja ki az alábbi főprogrammal a létrehozott osztályokat:
    /// Utasítás kiíró és végrehajtó makró
    #define _(x)  std::cout << #x << std::endl; x
    
    int main() {
    _(    Szan sz0_obj;             )
    _(    Szan sz1_obj(1.1, 1);     )
    _(    return 0;                 )
    }
    

    Ha mindent jól valósított meg, akkor a standard outputon az alábbi sorok jelennek meg:

    Szan sz0_obj;
            Jarmu ctor vMax=0
            Szan ctor kutyakSzama=0
    Szan sz1_obj(1.1, 1);
            jarmu ctor vMax=1.1
            Szan ctor kuytakSzama=1
    return 0;
    

    Vegye észre, hogy az alaposztály (Jarmu) adattagját (vMax) a főprogram egyfajta azonosítónak használja, azaz mindig olyan értéket kap, melynek egészrésze előfordul az objektum nevében! A kiírások elemzésénél ez később segítség lesz.

  4. Bővítse az osztályokat destruktorral, melyek "\tJarmu dtor" ill. "\Szan dtor" szöveg után a konstruktorhoz hasonlóan írják ki a megfelelő adattagot is!
  5. Ha mindent jól valósított meg, akkor standard outputon a következő 11 sor jelenik meg:

    Szan sz0_obj;
            Jarmu ctor vMax=0
            Szan ctor kutyakSzama=0
    Szan sz1_obj(1.1, 1);
            jarmu ctor vMax=1.1
            Szan ctor kutyakSZama=1
    return 0;
            Szan dtor kutyakSZama=1
            Jarmu dtor vMAx=1.1
            Szan dtor kutyakSZama=0
            Jarmu dtor vMAx=0
    

    Figyelje meg a konstruktorok/destruktorok hívási sorrendjét, és azt, hogy az alaposztály destruktorát nem kellett (nem is szabad) explicit meghívni Szan osztály destruktorából!

  6. Módosítsa main függvényt úgy, hogy hívódjon meg Szan osztály másoló konstruktora is!
    int main() {
    _(    Szan sz0_obj;             )
    _(    Szan sz1_obj(1.1, 1);     )
    _(    Szan sz1m_obj = sz1_obj;  )
    _(    return 0;                 )
    }
  7. Futtassa programot, majd bővítse a Jarmu osztályt másoló konstruktorral, ami kiírja, hogy \tJarmu copy, majd a szöveg után a konstruktorhoz hasonlóan írja ki a megfelelő adattagot is! Ügyeljen arra, hogy az adattag másolása is megtörténjen!
  8. Futtassa programot, majd bővítse a Szan osztályt is másoló konstruktorral, ami kiírja, hogy "\tSzan copy\n", majd a szöveg után a konstruktorhoz hasonlóan írja ki a megfelelő adattagot is! Ügyeljen arra, hogy az adattag és alaposztály másolása is megtörténjen!
  9. A destruktoroknál kiíródó adatok alapján ellenőrizze, hogy tényleg megtörtént-e a másolás! (2 db olyan objektumnak kell megszűnnie, melynek az adattagja nem nulla.) Ha mindent jól valósított meg, akkor standard outputon a következő kimenet jelenik meg:
    Szan sz0_obj;
            Jarmu ctor vMax=0
            Szan ctor kutyakSzama=0
    Szan sz1_obj(1.1, 1);
            Jarmu ctor vMax=1.1
            Szan ctor kutyakSzama=1
    Szan sz1m_obj = sz1_obj;
            Jarmu copy vMax=1.1
            Szan copy kutyakSzama=1
    return 0;
            Szan dtor kutyakSZama=1
            Jarmu dtor vMAx=1.1
            Szan dtor kutyakSZama=1
            Jarmu dtor vMAx=1.1
            Szan dtor kutyakSZama=0
            Jarmu dtor vMAx=0
    

    Vegye észre, hogy az elkészített másoló konstruktor default konstruktort fog hívni, ha nem hívjuk meg az alap, vagy a beágyazott objektum másolóját explicit módon!

  10. Módosítsa a főprogramot az alábbiak szerint, majd futtassa a kódot!
    int main() {
    #if 1
    _(    Szan sz1_obj(1.1, 1);         )
    #else
    _(    Jarmu *jp = new Szan(2.2, 2); )
    _(    delete jp;                    )
    #endif
    _(    return 0;                     )
    }
    
  11. Ellenőrizze, hogy minden objektum megszűnt-e, azaz minden objektumnak lefutott-e a destruktora!
  12. Most írja át a feltételes fordítás feltételét, hogy az objektum a dinamikus memóriaterületen jöjjön létre, majd futtassa a kódot!
  13. Minden objektum megszűnt? Mit kell tennie, hogy minden rendben megszűnjön? (Ötlet: delete jp; utasítás az alaposztály pointerén keresztül töröl! Most kellene a MANÓ!)

    Ha mindent jól valósított meg, akkor standard outputon a következő 7 sor jelenik meg:
    Jarmu *jp = new Szan(2.2, 2);
            Jarmu ctor vMAx=2.2
            Szan ctor kutyakSzama=2
    delete jp;
            Szan dtor kutyakSzam=2
            Jarmu dtor vMAx=2.2
    return 0;
    

3. Feladatcsoport (~10')

Egészítse ki a 2. feladatcsoport modelljét kerékpár (Bicikli) osztállyal. Használja fel az 1. feladatcsoport Kerek osztályát, amit úgy alakítson át, hogy az is alaposztállyá válhasson! Segítségül megadjuk a modell osztálydiagramját, melyben feltételezzük, hogy az első és hátsó kerék átmérője azonos. Az alapértelmezett tagfüggvényeket (paraméter nélküli konstruktor, copy konstruktor, destruktor, ...) általában nem szokták jelölni az osztálydiagramon, ezért mi sem jelöltük, azonban ügyeljen a megvalósításra.

Feladatok:

  1. Töltse le az előkészített projektet (https://git.ik.bme.hu/Prog2/labor_peldak/lab_06 -> bicikli) a GIT tárolóból!
  2. A bicikli.h fájlban valósítson meg minden szükséges osztály! Az osztályok tagfüggvényeit inline valósítsa meg!
  3. Minden osztály konstruktora, másoló konstruktora és destruktora a fentieknek megfelelő formátumban és tartalommal írjon a standard kimenetre. (pl. "Bicikli ctor atmero=16", vagy "Szan ctor kutyakSzama=0")
  4. Futtassa a bicikli_teszt0.cpp tesztprogramot! Ha mindent jól valósított meg, akkor standard outputon a következő kimenet jelenik meg:
    	Jarmu ctor vMax=1.1
    	Szan ctor kutyakSzama=7
    	Jarmu copy vMax=1.1
    	Szan copy kutyakSzama=7
    	Jarmu ctor vMax=10
    	Kerek ctor
    	Kerek ctor
    	Bicikli ctor atmero=16
    	Jarmu copy vMax=10
    	Kerek copy
    	Kerek copy
    	Bicikli copy atmero=16
    	Kerek ctor
    	MyKerek ctor atmero=-1
    	MyKerek dtor atmero=-1
    	Kerek dtor
    	Jarmu ctor vMax=0
    	Kerek ctor
    	Kerek ctor
    	Bicikli ctor atmero=0
    	Bicikli dtor atmero=0
    	Kerek dtor
    	Kerek dtor
    	Jarmu dtor vMax=0
    	Bicikli dtor atmero=16
    	Kerek dtor
    	Kerek dtor
    	Jarmu dtor vMax=10
    	Bicikli dtor atmero=16
    	Kerek dtor
    	Kerek dtor
    	Jarmu dtor vMax=10
    	Szan dtor kutyakSzama=7
    	Jarmu dtor vMax=1.1
    	Szan dtor kutyakSzama=7
    	Jarmu dtor vMax=1.1
    
  5. Ha elégedett az eredménnyel, akkor töltse fel az elkészített bicikli.h fájlt a Jporta rendszerbe (6. labor önellenőrző feladat), hogy megkapja a labor elvégzéséért járó pontot!
    A Jpota tesztprogramja a standard outputra történő kiírásokat egy stringstream objektumba irányítja, és úgy elemzi. Ha szorgalmi jelleggel érdeklik a részletek, a feladat feltöltésekor lekérdezheti magát a tesztpogramot is. A tesztprogram frodításához C++11-es beállítás szükséges.

Jó munkát!

Utolsó frissítés: 2021-03-15 22.06