1. Objektum

  • jól meghatározható felelősséggel rendelkezik
  • pontosan egy dologért felelős
  • a belső reprezentáció rejtve marad

2. Kitérő: string és vector

string:

std::string str = "hello";
str[0] = toupper(str[0]);
str.push_back('!');
std::cout << str.size() << " " << str << std::endl;

vector:

std::vector<int> t = {1, 2, 3};
t.push_back(4);
for (size_t i = 0; i < t.size(); ++i)
    std::cout << t[i] << " ";

3. Kitérő: string és vector

#include <iostream>
#include <string>
#include <vector>

int main() {
    std::string str = "hello";
    str[0] = toupper(str[0]);
    str.push_back('!');
    std::cout << str.size() << " " << str << std::endl;

    std::vector<int> t = {1, 2, 3};
    t.push_back(4);
    for (size_t i = 0; i < t.size(); ++i)
        std::cout << t[i] << " ";
}

4. Alakzatok

Mi a helyzet, ha a felelősség nem különíthető el egyszerűen? Példa:

  • körök, téglalapok, szövegek
  • minden alakzat kirajzolható és mozgatható
  • egyszínűek az alakzataink
  • ezeken kívül akárhányféle alakzattípus elképzelhető
  • közös tárolóba akarjuk tenni őket (sorrendtartó kirajzolás)

5. Példaprogram

6. Öröklés mint nyelvi elem

  • a "minden micsoda micsoda" reláció, is-a
  • ősosztály, leszármazott
  • Liskov Substitution Principle (LSP): minden leszármazottnak teljesítenie kell mindent, amit az ősosztály ígér!
  • a leszármazott örökli az ősosztály minden tagfüggvényét
class Alakzat {
    uint32_t szin;
public:
    // rajzolás?
};

class Teglalap : public Alakzat {
    // ...
};

7. Kompatibilitás

  • mindenhova, ahova ősosztály példánya kerül, kerülhet leszármazott
  • Teglalap& → Alakzat&, és Teglalap* → Alakzat* implicit
void invertal(Alakzat& alakzat) {
    uint32_t regiszin = alakzat.get_szin();
    uint32_t ujszin = szin_invertal(regiszin);
    alakzat.set_szin(ujszin);
}

int main() {
    Teglalap t1(/* ... */);
    invertal(t1); // Teglalap& → Alakzat&
}
  • a függvényben a statikus típus Alakzat, a dinamikus típus Teglalap

8. protected adattag

class Alakzat {
  protected: // a leszármazott is eléri
    uint32_t szin;
  public:
    // rajzolás?
};

class Kor : public Alakzat {
    Pont kozeppont;
    int sugar;
  public:
    void rajzol() const {
        SDL_rajzol_kor(kozeppont.x, kozeppont.y, sugar, szin);
    }
};

9. Inkább getter!

class Alakzat {
    uint32_t szin;
  protected:
    void set_szin(uint32_t uj);
  public:
    uint32_t get_szin() const;
    // rajzolás?
};

class Kor : public Alakzat {
    Pont kozeppont;
    int sugar;
  public:
    void rajzol() const {
      SDL_rajzol_kor(kozeppont.x, kozeppont.y, sugar, get_szin());
    }
};

10. Virtuális függvény

class Alakzat {
    uint32_t szin;
  public:
    virtual void rajzol() const { // !
      // ???
    }
};

class Kor : public Alakzat {
    Pont kozeppont;
    int sugar;
  public:
    virtual void rajzol() const {
       SDL_rajzol_kor(kozeppont.x, kozeppont.y, sugar, szin);
    }
};

11. Virtuális függvény mitírki

#include <iostream>
class Alakzat {
  public:
    virtual void kiir() const {
        std::cout << "Alakzat::kiir()" << std::endl;
    }
};

class Kor : public Alakzat {
  public:
    virtual void kiir() const {
        std::cout << "Kor::kiir()" << std::endl;
    }
};

void kiir(Alakzat const& a) {
    a.kiir();
}

int main() {
    Kor kor;
    kor.kiir();
    kiir(kor);
}

Ősosztály függvényének az explicit hívása:

class Kor : public Alakzat {
  public:
    virtual void kiir() const {
        Alakzat::kiir(); // !
        std::cout << "Kor::kiir()" << std::endl;
    }
};

12. C++11 override

  • az ősosztályban kell virtuálissá tennünk a függvényeket
  • a leszármazottban pontosan azt kell override-olni, amit az ősosztály kér
#include <iostream>
class Alakzat {
  public:
    virtual void kiir() const {
        std::cout << "Alakzat::kiir()" << std::endl;
    }
};

