Programozás alapjai II. (5. ea) C++

Objektummodell, öröklés, virtuális tagfüggvény

Szeberényi Imre, Somogyi Péter BME IIT szebi@iit.bme.hu

OO modellezés fogalmai újból

Osztály és példány jelölése

Osztály és típus

Modellezés objektumokkal

Modellezés eszközei, módszertana

http://www.tankonyvtar.hu/hu/tartalom/tkt/objektum-orientalt/ch03.html

Objektummodell

Példák a kapcsolatok leírására

Egy – több kapcsolat

Kapcsolatok attribútumai

Komponens reláció

Komponens vs. aggregáció

Öröklés

Feladat

Örökléssel

Öröklés másként jelölve

Ember alaposztály

class Ember {
  String nev;
  Date szulDatum;

public:
  Ember();
  void setDate(Date d);
  void setName(char *n);
  const char *getName() const;
  //   ...
};

Származtatott osztályok

class Diak : public Ember {
  //           ^Alaposztályból minden
  //            látszik, ami publikus
  double atlag;
  // ^ +új attribútum
public:
  Diak();
  void setAv(double a);
  // ^ +új tagfüggvény
};
class Tanar : public Ember {
  double fizetes;
public:
  Tanar();  // ...
};

Öröklés előnyei

Öröklés fajtái

Analitikus és korlátozó öröklés

Kompatibilitás és öröklés

Alakzatok pl. rajztáblán

Van közös attribútum?

Geometria alakzatok C++-ban

Alakzat alaposztály

class Alakzat {
protected:
  // ^ Védelem enyhítése a leszármazottak felé
  int x, y;
  int szin;

public:
  Alakzat(int x0, int y0, int sz) : x(x0), y(y0), szin(sz) {}
  // mozgat(), érezzük, hogy itt a helye, de nem
  // tudjuk hogyan kell megvalósítani a rajzolás részét.
  // Ezért oda tesszük, ahol már ismert a rajzolás menete.
};

Szakasz osztály

class Szakasz : public Alakzat {
  //             ^ Alaposztályból minden látszik ami publikus
  int xv, yv;

public:
  Szakasz(int x1, int y1, int x2, int y2, int sz)
      : Alakzat(x1, y1, sz), xv(x2), yv(y2) {}
  void rajzol();
  void mozgat(int dx, int dy);
  // ^ Itt már tudjuk, hogyan kell rajzolni.
};

Szakasz tagfüggvényei

void Szakasz::rajzol() {
  // ... szakaszt rajzol
}

void Szakasz::mozgat(int dx, int dy) {
  int sz = szin;  // tényleges rajzolási szín elmentése
  szin = BACKGND; // rajzolási szín legyen a háttér színe
  rajzol();       // A vonal letörlése az eredeti helyről
  x += dx;
  y += dy;   // mozgatás: a pozíció változik
  szin = sz; // rajzolási szín a tényleges szín
  rajzol();  // A vonal felrajzolása az új pozícióra
}

Téglalap osztály

class Teglalap : public Alakzat {
  int xc, yc;

public:
  Teglalap(int x1, int y1, int x2, int y2, int sz)
      : Alakzat(x1, y1, sz), xc(x2), yc(y2) {}
  void rajzol();
  void mozgat(int dx, int dy);
  // ^ Ugyanaz, mint a szakasznál, csak a hívott rajzol() más
};

mozgat() helye

Alakzat osztály virtuális függvénnyel

class Alakzat {
protected:
  int x, y;
  int szin;

public:
  Alakzat(int x0, int y0, int sz) : x(x0), y(y0), szin(sz) {}
  virtual void rajzol() {}
  // ^ Az öröklés során újabb jelentést kaphat, ami az
  // alaposztályból is elérhető, így a mozgat()-ból is.
  void mozgat(int dx, int dy);
  // ^ Most már ide tehetjük, mert a rajzol() is itt van.
};

Alakzat mozgat() tagfüggvénye

