====== 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: {{ :courses:b2b99ppc:tutorials: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 ''''. 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; }