class Kor : public Alakzat {
  public:
    void kiir() const override { // !
        std::cout << "Kor::kiir()" << std::endl;
    }
};

13. Tisztán virtuális függvény

  • amikor az ősosztály nem tudja implementálni a függvényt
class Alakzat {
    uint32_t szin;
  public:
    virtual void rajzol() const = 0; // !
};

class Kor : public Alakzat {
    Pont kozeppont;
    int sugar;
  public:
    void rajzol() const override {
       SDL_rajzol_kor(kozeppont.x, kozeppont.y, sugar, szin);
    }
};

14. Absztrakt osztály

  • tisztán virtuális függvénnyel rendelkező osztályból önmagában nem jöhet létre példány
class Alakzat {
    uint32_t szin;
  public:
    virtual void rajzol() const = 0; // !
};

int main() { 
    Alakzat a; // ERROR
}

15. Konstruktorhívás

class Alakzat {
    uint32_t szin;
  public:
    explicit Alakzat(uint32_t szin) : szin(szin) {}
    /* ... */
};
class Kor : public Alakzat {
    Pont kozeppont;
    int sugar;
  public:
    Kor(uint32_t szin, Pont kozeppont, int sugar)
      : Alakzat(szin) // ősosztály konstruktora
      , kozeppont(kozeppont)
      , sugar(sugar) {
    }
    /* ... */
};

16. Konstruktorhívás menete

  1. ősosztály konstruktorának hívása
  2. adattagok konstruktora, deklaráció szerinti sorrendben!
  3. konstruktor törzse
class Kor : public Alakzat {
    Pont kozeppont;
    int sugar;
  public:
    Kor(uint32_t szin, Pont kozeppont, int sugar)
      : Alakzat(szin)
      , kozeppont(kozeppont)
      , sugar(sugar) {
    }
    /* ... */
};

17. Közös tároló

Sorrendtartó kirajzolás:

Teglalap t1(0xFF0000FF, Pont(1, 2), 4, -4);
Kor k1(0x00FF00FF, Pont(3, 0), 2);

std::vector<Alakzat*> alakzatok; // közös tároló

alakzatok.push_back(&t1); // Teglalap* -> Alakzat*
alakzatok.push_back(&k1); // Kor* -> Alakzat*

for (size_t i = 0; i < alakzatok.size(); ++i)
    alakzatok[i]->rajzol(); // a kör a téglalap felett van

18. Ősosztály felőli törlés

int main() {
    Alakzat* p = new Kor; // Kor* -> Alakzat*
    delete p;
}
  • a delete p sornak Kor objektumot kell megszüntetnie
  • az Alakzat-ból elérhetőnek kell lennie Kor destruktorának
  • legyen virtuális függvény a destruktor is!

19. Virtuális destruktor

class Alakzat {
    uint32_t szin;
  public:
    virtual void rajzol() const = 0;
    virtual bool bennevan(Pont p) const = 0;
    virtual void mozgat(Pont ennyivel) = 0;
    virtual ~Alakzat() {} // !
};
class Kor : public Alakzat {
    Pont kozeppont;
    int sugar;
  public:
    // default destruktor jó
};
  • ősosztályban kell virtuálissá tenni a destruktort
  • általában: ha van legalább egy virtuális függvény

20. Heterogén kollekció

std::vector<Alakzat*> alakzatok; // tulajdonos: majd törölnie kell!

alakzatok.push_back(new Teglalap(0xFF0000FF, Pont(1, 2), 4, 3));
alakzatok.push_back(new Kor(0x00FF00FF, Pont(3, 0), 2));

for (size_t i = 0; i < alakzatok.size(); ++i)
    alakzatok[i]->rajzol();

for (size_t i = 0; i < alakzatok.size(); ++i)
    delete alakzatok[i]; // destruktort hív
  • mindig pointert tárol
  • dinamikus memóriát kezel

21. Klikk

std::vector<Alakzat*> alakzatok;
Alakzat* mozgatott = NULL;
while (SDL_WaitEvent(&ev) && ev.type != SDL_QUIT) {
    switch (ev.type) {
      case SDL_MOUSEBUTTONDOWN:
        Pont hol(ev.button.x, ev.button.y);
        for (int i = alakzatok.size() - 1; i >= 0; --i) {
            if (alakzatok[i]->bennevan(hol)) { // bennevan()
                mozgatott = alakzatok[i];
                break; /* első után álljunk meg */
            }
        }
        break;
      case SDL_MOUSEBUTTONUP:
        mozgatott = NULL;
        break;
      /* ... */
    }
}

