Ve cvičení se seznámíme s jednoduchými příklady dědění.
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.
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 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:
protected
.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; }