void Alakzat::mozgat(int dx, int dy) {
  int sz = szin;  // tényleges rajzolási szín elmentése
  szin = BACKGRD; // rajzolási szín legyen a háttér színe
  rajzol();       // A vonal letörlés az eredeti helyről
  x += dx;
  y += dy;   // mozgatás: a pozíció változik
  szin = sz; // rajzolási szín a tényleges szín
  rajzol();  // A vonal felrajzolása az új pozícióra
}

Szakasz osztály újra

class Szakasz : public Alakzat {
  int xv, yv;

public:
  Szakasz(int x1, int y1, int x2, int y2, int sz)
      : Alakzat(x1, y1, sz), xv(x2), yv(y2) {}
  void rajzol(); // átdefiniáljuk a virt. fv-t.
  //  void mozgat(int dx, int dy);
};
void Szakasz::rajzol() {
  // ... szakaszt rajzol.
  // Az alaposztályból hívva is ez hívódik
}

Téglalap osztály újra

class Teglalap : public Alakzat {
  int xc, yc;

public:
  Teglalap(int x1, int y1, int x2, int y2, int sz)
      : Alakzat(x1, y1, sz), xc(x2), yc(y2) {}
  void rajzol(); // átdefiniáljuk a virt. fv-t.
  //  void mozgat(int dx, int dy);
};
void Teglalap::rajzol() {
  // ... téglalapot rajzol.
  // Az alaposztályból hívva is ez hívódik
}

Mintaprogram

int main() {
  Teglalap tegla(1, 10, 2, 40, RED); // téglalap
  Szakasz szak(3, 6, 80, 40, BLUE);  // szakasz
  Alakzat alak(3, 4, GREEN);         // ???
  alak.mozgat(3, 4);                 // 2 db rajzol() hívás
  szak.rajzol();                     // 1 db rajzol()
  szak.mozgat(10, 10);               // 2 db rajzol() hívás
  Alakzat *ap[10];
  ap[0] = &szak;  // nem kell típuskonverzió
  ap[1] = &tegla; // kompatibilis
  for (int i = 0; i < 2; i++) {
    ap[i]->rajzol();
  }
}

Mikor melyik rajzol()?

  Virtuális Nem virtuális
  Alakzat::rajzol() Alakzat::rajzol()
alak.mozgat() Alakzat::rajzol() Alakzat::rajzol()
szak.rajzol() Szakasz::rajzol() Szakasz:rajzol()
szak.mozgat Szakasz::rajzol() Alakzat::rajzol()
sp[0]->rajzol() Szakasz-ra mutat Szakasz::rajzol() Alakzat::rajzol()
sp[1]->rajzol() Teglalap-ra mutat Teglalap::rajzol() Alakzat::rajzol()

Alakzat önállóan?

Alakzat alak(3, 4, GREEN); // ???
alak.mozgat(3, 4);         // Mit rajzol ??

Absztrakt alaposztályok

class Alakzat {
protected:
  int x, y, szin;
  
public:
  Alakzat(int x0, int y0, int sz);
  void mozgat(int dx, int dy);
  virtual void rajzol() = 0; // tisztán virtuális
  // ^ Nem példányosítható
  virtual ~Alakzat(){};      // Ez meg mi ?
};

Virtuális függvények szerepe

Virtuális destruktor hiánya

struct A {
  ~A(){};
};
class B : public A {
  char *p;
public:
  B() { p = new char[10]; }
  ~B() { delete[] p; }
};
int main() {
  A *pa = new B;
  delete pa;
} // ~B() nem hívódik meg, memleak p-nél
// Direct leak of 10 byte(s) in 1 object(s) allocated from:
// #0 0x4f5512 in operator new[](unsigned long)
// #1 0x4f8300 in B::B()

Virtuális destruktor szerepe

struct A {
  virtual ~A(){};
  // ^ Virtuális destruktor
};
class B : public A {
  char *p;
public:
  B() { p = new char[10]; }
  ~B() { delete[] p; }
};
int main() {
  A *pa = new B;
  delete pa;
} // A lefoglalt terület felszabadult, mert a
// származtatott osztály destruktora is lefutott