22. Mozgatás

std::vector<Alakzat*> alakzatok;
Alakzat* mozgatott = NULL;
while (SDL_WaitEvent(&ev) && ev.type != SDL_QUIT) {
    bool mozgott = false;
    switch (ev.type) {
      /* ... */
      case SDL_MOUSEMOTION:
          Pont mennyivel(ev.motion.xrel, ev.motion.yrel);
          if (mozgatott != NULL) {
              mozgatott->mozgat(mennyivel); // mozgat()
              mozgott = true;
          }
          break;
    }
    if (mozgott) {
        /* ... háttér ... */
        for (size_t i = 0; i < alakzatok.size(); ++i)
            alakzatok[i]->rajzol(renderer); // rajzol()
    }
}

23. Alakzat interfésze

class Alakzat {
    /* ... */
  public:
    virtual void rajzol() const = 0;
    virtual bool bennevan(Pont p) const = 0;
    virtual void mozgat(Pont ennyivel) = 0;
    virtual ~Alakzat() {}
};
class Kor : public Alakzat {
    /* ... */
  public:
    void rajzol() const override {
       SDL_rajzol_kor(kozeppont.x, kozeppont.y, sugar, szin);
    }
    void bool bennevan(Pont p) const override {
       return tavolsag(p, kozeppont) <= sugar;
    }
    void mozgat(Pont ennyivel) override {
      kozeppont += ennyivel;
    }
};

24. Polimorfizmus fogalma

  • "többalakúság", "többarcúság"
  • ugyanaz a kódban szereplő kifejezés különbözően viselkedik
  • 1.0 / 2 vagy 1 / 2
  • függvény-overload
  • [template]
  • [union + type flag, stb.]
  • öröklés: futási idejű, nyílt végű

25. Az öröklés mint a polimorfizmus eszköze

Az Alakzat egész interfésze polimorf.

class Alakzat {
    uint32_t szin;
  public:
    uint32_t get_szin() const;
    void set_szin(uint32_t uj);

    virtual void rajzol() const = 0;
    virtual bool bennevan(Pont p) const = 0;
    virtual void mozgat(Pont ennyivel) = 0;
    virtual ~Alakzat();

    Alakzat* kattint(Pont p) { // nem virtuális, de polimorf!
        if (bennevan(p))
            return this;
        return nullptr;
    }
};

26. Az interface fogalma

  • a main-t nem érdekli a szín, egyáltalán
  • elképzelhető olyan leszármazott, aminek nincs (egy, kitüntetett) színe
class Alakzat { // "interface"
  public:
    virtual void rajzol() const = 0;
    virtual bool bennevan(Pont p) const = 0;
    virtual void mozgat(Pont ennyivel) = 0;
    virtual ~Alakzat() {}
};

class TeliAlakzat : public Alakzat { // ez is absztrakt!
    uint32_t szin;
  public:
    uint32_t get_szin() const;
    void set_szin(uint32_t uj);
};

27. Madár-e a strucc?

class Madar {
    virtual void repul() { /* ... */ }
};

class Strucc : public Madar {  // hibás
    virtual void repul() {
        throw std::runtime_error("Nem tudok repülni");
    }
};
  • ha van Madar::repul(), akkor a strucc nem lehet madár!
  • RopkepesMadar != Madar

28. Téglalap-e a négyzet?

Ha egy téglalap objektumnak bármikor lehetnek különbözőek az oldalai, akkor nem:

void pelda1(Teglalap& t) {
    t.set_a(20);
    t.set_b(10);
    std::cout << t.terulet() << " == 200";
}

29. Négyzet-e a téglalap?

A téglalap nem tudja megígérni, hogy az oldalai egyforma hosszúak:

ezért hibás
double terulet(Negyzet const& n) {
    return pow(n.get_a(), 2);
}

Teglalap t1(20, 30);
std::cout << terulet(t1);   /* 400 :( */

30. Négy láb

31. Négyzet és téglalap

class Alakzat { /* ... */ };
class TeliAlakzat : public Alakzat { /* ... */ };

class Teglalap : public TeliAlakzat { 
    int a;
    int b;
    /* ... */
};
class Negyzet : public TeliAlakzat { 
    int a;
    /* ... */
};

A kész példaprogram innen letölthető.

Következik: Öröklés C++ módra

33. Kudos

  • BME EET-nek az InfoC motorért

    • Kohári Zsolt
  • CPPFTW-nek

    • Máté Gábor
    • Szász Márton
    • Csala Péter
  • prog2.cppftw.org