820 likes | 1.01k Views
RND r. Jan Lánský, Ph.D. Katedra softwarového inženýrství MFF UK Katedra informatiky V Š FS (Autor původní verze slajd ů : Mgr. Zbyněk Winkler ) (Autor prapůvodní verze slajd ů : RND r. Filip Zavoral, Ph.D. ) ( Část slajdů převzata od : RND r. David Bednárek ) zizelevak@gmail.com
E N D
RNDr. Jan Lánský, Ph.D. Katedra softwarového inženýrství MFF UK Katedra informatiky VŠFS (Autor původní verze slajdů:Mgr. Zbyněk Winkler) (Autor prapůvodní verze slajdů:RNDr. Filip Zavoral, Ph.D.) (Část slajdů převzata od:RNDr. David Bednárek) zizelevak@gmail.com http://kocour.ms.mff.cuni.cz/~lansky/ Objektově orientované programování
Studijní povinnosti Zápočet Aktivní účast na cvičeních (80 %) Chybějící účast lze nahradit: Vypracováním a předvedením příkladů probraných na cvičení [v semestru] Zápočtový test (praktický příklad, odladěný, 3 hodiny) [kdykoliv] Zápočtový program Téma (do 2. přednášky) Specifikace (do 3. přednášky) Betaverze (do konce semestru) Finální verze (do 30.6.) Zkouška Ústní, s přípravou na PC (2 hodiny) Na zkoušku lze jít i bez zápočtu
Literatura Miroslav Virius: Programování v C++ Miroslav Virius: Pasti a propasti jazyka C++ Miroslav Virius: Od C k C++ Scott Meyers: Effective C++, More Effective C++, Effective STL Herb Sutter: Exceptional C++, More Exceptional C++ Que: ANSI/ISO C++ Professional Programmer's Handbook Bruce Eckel: Myslíme v jazyce C++ James O. Coplien: Advanced C++ Programming Styles and Idioms Bjarne Stroustrup: The C++ Programming Language ISO/IEC 14882, ANSI: Programming languages - C++ (1998, 2003)
ZS (PJC) LS (OOP) Obsah předmětu C++ C++ C C Paradigmata programování, OOP Objekty, zapouzdření, dědičnost, konstruktory a destruktory Přetěžování funkcí, předefinování operátorů Pozdní vazba, virtuální funkce Abstraktní datové typy Šablony, výjimky, prostory jmen. Objektové knihovny: streams, STL RTTI, OO styly a idiomy...
Paradigmata programování side effects, údržba nelze rozumně rozšiřovat dále – generické programování šablony, STL Procedurální programování • jakou akci mám provést • vstup – výpočet (algoritmus) – výstup • black box: procedura / funkce Modulární programování • rozdělení problému na komponenty • procedury pracují nad daty - rozhraní • black box: modul Datová abstrakce • vytvoření vlastního datového typu (abstract/user defined datové typy) • kompletní množina operací nad tímto typem • black box: datový typ Objektové programování • dědičnost – obecné / konkrétní vlastnosti • Polymorfismus – odlišné chování potomků • možnost pozdějších rozšíření • zapouzdření
Koncepční pohled objekt: entita reagující na vnější podněty třída: množina stejně reagujících entit Technický pohled objekt: struktura obsahující data a funkce, instance třídy (proměnná) třída: typ objektu – jednotná struktura dat, stejné operace nad daty Zobecnění pojmu struktura (struct) Rozdíl mezi class a struct v C++ je nepatrný, užívání class je pouze konvence deklarace třídy obsahuje Deklarace datových položek (stejně jako v C) Funkce (metody), virtuální funkce a statické funkce Definice výčtových konstant a typů (včetně vnořených tříd) Rozhraní – veřejné informace a služby pro uživatele Implementace – (neveřejná) interní data a metody (funkce) Třídy a objekty
Třída zvíře v C++ - rozhraní definice třídy class Zvire { private: int zaludek; public: Zvire() { zaludek = 1; }; int zije() { return zaludek>0; }; int jez( int jidlo); int vymesuj( int objem); }; zvire.h rozhraní (veřejné) vnitřní stav (privátní) Datová položka konstruktor (inicializace) inline tělo funkce metody Deklarace metody
Třída zvíře - implementace class Zvire { private: int zaludek; public: Zvire() { ... }; int zije() { ... }; int jez( int jidlo); int vymesuj( int objem); }; #include ”zvire.h” int Zvire::jez( int jidlo) { if( ! zije()) return 0; return zaludek += jidlo; } int Zvire::vymesuj( int objem) { if( (zaludek -= objem) <= 0) zaludek = 0; return zaludek; } zvire.h zvire.cpp :: operátor kvalifikace Třída metody Implementace (tělo) metody Přístup k datům metody Středník !!!
Třída zvíře - použití class Zvire { private: int zaludek; public: Zvire() { ... }; int zije() { ... }; int jez( int jidlo); int vymesuj( int objem); }; #include ”zvire.h” ..... { ..... Zvire pytlik; pytlik.jez(5); pytlik.vymesuj(3); if( ! pytlik.zije()) return -1; pytlik.vymesuj(4); if( ! pytlik.jez(1)) return -2; ..... } -1 0 mujprogram.cpp zvire.h Import rozhraní Automatický konstruktor zaludek = 1 Instance třídy = objekt zaludek = 6 zaludek = 3
Objekt - instance třídy int Zvire::jez( int jidlo) { if( ! zije()) return 0; return zaludek += jidlo; } ..... Zvire pytlik, beruska; pytlik.jez( 5); beruska.jez( 1); ..... zaludek 6 zaludek 2 dvě instance třídy Metoda třídy - ke kterému objektu má přistupovat? ? pytlik: beruska:
this int jez( Zvire* this, int jidlo) { if( ! zije( this)) return 0; return this->zaludek += jidlo; } ..... Zvire pytlik, beruska; jez( &pytlik, 5); jez( &beruska, 1); ..... int Zvire::jez( int jidlo) { if( ! zije()) return 0; return zaludek += jidlo; } ..... Zvire pytlik, beruska; pytlik.jez( 5); beruska.jez( 1); ..... zaludek 6 zaludek 2 Každá metoda dostane 'tajný'parametr this – ukazatel na objekt zvire:: znamena zvire * this C C++ this->zije() this->zaludek this pytlik: beruska:
Reference reference pouze inicializacenelze měnit int x = 1, y = 2; int *px; px = &x; *px = 3; int &ry = y; ry = 4; return *px + ry; 3 4 reference i ukazatele jsou reprezentovány adresou swap( int& a, int& b) { int c = a; a = b; b = c; } int x = 1, y = 2; swap( x, y); skutečné parametry odkazy na proměnné 1 2 zpřehlednění kódu přetěžování funkcí x: :px y: :ry x: :a y: :b
Přetěžování funkcí Funkce je definována svým identifikátorem a počtem a typem parametrů int pocitej( int x) { return x+1; } int pocitej( int a, int b) { return 2*a + b; } int pocitej( int a, const char* s) { return a + strlen( s); } pocitej( 1); // int pocitej( int) pocitej( 1, 2); // int pocitej( int, int) pocitej( 1, "ahoj"); // int pocitej( int, char*) Funkce se stejným identifikátorem ale různým počtem parametrů Funkce se stejným počtem ale různým typem parametrů Správná funkce podle počtu a typů skutečných parametrů
Implicitní parametry Některé parametry funkce mohou mít implicitní hodnoty • pokud nejsou všechny parametry implicitní – implicitní parametry odzadu Při volání funkce lze implicitní parametry vynechat • použije se implicitní hodnota Kdy použít přetěžování a kdy implicitní parametry? • Stejný kód pro různý počet parametrů implicitní parametry • Pro různé počty nebo typy parametrů různý kód přetěžování int fce( int a, int b = 2, int c = 4) { return 2*a + b - c; } fce( 1); // int fce( 1, 2, 4) fce( 1, 5); // int fce( 1, 5, 4) fce( 1, 5, 6); // int fce( 1, 5, 6) Volá se stále stejná funkce int fce( int, int, int)
Konstruktory class Zvire { private: int zaludek; public: Zvire() { zaludek = 1; }; Zvire( int zal) { zaludek = zal; }; Zvire( const Zvire& vzor) { zaludek = vzor.zaludek; }; }; Zvire beruska; Zvire pytlik( 20); Zvire beberuska( beruska); Zvire tlustoch = pytlik; různé zápisy copy konstruktoru Pro U≠T nejsou zcela ekvivalentní: U u; T t(u); // T::T( U&) T t = u; // T::T( T(u)) nebo // T::T( u.operator T()) zatím lze ignorovat Implicitní konstruktor bez parametrů Konstruktor s parametry Copy konstruktor X (const X&) vytvoří objekt jako kopii jiného
Konstruktor s parametry class Clovek { private: char jmeno[50]; Clovek(); public: //Clovek() { jmeno[0] = 0; }; Clovek( char *jmeno) { strcpy(this->jmeno, jmeno); }; }; Clovekhonza("Honza"); //Clovek petr; Tímto zakážeme deklarovat objekt bez použití NEimplicitního konstruktoru • Využití • Předání nastavení objektu • Šetří řádky kódu • Zakázat implicitiní konstruktor • Dobře rozvážit zda zakázat • Bezpečnost proti nezadání klíčové hodnoty • identifikátor Nejde, zakázali jsme
Přetěžování operátorů - deklarace implicitní parametry implicitní konstruktor class Bod { private: int x, y; public: Bod( int xx=0, int yy=0) { x=xx; y=yy; }; Bod operator+( const Bod&); Bod operator=( const Bod&); }; Bod a(1,2), b, c; c = a + b; přetížení operátoru + a + b a.operator+(b) a = b a.operator=(b) Bod::Bod(0,0); c.operator=(a.operator+(b)); c.assign( a.add( b));
Přetěžování operátorů – těla metod Bod Bod::operator=( const Bod& b) { x = b.x; y = b.y; return *this; } Bod Bod::operator+( const Bod& b) { return Bod( x+b.x, y+b.y); } reference x this->x aktualizace stavu kopie objektu (hodnotou přiřazení je přiřazovaná hodnota) vytvoření dočasného objektu konstruktor Bod::Bod(int, int) co to je ???
Přetěžování operátorů - pravidla • Většinu operátorů jazyka C++ lze definovat pro uživatelské datové typy • Nelze předefinovat tyto operátory:. .* :: ? : sizeof • Alespoň jeden z operandů musí být třída nebo výčtový typ nebo reference na ně • Nelze předefinovat operace na číselných typech a ukazatelích • Předefinováním nelze měnit prioritu a asociativitu operátorů • Pro předefinované operátory nemusí platit identity definované pro základní typy • ++a nemusí být ekvivalentní a=a+1 • a[b] nemusí být ekvivalentní *(a+b)ani b[a] • je však velmi doporučeno dodržovat běžnou sémantiku • Pro předefinované operátory && a || neplatí pravidla o zkrácenémvyhodnocování • Typy skutečných operandů nemusejí přesně odpovídat typům formálních parametrů • stejná pravidla jako pro přetížené funkce
Pozor! Pro předefinované operátory nemusí platit identity definované pro základní typy: a=a+b a+=b a[b] *(a+b) Přetěžování operátorů – ekvivalence Bod Bod::operator+=( const Bod& b) { x += b.x; y += b.y; return *this; } Bod Bod::operator+=( const Bod& b) { return *this = *this + b; } this->operator=( this->operator+( b))
copy konstruktor a operator= class Bod { private: int x, y; public: Bod( const Bod& b) { x=b.x; y=b.y; }; Bod operator=( const Bod& b) { x=b.x; y=b.y; return *this; }; }; Bod a(1,2); Bod k, m(a), n = a; k = m; není-li copy konstruktor nebo operator= definován, automaticky se vygeneruje copy konstruktor resp. operator= všech složek copy konstruktordefinice nového objektu operator= přiřazení do existujícího objektu Rozdíl mezi copy konstruktorem a přiřazením: • copy konstruktor se nemusí starat o předchozí stav objektu, přiřazení ano • přiřazení vrací (přiřazovanou) hodnotu, copy konstruktor nevrací nic
class Bod { public: Bod( const Bod& b) { dosad(b); }; Bod operator=( const Bod& b) { dosad(b); return *this; }; int GetX() {return x; }; int GetY() {return y; }; void SetX(int x) { this->x = x; } void SetY(int y) { this->y = y; } private: int x, y; void dosad( const Bod & b) { x=b.x; y=b.y; }; }; Udržovatelnost kódu • Těla operátorů a konstruktorů • volání jiné funkce • Public část před private • Private položky nejsou zajímavé • Datové položky vždy private • Funkce Get a Set • Těla metod vždy v *.cpp souboru • Pro pozdější rozšíření • Lépe se hledá kde je implementované • Jména tříd • ToJeMojeTrida • Jména funkcí proměnných • mojePrvniFunkce
Objekt a ukazatel na objekt class Zvire { ..... }; Zvire * pytlik; Zvire beruska; pytlik = &beruska; pytlik = new Zvire; C++ odlišuje objekt a ukazatel na něj • Rozdíl oproti jiným jazykům • Java, JavaScript, PHP, VisualBasic, ... • Analogie s chováním stuct v C • Ukazatel nelze použít dokud není splněna jedna z možností: • Přiřazen existující objekt • Reference • Dynamicky vytvořen nový objekt • Operátor new Nevzniká tu žádný objekt vzniká nový objekt
Operátory new a delete new: alokace paměti, zavolání konstruktoru • není nutno testovat úspěšnost – mechanismus výjimek delete: zavolání destruktoru, dealokace paměti • jako parametr lze i 0 dynamická alokace, implicitní konstruktor náhrada za malloc() Bod a(1,2); Bod *pb = newBod; *pb = a + a; a = *pb; delete pb; pb = newBod( a); Bod *pc = newBod( 3, 5); a = *pb + *pc; delete pc; delete pb; uvolnění paměti další alokace, explicitní konstruktory alokace pole objektů char* buf = new char[10]; strcpy( buf, “ahoj”); ... delete[] buf; uvolnění paměti - u alokovaných polí nutno []
Chytré řetězce – nápad Práce s řetězci v C + efektivní– nepohodlá a těžkopádná – časté chyby Chtěl bych: přiřazování, zřetězení, automatická alokace místa Str s1 = “ahoj”; Str s2 = “babi”; Str s3; s3 = s1 + ‘ ‘ + s2; s3 += “.”; s3 = (char*) malloc( strlen(s1) + strlen(s2) + 2); strcpy( s3, s1); s3[ strlen(s1)] = ‘ ‘; strcpy( s3 + strlen(s1) + 1, s2); ‘obyčejné‘ zřetězení – nechci se starat o to, kde sebrat místo
Chytré řetězce - třída operace s řetězci ukazatel na alokovaná data class Str { private: char* buf; public: Str() { buf = 0; }; Str( const Str& s); Str( const char* s); ~Str() { delete[] buf; }; Str& operator=( const Str& s); Str operator+( const Str& s); int len() const{ return buf ? strlen(buf) : 0; }; }; implicitní konstruktor prázdný řetězec destruktor objekt si musí po sobě uklidit delete přežije i 0, nemusím testovat další metody (délka řetězce) Konstantní funkce, nemodifikuje objekt
Destruktory ukazatel na alokovaná data class Str { private: char* buf; public: Str() { buf = 0; }; Str( const char* s) { buf = new char[ strlen( s) + 1]; strcpy( buf, s); }; ~Str() { delete[] buf; }; }; alokace paměti pro řetězec destruktor - automaticky se volá při zrušení objektu nemá argumenty nic nevrací
Vyvolání destruktoru v kostruktoru s1 se alokuje paměť pro řetězec fce() { Str s1 = “ahoj”; Str* s2 = new Str( “babi”); ..... delete s2; ..... } dynamická alokace sp2 delete zavolá destruktor(a potom uvolní paměť) zde končí život s1 automaticky se vyvolá destruktor
Řetězce – implementace uklidit po předchozím řetězci Str& Str::operator=( const Str& s) { delete[] buf; if( ! s.len()) { buf = 0; } else { buf = new char[ s.len()+1]; strcpy( buf, s.buf); } return *this; } Str::Str( const Str& s) { .... } prázdný řetězec alokace paměti okopírování znaků přiřazená hodnota – objekt sám reference kvůli efektivitě copy konstruktor – totéž bez delete a return
později si ukážeme ještě lepší – counted pointers class Str { private: char* buf; void copy( const char* s); public: Str() { buf = 0; }; Str( const Str& s) { copy( s.buf); }; Str( const char* s) { copy( s); }; ~Str() { clear(); }; Str& operator=( const Str& s) { clear(); copy( s.buf); return *this; }; Str& operator=( const char* s) { clear(); copy( s); return *this; }; void clear() { delete[] buf; }; }; privátní metoda – alokace a kopírování konstruktory: jen alokace a kopírování přiřazení: i uklizení a návratová hodnota často potřebujeme uklízet O něco lepší implementace !!! buf = 0; nebo private !!!
Implementace kopírování class Str { private: char* buf; void copy( const char* s); public: Str() { buf = 0; }; Str( const Str& s) { copy( s.buf); }; Str( const char* s) { copy( s); }; ~Str() { clear(); }; Str& operator=( const Str& s) { clear(); copy( s.buf); return *this; }; Str& operator=( const char* s) { clear(); copy( s); return *this; }; void clear() { delete[] buf; }; }; předpokládáme prázdný buf zařídí volající metoda - copy je private void Str::copy( const char* s) { if( !s || !*s) { buf = 0; } else { buf = new char[ strlen( s)+1]; if( buf) strcpy( buf, s); } } zkontrolovat prázdný řetězec alokace a kopírování Jde clear() přidat do copy() ???
Zřetězení Str Str::operator+( const Str& s) { Str newstr; newstr.buf = new char[ len() + s.len() + 1]; strcpy( newstr.buf, buf); strcat( newstr.buf, s.buf); return newstr; } Str Str::operator+( const char* s) { Str newstr; newstr.buf = new char[ len() + strlen(s) + 1]; strcpy( newstr.buf, buf); strcat( newstr.buf, s); return newstr; } nový prázdný řetězec místo na znaky první operand druhý operand návratová hodnota nelze návrat reference (lokální dočasný objekt) nové hodnoty VŽDY vracet hodnotou
Připojení řetězce když už umíme + a = proč si neudělat += class Str { .... public: .... Str& operator=( const Str& s); Str& operator=( const char* s); Str operator+( const Str& s); Str operator+( const char* s); Str& operator+=( const Str& s) { *this = *this + s; return *this; }; Str& operator+=( const char* s) { *this = *this + s; return *this; }; }; operator+( Str&) operator+( char*) lze vracet referencí existující hodnota
Str a jednotlivé znaky class Str { ... public: Str(); Str( const Str&); Str( const char*); Str( char c) { buf = new char[ 2]; buf[0] = c; buf[1] = '\0'; }; Str& operator=( const Str&); Str& operator=( const char*); Str& operator=( char); Str operator+( int); Str operator+=( int); }; dodefinovat konstruktor, přiřazení a operace pro další typ
Výstup později si ukážeme elegantnější řešení - streams neprázdný obsah na stdout class Str { ... public: int print() const { return buf ? printf( "%s", buf) : 0; }; }; Str s1 = "ahoj", s2("babi"), s3; s3 = s1 + ' ' + s2; s3.print(); Str("\n").print(); (s3 += ".\n").print(); ‘normálně’ spojím řetězce s mezerou ... a vytisknu dočasný objekt reference na s3
... and together class Str { private: char* buf; void copy( const char* s); void clear(); public: Str() { buf = 0; }; Str( const Str& s); Str( const char* s); Str( char c); ~Str(); Str& operator=( const Str& s); Str& operator=( const char* s); Str& operator=( char c); Str operator+( const Str& s); Str operator+( const char* s); Str operator+( char c); Str& operator+=( const Str& s); Str& operator+=( const char* s); Str& operator+=( char c); int len() const; int print() const; };
vztah tříd předek-potomek – hierarchie přesnější názvosloví: základní (base) / odvozená třída (derived class) vícenásobná dědičnost dvakrát měř, jednou řež, protokoly specializace potomek má/umí něco navíc reusabilita jiné chování bez změny původní třídy Dědičnost Zvíře Pes Pitbul Člověk jez, vyměšuj sedni, lehni trhej uč_se
pes jako potomek zvířete - definice class Zvire { protected: int zaludek; public: Zvire(); Zvire( int jidlo); int zije(); int jez( int jidlo); int vymesuj( int objem); }; potomek (odvozená třída od) Zvířete class Pes : public Zvire { private: enum t_stav { Stoji, Sedi, Lezi }; t_stav stav; public: Pes() { stav = Stoji; }; void sedni() { stav = Sedi; }; t_stav codela() { return stav; } }; přidaná položka položky předka metody předka Zvire pytlik; Pes azor; pytlik.jez(); azor.jez(); azor.sedni(); stav žaludek položky potomka metody potomka potomek obsahuje všechny položky a metody předka Přístup pro třídu a potomky jez, vyměšuj sedni
Konstruktor a destruktor předka implicitní konstruktor předka (automaticky) class Zvire { ... ~Zvire() { printf( "zabijim zvire "); }; }; class Pes : public Zvire { ... public: Pes() { stav = Stoji; }; Pes( int jidlo) : Zvire( jidlo) { stav = Stoji; }; ~Pes() { printf( "zabijim psa "); }; }; { Pes azor; ... } explicitní konstruktor předka konstruktory předků a vložených tříd se volají před konstruktorem potomka destruktor předka se vyvolá automaticky po ukončení destruktoru potomka zabijim psa zabijim zvire
Potomka lze přiřadit do předka (platí i pro ukazatele) Předka NELZE přiřadit do potomka (platí i pro ukazatele) Kompatibilita předka a potomka pes umí jíst, brouk neumí štěkat Zvire pytlik, *pz; Pes azor, *pp; pytlik = azor; pz = &azor; stav žaludek žaludek stav azor = pytlik; pp = &pytlik; žaludek žaludek nelze azor pytlik azor pytlik ???
odlišné chování potomků – pozdní vazba (late binding) Polymorfismus Zvíře Pes Pitbul sní maso sní hodně masa najde něco v přírodě Člověk jde do restaurace jez jez jez jez
Polymorfismus - motivace Tohle není polymorfismus ! class Zvire { jez() { priroda(); }; }; class Pes : public Zvire { jez() { maso(1); }; }; class Pitbul : public Pes { jez() { maso(10); }; }; class Clovek : public Zvire { jez() { hospoda(); }; }; Zvire pytlik; Pes punta; Pitbul zorro; Clovek pepa; pytlik.jez(); // priroda(); punta.jez(); // maso(1); zorro.jez(); // maso(10); pepa.jez(); // hospoda(); 'normální' vlastnost tříd zakrývání metod Při překladu je známo ze které třídy se volá metoda Každá třída má vlastní implementaci (tělo) metody jez
Polymorfismus – takto nelze do ukazatele na základní třídu (předka) dám ukazatel na nově vytvořený objekt odvozené třídy (potomka) z je ukazatel na zvíře volá se Zvire::jez() Zvire* z; z = new Pes; z->jez(); // priroda(); z = new Clovek; z->jez(); // priroda(); Zvire* z; z = new Pes; z->Pes::jez(); // priroda(); z = new Clovek; z->Clovek::jez(); // priroda(); pokus – 'na tvrdo' chci metodu potomka nelze - syntaktická chyba pes není předkem zvířete
Polymorfismus – takto bych to chtěl chtěl bych, aby se volaly 'správné' metody Zvire* z; z = new Pes; z->jez(); // maso(1); z = new Clovek; z->jez(); // hospoda(); Zvire* naseRodina[3]; naseRodina[0] = new Clovek; naseRodina[1] = new Pes; naseRodina[2] = new Pitbul; for( int i = 0; i < 3; i++) naseRodina[i]->jez(); Chci pokaždé se zavolat jinou metodu Rozlišení metody se musí dít za běhu
Virtuální funkce - deklarace magické klíčové slovo virtual class Zvire { virtual jez() { priroda(); }; }; class Pes : public Zvire { virtual jez() { maso(1); }; }; class Pitbul : public Pes { virtual jez() { maso(10); }; }; class Clovek : public Zvire { virtual jez() { hospoda(); }; }; každý objekt si s sebou nese informaci kterou virtuální funkci používá
Virtuální funkce - implementace stav žaludek žaludek tabulka virtuálních funkcí jez jez Zvire::jez() { priroda(); }; Pes::jez() { maso(1); }; zavolá se správná metoda podle tabulky virtuálních funkcí Zvire * z; z = new Zvire; z = new Pes; Pes Zvire z->jez();
Virtuální funkce a konstruktory a destruktory v konstruktoru a destruktoru se vždy volá metoda vytvářeného/rušeného objektu nejdřív se zavolá konstruktor předka class A { public: virtual f(); A() { f(); }; // A::f ~A() { f(); }; // A::f g() { f(); }; // A/B::f }; class B : public A { public: virtual f(); B() { f(); }; // A::A B::f ~B() { f(); }; // B::f A::~A g() { f(); }; // B::f }; určí se za běhu podle skutečného typu objektu nejdřív se provede kód destruktoru, pak se zavolá destruktor předka
Volání virtuálních funkcí class A { public: virtual f(); }; class B : public A { public: virtual f(); }; A a; // A::A B b; // B::B A * paa = &a; A * pab = &b; B * pbb = &b; // B * pba = &a; nelze!! (předka do potomka) a.f(); // A::f b.f(); // B::f paa->f(); // A::f pab->f(); // B::f pbb->f(); // B::f b.A::f(); // A::f b.B::f(); // B::f a.B::f(); // NE! paa->A::f(); // A::f pab->A::f(); // A::f pab->B::f(); // NE! pbb->A::f(); // A::f pbb->B::f(); // B::f pozdní vazba A::f kvalifikované volání B::f paa a pab b pbb
Abstraktní třída, čistě virtuální funkce abstraktní třída nelze vytvořit objekt společný předek int armada; class Vojak { public: enum THod { vojin, desatnik, porucik, general }; Vojak( THod hod = vojin) { hodnost=hod; armada++; }; virtual void pal() = 0; virtual ~Vojak() { armada--; }; private: THod hodnost; }; pure virtual function ⇒abstraktní třída společné rozhraní class Samopal {}; class Kalasnikov : public Samopal {}; class Pesak : public Vojak { private: Samopal* sam; public: Pesak( THod hod=vojin) : Vojak( hod) { sam = new Kalasnikov; }; virtual void pal() { sam->pal(); }; virtual ~Pesak() { delete sam; }; }; POZOR!!! Nutný virtuální destruktor
Abstraktní třídy, virtuální destruktory pokud by ~Vojak nebyl virtuální class Vojak { virtual ~Vojak() { armada--; }; }; class Pesak : public Vojak { virtual ~Pesak() { delete sam; }; }; // Vojak v; // NELZE – abstraktní třída Pesak p; // OK – Pesak Vojin Pesak* pp = new Pesak; // OK pp->pal(); // Pesak::pal Vojak* pv = new Pesak; // OK pv->pal(); // Pesak::pal delete pp; // OK, Pesak::~Pesak delete pv; // !!! Vojak::~Vojak POZOR!!! nejsou-li destruktory virtuální, nezruší se samopal Řešení: virtuální destruktor