https://git.ik.bme.hu/Prog2/eloadas_peldak/ea_05 virt_destruktor és otthonMegMukodott

Most itt tartunk a feladattal

Öröklés impl., ha nincs virtuális fv.

Öröklés impl., ha a Rajzol() virt.

Alakzat C implementációja

struct Alakzat {
  int x, y, szin;
  void (*rajzol)();
};

void AlakzatRajzol(struct Alakzat *this) {}

void AlakzatKonstr(struct Alakzat *this, int x0, int y0, int sz) {
  this->rajzol = AlakzatRajzol; // manó v. fordító ?
  this->x = x0;
  this->y = y0;
  this->szin = sz;
}
void AlakzatMozgat(struct Alakzat *this, int dx, int dy) {
  int sz = this->szin;
  this->szin = BACKGND;
  (*(this->rajzol))(this); //
  this->x += dx;
  this->y += dy;
  this->szin = sz;
  (*(this->rajzol))(this); //
}

Téglalap osztály újra

class Teglalap : public Alakzat {
  int xc, yc;

public:
  Teglalap(int x1, int y1, int x2, int y2, int sz)
      : Alakzat(x1, y1, sz), xc(x2), yc(y2) {}
  void ujMeret(int x2, int y2) {
    xc = x + x2;
    yc = y + y2;
  }
  void rajzol();
  // mozgat() az alaposztályban
};

Négyzet osztály (korlátoz)

class Negyzet : private Teglalap {
  // Eltakarja az alaposztályt ^
public:
  Negyzet(int x1, int y1, int s, int sz)
      : Teglalap(x1, y1, x1 + s, y1 + s, sz) {}
  void rajzol() { Teglalap::rajzol(); }
  void mozgat(int dx, int dy) { Teglalap::mozgat(dx, dy); }
};

Az ujMeret() fv-t így kívülről elérhetetlenné tettük (korlátoztuk az elérését)

Összefoglalás

Elérhetőség összefoglalása

attr\honnan külső származtatott tagfüggvény és barát
public:
protected: -
private: - -

Példa

Objektummodell kibővítve

Előző modell kiegészítései

Kiegészített alakzat

class Alakzat {
protected:
  Pont p0;                 // alakzat origója
  Szin sz;                 // alakzat színe
  static SDL_Surface *scr; // eldugott ”globális” (SDL1)
public:
  Alakzat(const Pont &p0, const Szin &sz) : p0(p0), sz(sz) {}
  const Pont &getp0() const { return p0; }
  static void setSurface(SDL_Surface *s) { scr = s; }
  virtual void rajzol() const = 0; // tisztán virt.
  void mozgat(const Pont &d);
  virtual ~Alakzat() {} // fontos, ha az alap. felől szabadítunk fel
};

Kor

class Kor : public Alakzat {
  int r; //< sugár
public:
  Kor(const Pont &p0, int r, Szin sz)
      : Alakzat(p0, sz), r(r) // Ősosztály inic
  {}
  void rajzol() const;
  // Miért nincs destruktor?
};
void Kor::rajzol() const {
  filledCircleColor(scr, p0.x, p0.y, r, sz);
  SDL_Flip(scr); // képfrissítés
}

A rajztábla most egy tömb

Alakzat *idom[100]; unsigned int db = 0;
while (VAN_ESEMENY) {
  if (GOMBNYOMAS) {
    if (db < 100 - 1) {
      idom[db] = new Kor(Pont(x, y), 40, RED); // felvesz
      idom[db]->rajzol(); ++db;
    }
  } else if (EGERMOZGAS) {
    for (int i = 0; i < db; i++) {
      idom[i]->mozgat(Pont(dx, dy)); idom[i]->rajzol();
    }
  }
}
for (int i = 0; i < db; i++) { delete idom[i]; } // letöröl

https://git.ik.bme.hu/Prog2/eloadas_peldak/ea_05