Warning
This page is located in archive. Go to the latest version of this course pages. Go the latest version of this page.

3. Dědění, virtuální metody

Ve cvičení se seznámíme s jednoduchými příklady dědění.

Vzorové příklady najdete buď v repozitáři tutorials v adresáři tut03

git pull
cd tut03

Alternativně lze najít kódy také v archivu: ppc-tut03.zip

Bázová třída

Příklad z minulého cvičení lze velmi pěkně zobecnit tak, že Kocka i Mys budou potomky jedné bázové třídy Zviratko. Tato třída má dva atributy, zdravi a kalorie. Zdraví bude potomkům, kteří volají konstruktor bázové třídy, vždy nastaveno na maximální hodnotu, tj. 100. Kalorie mohou být nastaveny parametrem konstruktoru, nebo lze využít defaultní hodnotu parametru konstruktoru bázové třídy.

Kromě atributů a konstruktoru obsahuje bázová třída ještě metodu pro tisk informací o instanci. Tato metoda je pak využita v přetíženém proudovém operátoru. Protože je druhým argumentem operátoru reference na bázovou třídu, lze pak využít tento operátor v derivovaných třídách.

Pomocí klíčového slova friend by se dal navázat přátelský vztah mezi třídou a operátorem, takže by operátor mohl mít přístup k privátním atributům třídy. Zvolíme ale tento postup, protože metodu info lze využít k demonstraci virtuálních metod. Zároveň tato metoda ukazuje využití datového typu std::stringstream, který lze využít pro pohodlné formátování textových řetězců.

class Zviratko {
    int zdravi, kalorie;
public:
    Zviratko (int in_kalorie = 1000): kalorie(in_kalorie), zdravi(100) {}
    std::string info () {
         std::stringstream ss;
         ss << "# zviratko | kalorie: " << setw(6) << this->kalorie << " | zdravi: " << setw(4) << this->zdravi;
         return ss.str(); 
    } 
};
 
void operator<< (std::ostream & s, Zviratko & z) {
    s << z.info() << std::endl;
}

Derivované třídy

Derivované třídy jsou pak definované následovně:

class Mys: public Zviratko {
public:
    Mys (): Zviratko (100) {}
};
 
class Kocka: public Zviratko {
public:
    Kocka (): Zviratko () {}
};

Kontrola inicializace objektů pak může být velmi jednoduchá:

Kocka k;
Mys m;
std::cout << "mys: " << m;
std::cout << "kocka: " << k;

Bázová třída by mohla mít metodu, která zajistí interakci mezi instancemi derivovaných tříd.

void Zviratko::konzumace (Zviratko &z) {
    this->kalorie += z.kalorie;
    std::cout << "! nekdo nekoho sezral" << std:endl;
}

Použití reference (nebo ukazatele) zajistí, že argumenty funkce mohou být instance bázové i derivovaných tříd. Interakce pak může probíhat napříč instancemi.

Použití metody bázové třídy může být v určitých situacích nedostatečné, např. pokud je třeba omezit interakci pouze na instance vybraných tříd. Můžeme se pokusit vytvořit variantní metody pro derivované třídy (ne nutně ve všech třídách, je možné, že některé instance využijí metodu definovanou v bázové třídě).

void Kocka::konzumace (Zviratko &z) {
    this->kalorie += z.kalorie;
    std::cout << "! kocka neco sezrala" << std::endl;
}

Zde ale narazíme na dvě zásadní komplikace:

  • atributy bázové funkce jsou privátní pro třídy derivované - to lze napravit změnou viditelnosti atributů na protected.
  • i jako protected (tedy viditelné v derivovaných třídách), jsou atributy z vnějšku nedostupné - to lze vyřešit jejich změnou na public (což nechceme), nebo vyvořením metody, která hodnotu atributu zpřístupní. Další variantou je uvedení tříd Mys a Kocka mezi přátelské třídy, to ovšem vyžaduje deklaraci derivovaných tříd ještě před deklarací bázové třídy.

// deklarace přátelské třídy
class Mys; 
 
class Zviratko {
// privatni atributy
public:
// verejne atributy a metody
    friend Mys;
};
 
class Mys : public Zviratko {
// definice třídy 
};

Po přepsání metody konzumace ve třídě Kocka se pak tyto metody chovají rozdílně podle toho, zda jsou volány v rámci instance třídy Kocka nebo Mys.

Pokud má příklad modelovat reálný problém, je třeba se zamyslet nad tím, zda je možná interakce mezi instancemi téže třídy. Na rozdíl od C umožňuje C++ za běhu zjistit datový typ, např. pomocí operátoru typeid, deklarovaném v hlavičkovém souboru <typeinfo>. Variant, jak využít tento operátor, je celá řada, nabízí se třeba využití metody rodičovské třídy, kterou mohou (nebo nemusí) zavolat metody derivovaných tříd a případně ji doplnit nejakou vhodnou informací.

void Zviratko::konzumace (Zviratko &z) {
    if (typeid(*this) == typeid(z)) {
        std::cout << "! kanibalismus mezi zviratky netolerujeme" << std::endl;
        return;
    }
    this->kalorie += z.kalorie;
}
 
void Kocka::konzumace (Zviratko &z) {
    Zviratko::konzumace (z);
    std::cout << "! kocka neco sezrala" << std::endl;
}

courses/b2b99ppc/tutorials/03.txt · Last modified: 2020/03/17 17:36 by viteks