Prezentace se nahrává, počkejte prosím

Prezentace se nahrává, počkejte prosím

Pokročilé programování v C++ (část B) David Bednárek www.ksi.mff.cuni.cz.

Podobné prezentace


Prezentace na téma: "Pokročilé programování v C++ (část B) David Bednárek www.ksi.mff.cuni.cz."— Transkript prezentace:

1 Pokročilé programování v C++ (část B) David Bednárek

2 C++11

3  Jazyk  && (rvalue reference)  Lambda  auto  Variadic templates  Inicializátory  Knihovny

4 Policy class, traits

5 Šablony tříd - definice  Šablona je generická třída parametrizovaná libovolným počtem formálních parametrů těchto druhů:  celé číslo – uvnitř šablony se chová jako konstanta, použitelná i jako meze polí  ukazatel libovolného typu  libovolný typ – deklarováno zápisem class T nebo typename T, identifikátor formálního parametru se chová jako identifikátor typu, použitelný uvnitř šablony v libovolné deklaraci  Prefix definice šablony template  lze použít před několika formami deklarací; oblastí platnosti formálních parametrů je celá prefixovaná deklarace

6 Šablony funkcí  Šablona funkce je generická funkce (globální nebo metoda) prefixovaná konstrukcí template  se stejnými druhy formálních parametrů šablony jako u šablon tříd template // parametry šablony int f( T * p, int q);// parametry funkce template // parametry šablony int g( T * p, vector q);// parametry funkce  Šablony funkcí lze volat dvěma způsoby  Explicitně f ( a, b)  Automaticky g( a, b) Překladač dopočte parametry šablony z typů parametrů funkce Všechny formální argumenty šablony by měly být užity v typech formálních parametrů funkce

7 Šablony s mnoha parametry vs. policy class template< typename T, bool binary, bool cached> class File { //... T get() { if ( binary ) //... } }; File my_file; template class File { //... typename P::T get() { if ( P::binary ) //... } }; struct my_policy { typedef char T; static const bool binary = false; static const bool cached = true; }; File my_file;

8 Policy class template class File { //... typename P::T get() { if ( P::binary ) //... } }; struct my_policy { typedef char T; static const bool binary = false; static const bool cached = true; }; File my_file;  Policy class  Třída (class/struct) použitá pouze jako parametr šablony  Neexistuje objekt tohoto typu  Obsahuje pouze  typy (typedef/vnořené třídy)  statické konstanty  statické funkce  Reference na typy z policy class jsou závislá jména a musejí mít prefix typename

9 Šablony tříd – závislé typy  Šablony tříd (včetně těl metod) se při deklaraci kontrolují pouze částečně Překladač může kontrolovat jen to, co nezávisí na parametru Některé překladače nedělají ani to  Překladač potřebuje odlišit jména typů od ostatních jmen U jmen ve tvaru A::B to překladač někdy nedokáže Programátor musí pomoci klíčovým slovem typename template class X { typedef typename T::B U;// T::B je typ typename U::D p;// T::B::D je typ typename Y ::C q;// Y ::C je typ void f() { T::D(); }// T::D není typ } typename je nutné uvést před jmény typu ve tvaru A::B, kde A je závislé jméno Závislé jméno je jméno obsahující přímo či nepřímo parametr šablony

10 Šablony tříd - this  Pokud je předkem třídy závislé jméno  překladač pak neví, které identifikátory jsou zděděny nedokáže realizovat přednost děděných před vnějšími  uživatel musí pomoci konstrukcí this-> nebo kvalifikovaným jménem Pozor: Kvalifikované jméno vypíná virtuálnost funkce template class X : public T { void f() { return m(); }// globální funkce m() void g() { return this->m(); }// metoda třídy T (nebo předka) void h() { return T::m(); }// metoda třídy T (nebo předka) }

11 Ekvivalence typů template class File { //... typename P::T get() { if ( P::binary ) //... } }; struct my_policy { typedef char T; static const bool binary = false; static const bool cached = true; }; File my_file; struct my_policy2 { typedef char T; static const bool binary = false; static const bool cached = true; }; void f( File & p); f( my_file);// error  Při ekvivalenci typů rozhodují  jména tříd/struktur  obsah typových konstrukcí včetně typedef  my_policy a my_policy2 jsou různé typy, tudíž se liší i File a File

12 Využití neekvivalence typů template class Value { typename P::T v; //... }; struct mass { typedef double T; }; struct energy { typedef double T; }; Value m; Value e; e = m;// error  Silná typová kontrola  Odlišně pojmenované typy  mají stejný obsah  nejsou kompatibilní

13 Využití neekvivalence typů template class Value { double v; //... }; struct mass {}; struct energy {}; Value m; Value e; e = m;// error  Silná typová kontrola  Odlišně pojmenované typy  mají stejný obsah  nejsou kompatibilní  Lze použít i typy bez obsahu

14 Využití ekvivalence typů template class Value { double v; //... }; template< int kg1, int m1, int s1, int kg2, int m2, int s2> Value operator*( const Value & a, const Value & b); typedef Value Mass; typedef Value Velocity; typedef Value Energy; Mass m; Velocity c; Energy e; e = m * c * c; // OK  Instance šablony Value se stejnými hodnotami číselných parametrů jsou ekvivalentní  Konstrukce typedef je transparentní

15 Explicitní specializace template struct Unit { static void print( ostream & os) { os << ”kg^” << kg << ”.m^” << m << ”.s^” << s; } }; template<> struct Unit { static void print( ostream & os) { os << ”J”; } };  Generickou definici šablony lze překrýt jinou definicí pro speciální případ  Jiná šablona pak může mírně měnit své chování template ostream & operator<<( ostream & os, const Value & x) { os << x << “ “; Unit ::print( os); return os; }

16 Parciální specializace template class C { /* základní definice */ };  Parciální specializace  Deklarovanou šablonu lze pro určité kombinace parametrů předefinovat jinak, než určuje její základní definice Parciální specializace může mít stejný, menší i větší počet formálních parametrů než základní definice, jejich hodnoty se odvozují ze skutečných parametrů šablony (kterých je vždy tolik, kolik určuje základní definice) template class C { /* specializace pro dvě pole stejné velikosti */ };  Explicitní specializace template<> class C { /*... */ };  Explicitní specializace šablony není šablona  Podléhá trochu jiným (jednodušším) pravidlům Překlad se neodkládá Těla metod se nemusí psát do hlavičkových souborů

17 Parciální specializace  Typická použití parciální a explicitní specializace  Výhodnější implementace ve speciálních případech  Programátor - uživatel šablony o specializaci nemusí vědět  Příklad: Implementace vector může být jednodušší  Mírná změna rozhraní ve speciálních případech  Uživatel by měl být o specializaci informován  Příklad: vector nedovoluje vytvořit ukazatel na jeden prvek  Modifikace chování jiné šablony - traits  Specializovaná šablona je volána z jiného šablonovaného kódu  Autor volající šablony předpokládá, že ke specializaci dojde Někdy dokonce ani nedefinuje základní obsah volané šablony  Autor specializace tak upravuje chování volající šablony  Příklad: šablona basic_string volá šablonu char_traits, ve které je např. definována porovnávací funkce  Generické programování  Výpočty (s celými čísly a typovými konstrukcemi) při překladu

18 Parciální specializace - traits  Modifikace chování jiné šablony - traits  Specializovaná šablona je volána z jiného šablonovaného kódu  Autor volající šablony předpokládá, že ke specializaci dojde Někdy dokonce ani nedefinuje základní obsah volané šablony  Autor specializace tak upravuje chování volající šablony  Příklad: šablona basic_string volá šablonu char_traits, ve které je např. definována porovnávací funkce template struct char_traits; template class basic_string { /*... */ int compare( const basic_string & b) const { /*...*/ char_traits ::compare( /*... */) /*...*/ } }; template<> struct char_traits { /*... */ static int compare(const char* s1, const char* s2, size_t n) { return memcmp( s1, s2, n); } };

19 Parciální specializace  Modifikace chování jiné šablony  Specializovaná šablona je volána z jiného šablonovaného kódu  Autor volající šablony předpokládá, že ke specializaci dojde Někdy dokonce ani nedefinuje základní obsah volané šablony  Autor specializace tak upravuje chování volající šablony  Traits  Šablony, ze kterých nejsou vytvářeny objekty  Obsahují pouze:  Definice typů  Konstanty  Statické funkce  Určeny k doplnění informací o nějakém typu  Příklad: char_traits doplňuje informace o typu T, např. porovnávací funkci

20 Traits & policies  Traits  Šablony, ze kterých nejsou vytvářeny objekty  Obsahují pouze:  Definice typů  Statické funkce  Určeny k doplnění informací o nějakém typu  Příklad: char_traits doplňuje informace o typu T, např. porovnávací funkci  Policy classes  Třídy, ze kterých obvykle nejsou vytvářeny objekty  Předávány jako parametr šablonám  Defaultní hodnotou parametru často bývá šablona traits  Určeny k definování určitého chování  Příklad: Alokační strategie

21 Policy class vs. traits template void for_each( K & data) { for(... it =... ) P::f( * it); } struct policy_add { static void f(...) {... } }; my_k data; for_each ( data); template struct for_each_traits; template void for_each( K & data) { for(... it =... ) for_each_traits ::f( * it); } template<> struct for_each_traits { static void f(...) {... } }; my_k data; for_each( data);

22 Polymorfismus Kompilační a běhový

23 Polymorfismus  Polymorfismus  Stejný zdrojový kód v různých situacích dělá různé věci  Volání stejně pojmenované funkce (operátoru) volá různá těla  Šetří práci programátora (zejména na údržbě)  Kód není třeba kopírovat  Nahrazuje konstrukce if/switch  Kód je přehlednější a obsahuje méně chyb

24 Polymorfismus  Kompilační polymorfismus  Šablony, zejména:  Funktory apod.  Policy classes, traits  Běhový polymorfismus  C: ukazatele na funkce  C++: dědičnost a virtuální funkce  Běhový polymorfismus zdržuje  Používat pouze v nutném případě  Datové struktury obsahující objekty s různým chováním ("OOP")  Komunikace s neznámými partnery (komponentové systémy)  Vše ostatní lze řešit kompilačním polymorfismem

25 Polymorfismus běhový a kompilační class functor { public: virtual void f(...) = 0; }; void for_each( functor & x) { for(... it =... ) x.f( * it); // run-time binding } class functor_add : public functor { virtual void f(...) {... } }; for_each( functor_add()); template void for_each( P & x) { for(... it =... ) x.f( * it); // compile-time binding } struct functor_add { void f(...) {... } }; for_each( functor_add());

26 Kompilační polymorfismus s objektem a bez template void for_each( P & x) { for(... it =... ) x.f( it); // metoda } struct functor_add { void f(...) {... } }; for_each( functor_add()); template void for_each() { for(... it =... ) P::f( it); // statická funkce } struct policy_add { static void f(...) {... } }; for_each ();

27 Generické programování Výpočty při překladu

28 Teoretický pohled na šablony  Překladač dokáže vyhodnotit celočíselnou aritmetiku  I s rekurzivními funkcemi template struct Fib { static const int value = Fib ::value + Fib ::value; }; template<> struct Fib { static const int value = 1; }; template<> struct Fib { static const int value = 1; };  Kontrolní otázka:  Jak dlouho trvá výpočet (tj. kompilace) Fib ::value

29 Teoretický pohled na šablony  Překladač dokáže vyhodnotit celočíselnou aritmetiku  I s rekurzivními funkcemi template struct Fib { static const int value = Fib ::value + Fib ::value; }; template<> struct Fib { static const int value = 1; }; template<> struct Fib { static const int value = 1; };  Kontrolní otázka:  Jak dlouho trvá výpočet (tj. kompilace) Fib ::value  MS Visual C++ 7.1: Build Time 0:00  Kompilátory ukládají již vytvořené instanciace a nepočítají je znovu

30 Triky s šablonami  Podmíněný výraz nad typy template struct conditional { typedef A type; }; template struct conditional { typedef B type; };  conditional ::type je typ  Použití template class File { typename conditional ::type get(); /*... */ }; C++11:

31 Triky s šablonami  Porovnání typů s booleovským výstupem template struct is_same { static const bool value = false; }; template struct is_same { static const bool value = true; };  is_same ::value je konstantní výraz  Použití template class Test { static const bool very_long = is_same ::value; typedef conditional T; /*... */ }; C++11:

32 Triky s šablonami  Kompilační ověření invariantu template struct static_assert { struct type {}; }; template<> struct static_assert { }; template struct Assert { typename static_assert 0)>::type ignore_me(); }; template struct Assert { static_assert( x > 0); }; C++11: vestavěno v jazyce

33 Variadic templates

34 Šablony s proměnlivým počtem parametrů  Hlavička šablony  s proměnlivým počtem typových argumentů template class C { /*... */ };  pojmenovaný parametr zastupující seznam typů  lze i kombinovat s pevnými parametry template class D { /*... */ };  platí i pro hlavičky parciálních specializací template class C { /*... */ }; C++11

35 Šablony s proměnlivým počtem parametrů template  pojmenovaný parametr - seznam typů  lze uvnitř šablony použít v těchto konstrukcích: vždy se suffixem...  typové argumenty v použití (jiné) šablony X Y  seznam předků třídy class E : public TList...  deklarace parametrů funkce/metody/konstruktoru void f( TList... plist); double g( int a, TList... b, double c); tím vzniká pojmenovaný parametr zastupující seznam hodnot  několik dalších okrajových případů C++11

36 Šablony s proměnlivým počtem parametrů template void f( TList... plist);  pojmenovaný parametr - seznam hodnot  lze uvnitř funkce použít v těchto konstrukcích: vždy se suffixem...  hodnotové argumenty ve volání (jiné) funkce/konstruktoru g( plist...) new T( a, plist..., 7) T v( b, plist..., 8);  inicializační sekce konstruktoru E( TList... plist) : TList( plist)... { }  několik dalších případů C++11

37 Šablony s proměnlivým počtem parametrů template void f( TList... plist);  při použití je možno prvky seznamu obalit suffix... slouží jako kompilační for_each (každý) výskyt názvu seznamu je nahrazen jeho i-tým prvkem  výsledkem je  seznam typů X...> class E : public U... void f( const TList &... plist);  seznam výrazů ve volání funkce/metody/konstruktoru g( make_pair( 1, & plist)...); h( static_cast ( plist)...); i( sizeof( TList)...);  seznam inicializátorů v konstruktoru  další okrajové případy C++11

38 Generická N-tice template class tuple { public: tuple( const Types &...); /*... */ }; template class tuple_element { public: typedef /*... */ type; }; template typename tuple_element >::type & get( tuple & t);  použití tuple t1( 1, 2.3, 4); double v = get ( t1); C++11:

39 Rvalue reference Francisco de Goya. Sv. František Borgiáš u lože umírajícího (detail)

40 Rvalue reference  Rvalue reference  Nová typová konstrukce T &&  Lvalue reference  Nový název pro starou typovou konstrukci T &  Doplnění rvalue referencí do standardních knihoven  Cíl: Zrychlit běh existujících zdrojových kódů  Existující zdrojové kódy musí zůstat použitelné bez úprav  Přidány nové funkce umožňující další zrychlení  Rvalue reference mění styl programování v C++  Konstrukce dříve neefektivní se stávají použitelnými  Použití nových knihovních funkcí  Přímé použití rvalue referencí  Move semantika - umožňuje nové druhy ukazatelů

41 Rvalue reference  Rvalue reference  Jasná motivace  Odstranění nadbytečných kopírování  Srozumitelný způsob použití  Kanonické tvary tříd  Nové knihovní funkce  Složitá definice  lvalue, xvalue, prvalue, glvalue, rvalue  reference collapsing rules  Nedozírné následky  Princip navržen v r  Ještě v r odstraňována nevhodná vylepšení z návrhu normy Byla to všechna?

42 Třídy obsahující velká data Motivace pro rvalue reference

43 Matrix - Řešení bez rvalue referencí class Matrix { public: Matrix(); Matrix( const Matrix & b); const Matrix & operator=( const Matrix & b); ~Matrix(); Matrix & operator+=( const Matrix & b) const; /*... */ private: double * data_; std::size_t vsize_, hsize_; }; inline Matrix operator+( const Matrix & a, const Matrix & b) { Matrix t( a); t += b; return t; }

44 Poučení - konstruktory  Ve třídě Matrix jsou odkazy na data uložená jinde  Chování kompilátorem vytvořených metod nevyhovuje: Matrix();  Nedělá nic - je třeba alespoň vynulovat nebezpečné položky Matrix( const Matrix &);  Kopíruje ukazatel - je třeba kopírovat data, na která ukazuje Matrix & operator=( const Matrix &);  Totéž, navíc je nutné před přiřazením uklidit ~Matrix();  Nedělá nic - je třeba uklidit  Tyto metody je tedy třeba napsat vlastní

45 Poučení - operator=  Operátor přiřazení by měl udělat toto:  Zrušit starý obsah levé strany Totéž co destruktor  Okopírovat pravou stranu Totéž co konstruktor  Vrátit novou hodnotu levé strany To lze vrátit odkazem  Pozor - v některých případech to takto fungovat nebude: Matrix a; std::vector b; /*... */ a = a; b[ i] = b[ j];  Při kopírovaní pravá strana už nebude existovat

46 Poučení - operator=  Amatérské řešení operátoru přiřazení: Matrix & Matrix::operator=( const Matrix & b) { if ( this != & b ) { clean(); fill( b); } return * this; }  Konstruktory nelze přímo volat, u destruktoru to není rozumné Matrix::Matrix( const Matrix & b) { fill( b); } Matrix::~Matrix() { clean(); }

47 Poučení - operator=  Lepší řešení operátoru přiřazení: #include void Matrix::swap_with( Matrix & b) { swap( data_, b.data_); swap( xsize_, b.ysize_); swap( ysize_, b.ysize_); } Matrix & Matrix::operator=( const Matrix & b) { Matrix tmp( b); swap_with( tmp); return * this; }  Toto řešení je navíc exception-safe (později...)  Metodu swap je vhodné publikovat takto void swap( Matrix & a, Matrix & b) { a.swap_with( b); } Algoritmy (např. sort) používají swap a tento je rychlejší než generický

48 Poučení - operator+  operator + vrací novou hodnotu  v okamžiku návratu nikde jinde není  je nutné vracet hodnotou Matrix operator+( const Matrix & a, const Matrix & b)  vracení hodnotou znamená volání copy-constructoru  data se kopírují, mnohdy zbytečně Matrix a, b, c, d; a = b + c + d; před C++11

49 Poučení - operator+  Vracení hodnotou  konstruuje pomocný objekt Matrix operator+( const Matrix & x, const Matrix & y); Matrix a, b, c, d; a = b + c + d;  implementováno předáním ukazatele na volné místo void operator+( Matrix * rp, const Matrix * xp, const Matrix * yp); Matrix a, b, c, d; Matrix t1, t2; // bez volání konstruktoru operator+( & t1, & b, & c); operator+( & t2, & t1, & d); Matrix::operator=( & a, & t2); Matrix::~Matrix( & t2); Matrix::~Matrix( & t1); před C++11

50 Poučení - operator+ Matrix operator+( const Matrix & x, const Matrix & y) { Matrix t( x); t += y; return t; }  implementováno předáním ukazatele void operator+( Matrix * rp, const Matrix * xp, const Matrix * yp) { Matrix t; // bez volání konstruktoru Matrix::Matrix( & t, xp); Matrix::operator+=( & t, yp); Matrix::Matrix( rp, & t); Matrix::~Matrix( & t); } před C++11

51 Poučení - operator+ a = b + c + d; implementováno takto: Matrix t1, t2, t3, t4, t5; // bez volání konstruktoru // operator+( & t1, & b, & c); Matrix::Matrix( & t3, & b);// kopie #1 Matrix::operator+=( & t3, & c); Matrix::Matrix( & t1, & t3);// kopie #2 Matrix::~Matrix( & t3); // operator+( & t2, & t1, & d); Matrix::Matrix( & t4, & t1);// kopie #3 Matrix::operator+=( & t4, & d); Matrix::Matrix( & t2, & t4);// kopie #4 Matrix::~Matrix( & t4); // Matrix::operator=( & a, & t2); Matrix::Matrix( & t5, & t2);// kopie #5 Matrix::swap( & a, & t5); Matrix::~Matrix( & t5); Matrix::~Matrix( & t2); Matrix::~Matrix( & t1); před C++11

52 Poučení - operator+ Matrix operator+( const Matrix & x, const Matrix & y) { Matrix t( x); t += y; return t; }  šikovný překladač eliminuje proměnnou t pozorovatelná změna chování programu, povoleno normou void operator+( Matrix * rp, const Matrix * xp, const Matrix * yp) { Matrix::Matrix( rp, xp); Matrix::operator+=( & t, yp); } před C++11

53 Poučení - operator+ a = b + c + d; po eliminaci t3, t4: Matrix t1, t2, t5; // bez volání konstruktoru // operator+( & t1, & b, & c); Matrix::Matrix( & t1, & b);// kopie #1 Matrix::operator+=( & t1, & c); // operator+( & t2, & t1, & d); Matrix::Matrix( & t2, & t1);// kopie #2 Matrix::operator+=( & t2, & d); // Matrix::operator=( & a, & t2); Matrix::Matrix( & t5, & t2);// kopie #3 Matrix::swap( & a, & t5); Matrix::~Matrix( & t5); Matrix::~Matrix( & t2); Matrix::~Matrix( & t1); před C++11

54 Poučení - operator+ Matrix & Matrix::operator=( Matrix b) { swap_with( b); return * this; } chytrý programátor a chytrý překladač dokážou eliminovat t5: Matrix t1, t2; // bez volání konstruktoru // operator+( & t1, & b, & c); Matrix::Matrix( & t1, & b);// kopie #1 Matrix::operator+=( & t1, & c); // operator+( & t2, & t1, & d); Matrix::Matrix( & t2, & t1);// kopie #2 Matrix::operator+=( & t2, & d); // Matrix::operator=( & a, & t2); Matrix::swap( & a, & t2); Matrix::~Matrix( & t2); Matrix::~Matrix( & t1); kopie t1 -> t2 je stále zbytečná před C++11

55 Poučení - operator+ Matrix operator+( Matrix x, const Matrix & y) { x += y; return x; }  nepomůže, potřebuje t3 pro x z prvního volání Matrix t1, t2, t3; // bez volání konstruktoru Matrix::Matrix( & t3, & b);// kopie #1 // operator+( & t1, & t3, & c); Matrix::operator+=( & t3, & c); Matrix::Matrix( & t1, & t3);// kopie #2 // operator+( & t2, & t1, & d); Matrix::operator+=( & t1, & d); Matrix::Matrix( & t2, & t1);// kopie #3 // Matrix::operator=( & a, & t2); Matrix::swap( & a, & t2); Matrix::~Matrix( & t2); Matrix::~Matrix( & t3); Matrix::~Matrix( & t2); Matrix::~Matrix( & t1); před C++11

56 Zbytečná kopírování Řešení pomocí rvalue referencí

57 Řešení – operator=  Princip řešení - operator=  Dvě různé situace a = b;  zde se hodnota b okopírovat musí a = c + d;  výsledek operátoru + se stane novou hodnotou a  kopie principiálně není zapotřebí C++11

58 Řešení – operator=  Princip řešení - operator=  Dvě různé situace  Potřebujeme dvě implementace operatoru = a = b;  zde se hodnota b okopírovat musí T & T::operator=( const T & x);  copy-assignment  objekt b bude pouze čten a = c + d;  výsledek operátoru + se stane novou hodnotou a T & T::operator=( T && x);  move-assignment  hodnota vrácená operátorem + bude přesunuta do a  zdrojový pomocný objekt bude přesunem modifikován C++11

59 Řešení – inicializace  Totéž platí pro inicializaci  Dvě různé situace  Potřebujeme dvě implementace konstruktoru T a = b;  zde se hodnota b okopírovat musí T::T( const T & x);  copy-constructor  objekt b bude pouze čten T a = c + d;  výsledek operátoru + se stane hodnotou a T::T( T && x);  move-constructor  hodnota vrácená operátorem + bude přesunuta do a  zdrojový pomocný objekt bude přesunem modifikován C++11

60 copy/move Speciální metody tříd – C++11

61 copy/move  Speciální metody tříd  Copy constructor T( const T & x);  Move constructor T( T && x);  Copy assignment operator T & operator=( const T & x);  Move assignment operator T & operator=( T && x); C++11

62 copy/move  Překladačem definované chování (default)  Copy constructor T( const T & x) = default;  aplikuje copy constructor na složky  Move constructor T( T && x) = default;  aplikuje move constructor na složky  Copy assignment operator T & operator=( const T & x) = default;  aplikuje copy assignment operator na složky  Move assignment operator T & operator=( T && x) = default;  aplikuje move assignment operator na složky  default umožňuje vynutit defaultní chování C++11

63 copy/move  Podmínky automatického defaultu  Copy constructor/assignment operator  pokud není explicitně deklarován move constructor ani assignment operator budoucí normy pravděpodobně zakážou automatický default i v případě přítomnosti druhé copy metody nebo destruktoru  Move constructor/assignment operator  pokud není deklarována žádná ze 4 copy/move metod ani destruktor C++11

64 copy/move  Nejběžnější kombinace  Neškodná třída  Nedeklaruje žádnou copy/move metodu ani destruktor  Neobsahuje složky vyžadující zvláštní péči (ukazatele)  Složky vyžadující zvláštní péči  Překladačem generované chování (default) nevyhovuje  Bez podpory move (před C++11) T( const T & x); T & operator=( const T & x); ~T();  Plná podpora copy/move T( const T & x); T( T && x); T & operator=( const T & x); T & operator=( T && x); ~T(); C++11

65 copy/move  Další kombinace  Nekopírovatelná třída  Např. dynamicky alokované živé objekty v simulacích T( const T & x) = delete; T & operator=( const T & x) = delete;  delete zakazuje generování překladačem  Destruktor může ale nemusí být nutný  Přesouvatelná nekopírovatelná třída  Např. unikátní vlastník jiného objektu (viz std::unique_ptr ) T( T && x); T & operator=( T && x); ~T();  Pravidla jazyka zakazují generování copy metod překladačem  Destruktor typicky bývá nutný C++11

66 Třídy obsahující velká data Řešení podle C++11

67 Matrix - Řešení s podporou move class Matrix { public: Matrix(); Matrix( const Matrix & x); Matrix( Matrix && x); Matrix & operator=( const Matrix & x); Matrix & operator=( Matrix && x); ~Matrix(); /*... */ private: double * data_; std::size_t vsize_, hsize_; };  Default nevyhovuje  Copy metody musejí alokovat nová data_  Destruktor musí dealokovat data_  Move metody musejí vynulovat položku data_ ve zdroji rvalue reference neznamená, že zdroj nebude destruován  Assignment operátory musejí nějak uklidit starý obsah C++11

68 Matrix – copy metody a destruktor Matrix::Matrix( const Matrix & x) : data_( new double[ x.vsize_ * x.hsize_]), vsize_( x.vsize_), hsize_( x.hsize_) { std::copy( x.data_, x.data_ + vsize_ * hsize_, data_); } Matrix & Matrix::operator=( const Matrix & x) { Matrix t( x); t.swap_with( * this); return * this; }  Trik s eliminací proměnné t v operátoru = nelze použít Hodnotou předávaný parametr by zastínil move assignment Matrix::~Matrix() { delete data_; }  Operátor delete je odolný proti nulovému ukazateli C++11

69 Matrix – move metody Matrix::Matrix( Matrix && x) : data_( x.data_), vsize_( x.vsize_), hsize_( x.hsize_) { x.data_ = nullptr; }  x.data_ je nutné vynulovat – jinak je destruktor x dealokuje nullptr je nový preferovaný způsob zápisu nulového ukazatele Matrix & Matrix::operator=( Matrix && x) { x.swap_with( * this); return * this; }  Stará hodnota * this se dostane do x Destruktor x ji časem zneškodní C++11

70 Matrix – move metody - varianta Matrix::Matrix( Matrix && x) : data_( x.data_), vsize_( x.vsize_), hsize_( x.hsize_) { x.data_ = nullptr; }  x.data_ je nutné vynulovat – jinak je destruktor x dealokuje nullptr je nový preferovaný způsob zápisu nulového ukazatele Matrix & Matrix::operator=( Matrix && x) { Matrix t( std::move( x)); t.swap_with( * this); return * this; }  std::move vynutí move constructor pro inicializaci t  Stará hodnota * this se dostane do t Destruktor t ji včas zneškodní C++11

71 lvalue/rvalue Pravidla

72 lvalue/rvalue  U výrazu překladač zkoumá  Typ (po odstranění vnějších referencí)  Kategorie - lvalue/rvalue T x; T * p; T & lr; T && rr; T f(); T & lrf(); T && rrf();  Lvalue typu T  K objektu je možné přistupovat opakovaně (má jméno) Pozor: pojmenovaná rvalue reference je lvalue! x, * p, lr, rr, lrf()  Rvalue typu T  Tento výraz je jediná možnost přístupu k objektu Následovat bude už jen volání destruktoru f(), rrf(), std::move( x), std::move( rr) Číselné a znakové konstanty jsou rvalue  Toto je zjednodušená definice  Norma definuje lvalue, xvalue, prvalue, glvalue a rvalue C++11

73 lvalue/rvalue Pravidla předávání parametrů Kategorie a typ výrazu lvaluervalue Tconst TT Typ parametru T, const TOK T &OK (+)--- const T &OK (-)OKOK (-) T &&--- OK (+)--- const T &&--- OK (+) OK (+) má přednost před OK (-) Preferované případy, neobvyklé případy Platí i pro inicializaci proměnných a příkaz return Vše ostatní je jen volání funkcí a operátorů Zjednodušeno Skutečná pravidla jsou komplikována existencí dalších konverzí (předek-potomek, číselné konverze, uživatelské konverze, pole, funkce,...) C++11

74 Třídy obsahující velká data Operátory podle C++11

75 Matrix - Řešení s podporou move class Matrix { public: Matrix(); Matrix( const Matrix & x); Matrix( Matrix && x); Matrix & operator=( const Matrix & x); Matrix & operator=( Matrix && x); ~Matrix(); Matrix & operator+=( const Matrix & b) const; /*... */ private: double * data_; std::size_t vsize_, hsize_; }; Matrix operator+( const Matrix & a, const Matrix & b); Matrix && operator+( Matrix && a, const Matrix & b); Matrix && operator+( const Matrix & a, Matrix && b); Matrix && operator+( Matrix && a, Matrix && b); C++11

76 Matrix - Řešení s podporou move Matrix operator+( const Matrix & a, const Matrix & b) { return Matrix( a) += b; } Matrix && operator+( Matrix && a, const Matrix & b) { return std::move( a += b); } Matrix && operator+( const Matrix & a, Matrix && b) { return std::move( b += a); } Matrix && operator+( Matrix && a, Matrix && b) { return std::move( a += b); } C++11

77 lvalue/rvalue Nové rozhraní STL

78 lvalue/rvalue - STL  Nová implementace swap void swap( T & a, T & b) { T tmp( std::move( a)); a = std::move( b); b = std::move( tmp); }  Není nutné specializovat swap pro uživatelské typy  Postačí implementace move-metod  move std::move( b);  Zkratka za: static_cast ( b); C++11

79 lvalue/rvalue - STL  Copy/move insertion iterator insert( const_iterator p, const T & x); iterator insert( const_iterator p, T && x);  Druhá verze umožňuje efektivní přesun hodnoty  Týká se i push_back/push_front  Emplace template iterator emplace( const_iterator p, TList &&... plist);  Zkonstruuje objekt přímo na místě  plist se předá jako parametry konstruktoru  Existuje i emplace_back/emplace_front  Další optimalizace uvnitř implementace kontejnerů  Realokace vektoru používá move namísto copy C++11

80 lvalue/rvalue Perfect forwarding

81 template iterator emplace( const_iterator p, TList &&... plist) { void * q = /* místo pro nový prvek */; pointer r = new( q) value_type( plist...); /*... */ }  new( q) je starý trik: placement new  využívá možnost napsat si vlastní alokátor s parametrem navíc void * operator new( std::size, void * q) { return q; } C++11

82 Perfect forwarding template iterator emplace( const_iterator p, TList &&... plist) { void * q = /* místo pro nový prvek */; pointer r = new( q) value_type( plist...); /*... */ }  Jak dokáže emplace předat parametry?  Co když je skutečným parametrem lvalue?  Proč neexistuje verze s const TList &  Bylo by jich exponenciálně mnoho! C++11

83 Perfect forwarding  Trik: Skládání referencí  Jazyk definuje tato pravidla  Použijí se u šablon funkcí s parametrem typu T && template void f( T && p); X lv; f( lv);  Parametr typu T && lze navázat na lvalue typu X  Dosadí se T = X & f( std::move( lv));  Je-li skutečným parametrem rvalue typu X  Dosadí se T = X C++11

84 Perfect forwarding  Forwarding template void f( T && p) { g( p); } X lv; f( lv);  Parametr typu T && lze navázat na lvalue typu X  Dosadí se T = X & f( std::move( lv));  Je-li skutečným parametrem rvalue typu X  Dosadí se T = X  Parametr p je však lvalue  Do funkce g by měl být předán pomocí std::move C++11

85 Perfect forwarding  Perfect forwarding template void f( T && p) { g( std::forward ( p)); }  std::forward vrací T && X lv; f( lv);  T = X &  skládání referencí zajistí, že std::forward vrací X & f( std::move( lv));  T = X  std::forward vrací X &&  v tomto případě se std::forward chová jako std::move C++11

86 Perfect forwarding  Správná implementace emplace template iterator emplace( const_iterator p, TList &&... plist) { void * q = /* místo pro nový prvek */; pointer r = new( q) value_type( std::forward ( plist)...); /*... */ } C++11

87 Smart pointers

88  Chytré ukazatele  Automatické uvolnění dynamicky alokovaného objektu  Jinak se chovají jako T * Operátory *, -> Porovnání ukazatelů, nullptr  Výlučné vlastnictví  Vyžaduje move-semantiku std::unique_ptr Varianta pro pole: Operátor [] namísto * a -> std::unique_ptr  Sdílené vlastnictví  Založeno na počítání odkazů std::shared_ptr  Vedlejší odkaz, který se nepočítá (počítání zdržuje) std::weak_ptr Lze povýšit na shared_ptr, jinak je ekvivalentní s T * C++11

89 Smart pointers  Výlučné vlastnictví { std::unique_ptr q;// obsahuje nullptr { std::unique_ptr p = new T( /*...*/); q = std::move( p); } q->f(); h( * q); } // zde zaniká q i objekt T  Sdílené vlastnictví { std::shared_ptr q;// obsahuje nullptr { std::shared_ptr p = std::make_shared ( /*...*/); q = p;// zde se zvětšuje čítač p->f(); } // zde se zmenšuje čítač { std::weak_ptr r = q; h( * r); } } // zde zaniká q i objekt T C++11

90 Lambda

91 Lambda výrazy  Motivace class ftor { public: ftor(int a, int b) : a_(a),b_(b) { } bool operator()(int x) const { return x*a_

92 Lambda výrazy  Lambda výraz [ capture ]( params ) mutable -> rettype { body }  Deklaruje třídu ve tvaru class ftor { public: ftor( TList... plist) : vlist( plist)... { } rettype operator()( params ) const { body } private: TList... vlist; };  vlist je určen proměnnými použitými v body  TList je určen jejich typy a upraven podle capture  operator() je const pokud není uvedeno mutable  Lambda výraz je nahrazen vytvořením objektu ftor( vlist...) C++11

93 Lambda výrazy – návratový typ a typ funkce  Návratový typ operátoru  Explicitně definovaný návratový typ []() -> int { … }  Automaticky určen pro tělo lambda funkce ve tvaru []() { return V; }  Jinak void C++11

94 Lambda výrazy – capture  Capture [ capture ]( params ) mutable -> rettype { body }  Způsob zpřístupnění vnějších entit  Určuje typy datových položek a konstruktoru funktoru  Explicitní capture  Programátor vyjmenuje všechny vnější entity v capture [a,&b,c,&d] entity označené & předány odkazem, ostatní hodnotou  Implicitní capture  Překladač sám určí vnější entity, capture určuje způsob předání [=] [=,&b,&d] předání hodnotou, vyjmenované výjimky odkazem [&] [&,a,c] předání odkazem, vyjmenované výjimky hodnotou C++11

95 Lambda výrazy – příklad int a = 1, b = 1, c = 1; auto m1 = [a, &b, &c]() mutable { auto m2 = [a, b, &c]() mutable { std::cout << a << b << c; a = 4; b = 4; c = 4; }; a = 3; b = 3; c = 3; m2(); }; a = 2; b = 2; c = 2; m1(); std::cout << a << b << c;  Co to vypíše? C++11

96 Pokročilý pohled na kontejnery

97 Kontejnery a iterátory  Standardní knihovna definuje  Mnoho druhů kontejnerů  basic_string, vector, deque, list  array, forward_list  map, multimap, set, multiset  unordered_map, unordered_multimap, unordered_set, unordered_multiset  Odlišné přidávání/odebírání  Shodný způsob procházení  Každý kontejner definuje  Typy iterator, const_iterator  Metody begin(), end()  Iterátory jsou inspirovány ukazatelovou aritmetikou  Ukazatele do pole se chovají jako iterátory  Algoritmy mohou pracovat  s iterátory libovolného kontejneru  s ukazateli do pole  s čímkoliv, co má potřebné operátory void clr( int & x) { x = 0; } std::vector v; for_each( v.begin(), v.end(), clr); std::array a; for_each( a.begin(), a.end(), clr); int p[ N]; for_each( p, p + N, clr); C++11

98 Kategorie iterátorů  Standardní knihovna definuje  Algoritmy mohou pracovat s čímkoliv, co má potřebné operátory  Různé algoritmy mají různé nároky  Norma C++ definuje 5 kategorií iterátorů  random_access  bidirectional  forward  output  input  Kategorie určuje, které syntaktické konstrukce musí iterátor umožňovat  všechny kategorie iterator(I) /* copy constructor */ ++I, I++  output *I = x, *I++ = x  random_access, bidirectional, forward, input I1 = I2 I1 == I2, I1 != I2 I->m /* pokud existuje (*I).m */  input *I /* pouze pro čtení */  random_access, bidirectional, forward iterator() *I /* čtení i zápis */  random_access, bidirectional --I, I--  random_access I += n, I + n, n + I I -= n, I - n, I1 - I2 I[ n] I1 I2, I1 = I2

99 Použití iterátorů  Pravidla použití iterátorů  Procházený rozsah bývá zadán intervalem [b,e)  Dereferencovat e se nesmí  Přebíhat za e/před b se nesmí template void f( iterator b, iterator e) {... = * b; // chyba ! for ( iterator i = e - 1; // chyba ! i >= b; -- i) // chyba ! {... }

100 Standardem definované iterátory  Iterátory na kontejnerech  random_access category  iterátory k vector a deque  forward category  iterátory k forward_list  bidirectional category  iterátory ostatních kontejnerů  reverse_iterator  šablona pro otočení smyslu bidirectional/random_access iterátoru  kontejnery mají rbegin()/rend()  pozor: k.rbegin() != reverse_iterator( k.end())  Falešné iterátory  output category  back_inserter, front_inserter, inserter std::vector a; std::copy( x.begin(), x.end(), std::back_inserter( a));  ostream_iterator std::ostream & o =...; std::copy( x.begin(), x.end(), std::ostream_iterator( o));  input category  istream_iterator std::istream & i =...; std::copy( std::istream_iterator( i), std::istream_iterator(), std::back_inserter( a));

101 iterator_traits  Složitější algoritmy  Potřebují znát typ, na který iterátor ukazuje, apod.  Potřebují určit kategorii iterátoru  Standard definuje šablonu iterator_traits parametrizovanou typem iterátoru  Každý iterator ji musí definovat  Obvykle nepřímo definováním těchto typů uvnitř třídy iterátoru Pokud to nejde, explicitní specializací šablony template struct iterator_traits { typedef typename Iterator::difference_type difference_type; typedef typename Iterator::value_type value_type; typedef typename Iterator::pointer pointer; typedef typename Iterator::reference reference; typedef typename Iterator::iterator_category iterator_category; }; template struct iterator_traits { typedef ptrdiff_t difference_type; typedef T value_type; typedef T* pointer; typedef T& reference; typedef random_access_iterator_tag iterator_category; };

102 Exception handling Mechanismus výjimek

103 Exception-safe programming  Používat throw a catch je jednoduché  Těžší je programovat běžný kód tak, aby se choval korektně i za přítomnosti výjimek  Exception-safety  Exception-safe programming void f() { int * a = new int[ 100]; int * b = new int[ 200]; g( a, b); delete[] b; delete[] a; }  Pokud new int[ 200] způsobí výjimku, procedura zanechá naalokovaný nedostupný blok  Pokud výjimku vyvolá procedura g, zůstanou dva nedostupné bloky

104 Exception-safe programming  Používat throw a catch je jednoduché  Těžší je programovat běžný kód tak, aby se choval korektně i za přítomnosti výjimek  Exception-safety  Exception-safe programming T & operator=( const T & b) { if ( this != & b ) { delete body_; body_ = new TBody( b.length()); copy( body_, b.body_); } return * this; }  Pokud new TBody způsobí výjimku, operátor= zanechá v položce body_ původní ukazatel, který již míří na dealokovaný blok  Pokud výjimku vyvolá procedura copy, operátor zanechá třídu v neúplném stavu

105 Exception-safe programming  Pravidla vynucená jazykem  Destruktor nesmí skončit vyvoláním výjimky  Výjimka může být vyvolána uvnitř, ale musí být zachycena nejpozději uvnitř destruktoru  Zdůvodnění:  V rámci ošetření výjimek (ve fázi stack-unwinding) se volají destruktory lokálních proměnných  Výjimku zde vyvolanou nelze z technických i logických důvodů ošetřit (ztratila by se původní výjimka)  Nastane-li taková výjimka, volá se funkce terminate() a program končí

106 Exception-safe programming  Pravidla vynucená jazykem  Destruktor nesmí skončit vyvoláním výjimky  Výjimka může být vyvolána uvnitř, ale musí být zachycena nejpozději uvnitř destruktoru  Toto pravidlo jazyka sice platí pouze pro destruktory lokálních proměnných  A z jiných důvodů též pro globální proměnné  Je však vhodné je dodržovat vždy  Bezpečnostní zdůvodnění: Destruktory lokálních proměnných často volají jiné destruktory  Logické zdůvodnění: Nesmrtelné objekty nechceme

107 Exception-safe programming  Pravidla vynucená jazykem  Destruktor nesmí skončit vyvoláním výjimky  Konstruktor globálního objektu nesmí skončit vyvoláním výjimky  Zdůvodnění: Není místo, kde ji zachytit  Stane-li se to, volá se terminate() a program končí  Jiné konstruktory ale výjimky volat mohou (a bývá to vhodné)

108 Exception-safe programming  Pravidla vynucená jazykem  Destruktor nesmí skončit vyvoláním výjimky  Konstruktor globálního objektu nesmí skončit vyvoláním výjimky  Copy-constructor typu v hlavičce catch-bloku nesmí skončit vyvoláním výjimky  Zdůvodnění: Catch blok by nebylo možné vyvolat  Stane-li se to, volá se terminate() a program končí  Jiné copy-constructory ale výjimky volat mohou (a bývá to vhodné)

109 Exception-safe programming  Pravidla vynucená jazykem  Destruktor nesmí skončit vyvoláním výjimky  Konstruktor globálního objektu nesmí skončit vyvoláním výjimky  Copy-constructor typu v hlavičce catch-bloku nesmí skončit vyvoláním výjimky

110 Exception-safe programming  Poznámka: Výjimky při zpracování výjimky  Výjimka při výpočtu výrazu v throw příkaze  Tento throw příkaz nebude vyvolán  Výjimka v destruktoru při stack-unwinding  Povolena, pokud neopustí destruktor  Po zachycení a normálním ukončení destruktoru se pokračuje v původní výjimce  Výjimka uvnitř catch-bloku  Pokud je zachycena uvnitř, ošetření původní výjimky může dále pokračovat (přikazem throw bez výrazu)  Pokud není zachycena, namísto původní výjimky se pokračuje ošetřováním nové

111 Exception-safe programming  Kompilátory samy ošetřují některé výjimky  Dynamická alokace polí  Dojde-li k výjimce v konstruktoru některého prvku, úspěšně zkonstruované prvky budou destruovány Ve zpracování výjimky se poté pokračuje

112 Exception-safe programming  Kompilátory samy ošetřují některé výjimky  Dynamická alokace polí  Dojde-li k výjimce v konstruktoru některého prvku, úspěšně zkonstruované prvky budou destruovány Ve zpracování výjimky se poté pokračuje  Výjimka v konstruktoru součásti (prvku nebo předka) třídy  Sousední, již zkonstruované součásti, budou destruovány  Ve zpracování výjimky se poté pokračuje Uvnitř konstruktoru je možno výjimku zachytit speciálním try-blokem: X::X( /* formální parametry */) try : Y( /* parametry pro konstruktor součásti Y */) { /* vlastní tělo konstruktoru */ } catch ( /* parametr catch-bloku */ ) { /* ošetření výjimky v konstruktoru Y i ve vlastním těle */ }  Konstrukci objektu nelze dokončit Opuštění speciálního catch bloku znamená throw;

113 Exception-safe programming Bezpečné programování s výjimkami

114 Exception-safe programming  Definice  (Weak) exception safety  Funkce (operátor, konstruktor) je (slabě) bezpečná, pokud i v případě výjimky zanechá veškerá data v konzistentním stavu  Konzistentní stav znamená zejména: Nedostupná data byla korektně destruována a odalokována Ukazatele nemíří na odalokovaná data Platí další invarianty dané logikou aplikace

115 Exception-safe programming  Definice  (Weak) exception safety  Funkce (operátor, konstruktor) je (slabě) bezpečná, pokud i v případě výjimky zanechá veškerá data v konzistentním stavu  Konzistentní stav znamená zejména: Nedostupná data byla korektně destruována a odalokována Ukazatele nemíří na odalokovaná data Platí další invarianty dané logikou aplikace  Strong exception safety  Funkce je silně bezpečná, pokud v případě, že skončí vyvoláním výjimky, zanechá data ve stejném (pozorovatelném) stavu, ve kterém byla při jejím vyvolání Observable state - chování veřejných metod  Nazýváno též "Commit-or-rollback semantics"

116 Exception-safe programming  Poznámky  (Weak) exception safety  Tohoto stupně bezpečnosti lze většinou dosáhnout  Stačí vhodně definovat nějaký konzistentní stav, kterého lze vždy dosáhnout, a ošetřit pomocí něj všechny výjimky Konzistentním stavem může být třeba nulovost všech položek Je nutné upravit všechny funkce tak, aby je tento konzistentní stav nepřekvapil (mohou na něj ale reagovat výjimkou)  Strong exception safety  Silné bezpečnosti nemusí jít vůbec dosáhnout, pokud je rozhraní funkce navrženo špatně  Obvykle jsou problémy s funkcemi s dvojím efektem Příklad: funkce pop vracející odebranou hodnotu

117 Exception-safe programming Konstruktory a operator=

118 Exception-safe programming  copy-constructor  Silně bezpečné řešení  Pokud tělo dorazí na konec, budou datové položky korektně vyplněny  Tělo může vyvolávat výjimky V takovém případě není třeba datové položky vyplňovat Objekt nebude považován za platný a nebude používán ani destruován  Obecně je však třeba ošetřit try-blokem situace, kdy je v objektu více dynamicky alokovaných ukazatelů Vyplatí se uzavírat ukazatele do tříd po jednom Chytré ukazatele class String { /*...*/ char * str_; }; String( const String & b) { if ( b.str_ ) { str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } else { throw InvalidString(); }

119 Exception-safe programming  copy-constructor  Silně bezpečné řešení  Chytré ukazatele  std::unique_ptr #include class String { /*...*/ std::unique_ptr str_; }; String( const String & b) { if ( b.str_ ) { str_.reset( new char[ strlen( b.str_) + 1]); strcpy( str_.get(), b.str_.get()); } else { throw InvalidString(); } C++11

120 Exception-safe programming  operator=  Silně bezpečné řešení  Pokud je copy-constructor silně bezpečný  Copy-constructor naplní lokální proměnnou c kopií parametru b Zde může dojít k výjimce  Metoda swap_with vyměňuje obsah this a proměnné c Knihovní funkce swap je rychlá a (na ukazatelích) nevyvolává výjimky  Před návratem z operatoru se volá destruktor c Tím zaniká původní obsah this #include void String::swap_with( String & x) { swap( str_, x.str_); } String & String::operator=( const String & b) { String c( b); swap_with( c); return * this; }

121 Exception-safe programming  operator=  Silně bezpečné řešení  Metodu swap_with je vhodné publikovat ve formě globální funkce se standardním jménem swap  Některé algoritmy nad kontejnery obsahujícími String se tak zrychlí a stanou se bezpečnými vůči výjimkám #include void String::swap_with( String & x) { swap( str_, x.str_); } String & String::operator=( const String & b) { String c( b); swap_with( c); return * this; } void swap( String & x, String & y) { x.swap_with( y); } před C++11

122 Exception-safe programming  Move metody  Obvykle negenerují výjimky  Některé algoritmy nad kontejnery obsahujícími String se tak zrychlí a stanou se bezpečnými vůči výjimkám  Vlastní implementace globální funkce swap není zapotřebí  Knihovní implementace swap volá move metody #include String::String( String && b) : str_( std::move( b.str_)) { } String & String::operator=( String && b) { str_ = std::move( b.str_); return * this; } C++11

123 Exception-safe programming Funkce s vedlejšími efekty

124 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Slabě bezpečná implementace:  Při výjimce v konstruktoru proměnné s se nestane nic  operator delete nezpůsobuje výjimky struct Box { String v; Box * next; }; class StringStack { public: //... private: Box * top_; }; String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); Box * p = top_; String s = p->v; top_ = p->next; delete p; return s; }

125 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Slabě bezpečná implementace  Není silně bezpečná:  Funkce vrací hodnotou  Pokud při vracení dojde k výjimce v copy-constructoru, zásobník již bude zkrácen struct Box { String v; Box * next; }; class StringStack { public: //... private: Box * top_; }; String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); Box * p = top_; String s = p->v; top_ = p->next; delete p; return s; }

126 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Silně bezpečná implementace  Jak zrušíme proměnnou p, když k výjimce nedojde?  std::unique_ptr #include String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); std::unique_ptr p = top_; top_ = p->next; try { return p->v; } catch (...) { top_ = std::move( p); // toto přiřazení nuluje p throw; } // při návratu se automaticky zruší p // pokud je p nenulové

127 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Silně bezpečná implementace  Uživatel ji nedokáže použít tak, aby to bylo silně bezpečné  Vracenou hodnotu je nutné okopírovat  Nedá se poznat, zda výjimku vyvolala metoda pop nebo operator= V prvním případě je zásobník nedotčen, ale ve druhém je již zkrácen StringStack stk; String a; /*... */ try { a = stk.pop(); } catch (...) { /* ??? */ }

128 Exception-safe programming  Poučení  Funkce, která má vedlejší efekty, smí vracet hodnotou pouze typy, jejichž kopírování nevyvolává výjimky a = stk.pop();  Pokud je třeba nějakou "nebezpečnou" hodnotu vracet, musí se předávat jako výstupní parametr stk.pop( a);  Funkcí bez vedlejších efektů se problém netýká a = b + c; StringStack stk; String a; /*... */ try { a = stk.pop(); } catch (...) { /* ??? */ }  Většiny operátorů se problém netýká: buď nemají vedlejší efekty, nebo vracejí trvale existující objekt odkazem Problematické jsou postfixové ++, --

129 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Řešení A  Jako v STL  Rozdělit pop na dvě funkce  top vrací vrchol zásobníku může jej vracet odkazem nemodifikuje data  pop pouze zkracuje je silně bezpečná StringStack stk; String a; /*... */ try { a = stk.top(); } catch (...) { /* chyba kopírování nebo prázdný zásobník, proměnná a nezměněna, zásobník nedotčen */ } try { stk.pop(); } catch (...) { /* chyba zkracování, proměnná a změněna, zásobník nedotčen */ }

130 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Řešení B  Namísto vracení hodnoty funkce pop vyplňuje parametr předávaný odkazem  tím se vyloučí nutnost kombinovat volání pop s dalším kopírováním  Pro uživatele jednodušší, implementace pop je však těžší StringStack stk; String a; /*... */ try { stk.pop( a); } catch (...) { /* chyba zkracování nebo kopírování, proměnná a nezměněna, zásobník nedotčen */ }

131 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Řešení B  Lze implementovat nad řešením A #include class StringStack { public: /* A */ String & top(); void pop(); /* B */ void pop( String & out) { String & t = top(); swap( out, t); try { pop(); } catch (...) { swap( out, t); throw; } };

132 Exception specifications

133  Exception specifications  U každé funkce (operátoru, metody) je možno určit seznam výjimek, kterými smí být ukončena  Na výjimky ošetřené uvnitř funkce se specifikace nevztahuje  Pokud není specifikace uvedena, povoleny jsou všechny výjimky  Specifikace respektuje dědičnost, to jest automaticky povoluje i všechny potomky uvedené třídy void a() { /* tahle smí všechno */ } void b() throw () { /* tahle nesmí nic */ } void c() throw ( std::bad_alloc) { /* tahle smí std::bad_alloc */ } void d() throw ( std::exception, MyExc) { /* tahle smí potomky std::exception a MyExc */ } před C++11

134 Exception-safe programming  Exception specifications  U každé funkce (operátoru, metody) je možno určit, že nevyvolává výjimky void a() { /* tahle smí všechno */ } void b() noexcept { /* tahle nesmí nic */ } C++11

135 Exception-safe programming  Exception specifications  U každé funkce (operátoru, metody) je možno určit, že nevyvolává výjimky  noexcept může být podmíněno konstantním booleovským výrazem  vhodné pro šablony void a() { /* tahle smí všechno */ } void b() noexcept { /* tahle nesmí nic */ } template void swap( T & a, T & b) noexcept( is_nothrow_move_constructible ::value && is_nothrow_move_assignable ::value) { T c( std::move( a)); a = std::move( b); b = std::move( c); } C++11

136 Koenig lookup

137 iostream  Problém: namespace namespace prostor { class Souradnice { public: int x, y; }; std::ostream & operator<<( std::ostream & s, const Souradnice & a) { return s << '[' << a.x << ',' << a.y << ']'; } }; prostor::Souradnice p; std::cout << p;// správný operator<< je v namespace prostor, // který není přímo vidět

138 iostream  Problém: namespace namespace prostor { class Souradnice { public: int x, y; }; std::ostream & operator<<( std::ostream & s, const Souradnice & a) { return s << '[' << a.x << ',' << a.y << ']'; } }; prostor::Souradnice p; std::cout << p;// správný operator<< je v namespace prostor, // který není přímo vidět std::cout << std::endl;// tentýž problém je ale už tady: // tento operator<< je v namespace std

139 Koenig lookup prostor::Souradnice p; std::cout << p;// správný operator<< je v namespace prostor, // který není přímo vidět std::cout << std::endl;// tentýž problém je ale už tady: // tento operator<< je v namespace std  Oba případy jsou překládány správně  Je k tomu nutná složitá definice vyhledávání identifikátoru  tzv. Koenigovo vyhledávání  používá se, je-li význam identifikátoru závislý na parametrech volání funkce použití operátoru

140 Koenig lookup  Koenigovo vyhledávání (zjednodušeno)  Argument-dependent name lookup (ISO C++)  Pro každý skutečný parametr se z jeho typu T určí množina asociovaných namespace  Je-li T číselný, tyto množiny jsou prázdné  Je-li T union nebo enum, jeho asociovaným namespace je ten, ve kterém je definován  Je-li T ukazatel na U nebo pole U, přejímá asociované namespace od typu U  Je-li T funkce nebo ukazatel na funkci, přejímá (sjednocením) asociované namespace všech parametrů a návratového typu  Je-li T třída, asociovanými namespace jsou ty, v nichž jsou definovány tato třída a všichni její přímí i nepřímí předkové  Je-li T instancí šablony, přejímá kromě asociovaných tříd a namespace definovaných pro třídu také asociované třídy a namespace všech typových argumentů šablony

141 Koenig lookup  Koenigovo vyhledávání (zjednodušeno)  Argument-dependent name lookup (ISO C++)  Pro každý skutečný parametr se z jeho typu T určí množina asociovaných namespace  Identifikátor funkce se pak vyhledává v těchto prostorech  Globální prostor a aktuální namespace  Všechny namespace přidané direktivami using  Sjednocení asociovaných namespace všech parametrů funkce  Všechny varianty funkce nalezené v těchto namespace jsou rovnocenné  Mezi nimi se vybírá podle počtu a typu parametrů Pokud není jednoznačně určena nejlepší varianta, je to chyba  Volání v kontextu třídy: Je-li identifikátor nalezen uvnitř této třídy nebo některého předka (jako metoda), má přednost před výše uvedenými variantami (globálními funkcemi)

142

143 Klíčové vlastnosti C++

144 Koncepce jazyka C/C++  Zpřístupníme téměř vše, co dokáže „hardware“  Proto máme lokální/globální/dynamicky alokovaná data  Ukazatelová aritmetika  Nabízíme pouze to, co si programátor neudělá sám  Proto nemáme referenční semantiku a garbage collector  Nezdržujeme, pokud nemusíme  Nevirtuální metody  Minimální běhové kontroly  Neinicializovaná data  Nejsme mateřská školka  Konstrukce jsou často nebezpečné

145 Koncepce jazyka C/C++  The committee shall make no rule that prevents C++ programmers from shooting themselves in the foot.  First Amendment to the C++ Standard (urban legend)  In C++ it's harder to shoot yourself in the foot, but when you do, you blow off your whole leg.  Bjarne Stroustrup  Within C++, there is a much smaller and cleaner language struggling to get out.  Bjarne Stroustrup  Real programmers can write assembly code in any language.  Larry Wall

146 Důsledky koncepce C/C++  Existuje mnoho způsobů, jak udělat totéž  Špatně čitelné programy  Existuje mnoho způsobů, jak udělat chybu  Řešení:  Dobrovolné omezení síly jazyka  Některé možnosti se nevyužívají  Chystají se pasti na úmyslná i neúmyslná vybočení  Dodržování konvencí a vzorů  Využití moudrosti věků  Srozumitelnost rozhraní i kódu

147 Abstraktní pohled na typy

148 Dobře zapouzdřený typ  Dobře zapouzdřený typ  Nemůže „onemocnět“ při nevhodném používání Zejména z hlediska inicializace, kopírování a úklidu  Tím není vyloučena chyba jednotlivých operací dělení nulou, přístup mimo meze pole...  Číselné typy (char, int, double,...) jsou dobře zapouzdřené  Až na absenci inicializace  Ukazatele v C/C++ nejsou dobře zapouzdřené  Chybějící inicializací vznikne nedetekovatelný vadný obsah  Chybějící úklid může způsobit nedosažitelnost alokované paměti  Duplikovaná dealokace je katastrofální chyba  Kopírování ukazatelů vede k neřešitelné odpovědnosti za úklid  Ukazatele na lokální data mohou ztratit použitelnost ... a mnoho dalších potenciálních problémů

149 Dobře zapouzdřené typy  Číselné typy (char, int, double,...) jsou dobře zapouzdřené  Ukazatele v C/C++ nejsou dobře zapouzdřené  Reference v C++ nejsou dobře zapouzdřené  Odpadá problém kopírování a dealokace  Zůstává problém odkazu na zaniklá data  Kontejnery jsou tak dobře zapouzdřené, jako jejich prvky

150 Dobře zapouzdřené typy  Třída obsahující pouze dobře zapouzdřené prvky  Nemusí mít copy-constructor, operator= ani destruktor  Sama bude dobře zapouzdřená  Konstruktor může být nutný kvůli rozhraní a/nebo logice třídy  Třída obsahující prvky, které nejsou dobře zapouzdřené  Pokud má být dobře zapouzdřena, musí obsahovat: Konstruktor Copy-constructor operator= Destruktor  Prvky musí být privátní

151 Třídy z pohledu chování  Hodnotové typy  Nemají vlastní identitu (3+5 je totéž jako 2+6)  Kopírování je bez problémů  Jsou to programátorské proměnné, nikoliv matematické hodnoty Dědičnost nemá smysl  „Živé“ objekty  Mají vlastní identitu (dvě černé vrány nejsou jedna vrána)  Mění stav v průběhu života, kopírování nemívá smysl  Bývají polymorfní – dědičnost  Rozhraní  Vnější pohled na plnohodnotný objekt  Odkazy  Na rozhraní nebo plnohodnotné objekty  Nemají vlastní identitu  Mohou dovolovat kopírování – musí řešit odpovědnost za úklid  Další (funktory, singletony,...)

152 Třídy z pohledu instanciace  Neinstanciované třídy  Policy classes  Traits templates  Třídy s jednou instancí  Singletony  Třídy instanciované jako proměnné  Hodnotové typy, chytré ukazatele,...  Jednorázové třídy - Funktory, visitory apod.  Třídy reprezentující výjimky  Třídy instanciované dynamicky  Plain Old Data  Velká a sdílená data  Živé objekty - třídy s hierarchií dědičnosti Poznámka: Toto třídění nemůže být ani úplné ani jednoznačné

153 Kanonické tvary tříd

154 Singletony

155 Kanonické tvary tříd  Singletony  Veřejná statická funkce zpřístupňující instanci  Lokální statická proměnná obsahuje jedinou instanci  Privátní konstruktor a destruktor  Znemožní vytváření a likvidaci mimo předepsaná místa  Privátní hlavička copy- constructoru a operatoru=  Tělo se neimplementuje  Znemožní kopírování  Většinou nevyužívají dědičnost ani virtuální funkce /* syslog.h */ class SysLog { public: static SysLog & instance() { static SysLog inst; return inst; } void write( const std::string & s) { os_ << s; } private: std::ofstream os_; SysLog() : os_("syslog") {} ~SysLog() {} SysLog( const SysLog &); void operator=( const SysLog &); }; /* pouziti */ Syslog::instance().write( "Hi");

156 Kanonické tvary tříd Hodnotové typy

157 Kanonické tvary tříd  Hodnotové typy – jednoduché případy  Obsahují pouze dobře zapouzdřené prvky Číselné typy Jiné dobře zapouzdřené třídy  Prvky mohou být veřejné Pokud nemusí dodržovat nějaký invariant class Complex { public: double re, im; Complex(); Complex( double r, double i = 0); Complex operator-() const; Complex & operator+=( const Complex &); }; Complex operator+( const Complex &, const Complex &);

158 Kanonické tvary tříd  Hodnotové typy – jednoduché případy  Obsahují pouze dobře zapouzdřené prvky Číselné typy Jiné dobře zapouzdřené třídy  Privátní prvky mívají zpřístupňující metody set/get konvence class Complex { public: Complex(); Complex( double r, double i = 0); double get_re() const; void set_re( double x); double get_im() const; void set_im( double x); Complex operator-() const; Complex & operator+=( const Complex &); private: double re_, im_; }; Complex operator+( const Complex &, const Complex &);

159 Kanonické tvary tříd  Hodnotové typy – jednoduché případy  Obsahují pouze dobře zapouzdřené prvky  Privátní prvky mívají zpřístupňující metody  Konstruktory s parametry (někdy) Konverze, inicializace Mohou nahradit set-metody  Konstruktor bez parametrů Pokud jsou konstruktory s parametry Pokud třída obsahuje atomické číselné typy class Complex { public: Complex(); Complex( double r, double i = 0); double get_re() const; void set_re( double x); double get_im() const; void set_im( double x); Complex operator-() const; Complex & operator+=( const Complex &); private: double re_, im_; }; Complex operator+( const Complex &, const Complex &);

160 Kanonické tvary tříd  Hodnotové typy – jednoduché případy  Obsahují pouze dobře zapouzdřené prvky  Konstruktory s parametry (někdy)  Konstruktor bez parametrů  Copy-constructor, operator=, destruktor nejsou zapotřebí Vyhovuje chování generované překladačem class Complex { public: Complex(); Complex( double r, double i = 0); double get_re() const; void set_re( double x); double get_im() const; void set_im( double x); Complex operator-() const; Complex & operator+=( const Complex &); private: double re_, im_; }; Complex operator+( const Complex &, const Complex &);

161 Kanonické tvary tříd  Hodnotové typy  Unární a modifikující binární operátory jako metody  unární operátory (včetně postfixového ++) vrací hodnotou  modifikující operátory (vyjma postfixového ++) vrací odkazem * this  Nemodifikující binární operátory jako globální funkce  Vrací hodnotou  Většinou lze implementovat pomocí modifikujících binárních operátorů class Complex { public: double re, im; Complex(); Complex operator-() const; Complex & operator+=( const Complex &); }; Complex operator+( const Complex & a, const Complex & b) { Complex tmp( a); tmp += b; return tmp; }

162 Kanonické tvary tříd  Hodnotové typy  Dědičnost nemá smysl  Virtuální funkce nemají smysl class Complex { public: double re, im; Complex(); Complex operator-() const; Complex & operator+=( const Complex &); }; Complex operator+( const Complex & a, const Complex & b) { Complex tmp( a); tmp += b; return tmp; }

163 Kanonické tvary tříd  Hodnotové typy - složitější případy  Obsahují nedostatečně zapouzdřené prvky Ukazatele apod.  Konstruktor bez parametrů  Copy-constructor  operator=  Destruktor  Data a pomocné funkce privátní class Matrix { public: Matrix(); Matrix( const Matrix &); Matrix & operator=( const Matrix &); ~Matrix(); Matrix operator-() const; Matrix & operator+=( const Matrix &); friend Matrix operator+( const Matrix &, const Matrix &); private: int m_, n_; double * d_; }; Matrix operator+( const Matrix &, const Matrix &);

164 Kanonické tvary tříd  Hodnotové typy - složitější případy  Obsahují nedostatečně zapouzdřené prvky Ukazatele apod.  Více než jeden dynamicky alokovaný blok  Problémy s exception-safety  Vyplatí se samostatně zapouzdřit ukazatele do pomocných tříd class Matrix { public: Matrix(); Matrix( const Matrix &); Matrix & operator=( const Matrix &); ~Matrix(); Matrix operator-() const; Matrix & operator+=( const Matrix &); friend Matrix operator+( const Matrix &, const Matrix &); private: int m_, n_; double * d_; }; Matrix operator+( const Matrix &, const Matrix &);

165 Kanonické tvary tříd Funktory, visitory

166 Kanonické tvary tříd  Třídy instanciované jako proměnné  Funktory  Třídy určené k předávání šablonovaným funkcím  Obvykle používány pouze jednou Co nejjednodušší konstrukce  Konstruktor  S požadovanými parametry  Data  Metoda implementující požadovanou operaci  Typicky operator()  Dědičnost nemá smysl struct Printer { public: Printer( std::ostream & o) : out_( o) {} void operator()( int x) { o << x << std::endl; } private: std::ostream & out_; }; std::vector v; for_each( v.begin(), v.end(), Printer( std::cout));

167 Kanonické tvary tříd  Třídy instanciované jako proměnné  Abstraktní visitor  Sada čistě virtuálních funkcí  Virtuální destruktor Prázdné tělo  Vše veřejné  Konkrétní visitor  Obvykle používán pouze jednou Co nejjednodušší konstrukce Kontrola přístupu není nutná  Potomek abstraktní třídy  Privátní nebo veřejná data  Konstruktor (jsou-li data)  Virtuální metody implementující požadované operace class Visitor { public: virtual void visitEllipse( Ellipse *)=0; virtual void visitRectangle( Rectangle *)=0; virtual void visitLine( Line *)=0; virtual ~Visitor() {} }; class PrintVisitor : public Visitor { public: PrintVisitor( Printer * p) : p_( p) {} virtual void visitEllipse( Ellipse *); virtual void visitRectangle( Rectangle *); virtual void visitLine( Line *); private: Printer * p_; };

168 Kanonické tvary tříd Dynamicky alokovaná data

169 Kanonické tvary tříd  Třídy instanciované dynamicky  Plain Old Data  Neobsahují dynamicky alokované součásti  Datové položky obvykle veřejné  Většinou bez konstruktoru  Nedefinuje se copy- constructor, operator= ani destruktor  Bez virtuálních funkcí a dědičnosti  Někdy s obyčejnými metodami class Configuration { public: bool show_toolbar; bool show_status_bar; int max_windows; };  Dynamická alokace pouze kvůli požadované životnosti  Pro srovnání  Pojem "Plain Old Data" (POD) je definován jazykem C++ takto:  Třída nemá žádný konstruktor ani destruktor  Třída nemá virtuální funkce ani virtuální dědičnost  Všichni předkové a datové položky jsou POD  POD-třída má zjednodušená pravidla:  Může být součástí unie  Může být staticky inicializována

170 Kanonické tvary tříd  Třídy instanciované dynamicky  Velká a sdílená data  Často obsahuje definice typů  Konstruktor nebo několik konstruktorů, často s parametry  Destruktor  Privátní datové položky  Manipulace prostřednictvím metod udržujících konzistenci  Obvykle privátní neimplementovaný copy- constructor a operator=  Většinou bez virtuálních funkcí a dědičnosti  Dynamická alokace kvůli velikosti a životnosti class ColoredGraph { public: typedef int node_id; typedef int edge_id; typedef int color; ColoredGraph(); ColoredGraph( istream &); ~ColoredGraph(); node_id add_node(); edge_id add_edge( node_id, node_id, color); /*... */ private: vector nodes_; multimap edges_; map colors_; ColoredGraph(const ColoredGraph &); void operator=(const ColoredGraph &); };

171 Kanonické tvary tříd  Třídy instanciované dynamicky  Prvky datových struktur  Obsahují ukazatele  Konstruktor (inicializace ukazatelů)  Další konstruktory, jsou-li využitelné  Destruktor jen tehdy, pokud může mít dobře definované chování  Datové položky někdy veřejné  Nepolymorfní struktury: bez virtuálních funkcí a dědičnosti  Konzistentní manipulace zajištěna zapouzdřením celé struktury do jiné třídy  Dynamická alokace kvůli životnosti a složitosti struktury class TreeNode { public: TreeNode() : left( 0), right( 0) {} TreeNode( TreeNode * l, TreeNode * r) : left( l), right( r) {} ~TreeNode() { /* ??? */ } TreeNode * left, * right; }; class Tree { public: Tree() : root( 0) {} ~Tree() { /*... */ } void insert_node( /* ??? */ ); private: TreeNode * root; };

172 Kanonické tvary tříd  Třídy instanciované dynamicky  Prvky datových struktur  Datové položky někdy veřejné  Konzistentní manipulace zajištěna zapouzdřením celé struktury do jiné třídy  Problém: zpřístupnění prvků vnějšímu světu class Tree { public: Tree() : root( 0) {} ~Tree() { /*... */ } void insert_node( /* ??? */ ); /* ??? */ find_node( /*... */ ); private: class TreeNode { public: TreeNode() : left( 0), right( 0) {} ~TreeNode() { /* ??? */ } TreeNode * left, * right; }; TreeNode * root; };

173 Kanonické tvary tříd  Třídy instanciované dynamicky  Prvky datových struktur  Datové položky někdy veřejné  Konzistentní manipulace zajištěna zapouzdřením celé struktury do jiné třídy  Problém: zpřístupnění prvků vnějšímu světu  Lze řešit zapouzdřením ukazatelů do tříd – viz iterátory  Shrnuto:  NodeRef - technicky hodnotová třída s přiznanou referenční semantikou  Tree – obvykle nekopírovatelná třída  TreeNode - PlainOldData class Tree { private: class TreeNode { public: /*... */ TreeNode * left, * right; }; public: class NodeRef { public: /*... */ private: TreeNode * p; }; Tree() : root( 0) {} ~Tree() { /*... */ } NodeRef create_node( /*... */); void insert_node( NodeRef p); NodeRef find_node( /*... */ ); private: TreeNode * root; Tree( const Tree &); /*... */ };

174 Kanonické tvary tříd Třídy s dědičností

175 Kanonické tvary tříd  Třídy instanciované dynamicky  Třídy s hierarchií dědičnosti  "Objektové programování"  Abstraktní třída  Sada veřejných (čistě) virtuálních funkcí  Veřejný virtuální destruktor Prázdné tělo  Konstruktor obvykle protected Chrání proti instanciaci, pokud nejsou čistě virtuální funkce  Pro jistotu: privátní neimplementovaný copy- constructor a operator= Kopírování metodou clone  Žádné datové položky class AbstractObject { public: virtual void doit() {} virtual void showit( Where *) const = 0; virtual AbstractObject * clone() const = 0; virtual ~AbstractObject() {} protected: AbstractObject() {} private: AbstractObject( const AbstractObject&); void operator=( const AbstractObject&); };

176 Kanonické tvary tříd  Třídy instanciované dynamicky  Třídy s hierarchií dědičnosti  "Objektové programování"  Konkrétní třída  Potomek abstraktní třídy  Veřejný konstruktor  Virtuální funkce obvykle protected  Privátní data class ConcreteObject : public AbstractObject { public: ConcreteObject( /*...*/); protected: virtual void doit(); virtual void showit( Where *) const; virtual AbstractObject * clone() const; virtual ~ConcreteObject(); private: /* data */; };

177 Kanonické tvary tříd Neinstanciované třídy

178 Kanonické tvary tříd  Neinstanciované třídy  Policy classes  Traits templates  Obsahují pouze  Definice typů (typedef, případně vnořené třídy)  Definice výčtových konstant (a typů)  Statické funkce  Statická data  Obvykle vše veřejné (struct)  Nemají konstruktory ani destruktory  Obvykle nevyužívají dědičnost struct allocation_policy { static void * alloc( size_t); static void free( void *); }; template struct type_traits; template<> struct type_traits { typedef char param_type; enum { min = 0, max = 255 }; static bool less( char, char); };

179 Kanonické tvary tříd  Policy class – použití  Univerzální šablona template class BigTree { /*... */ Node * new_node() { return policy::alloc(sizeof(Node)); } };  Specifická policy class struct my_policy { static void * alloc( size_t); static void free( void *); };  Použití BigTree my_tree;  Traits template – použití  Deklarace traits template struct type_traits;  Univerzální šablona template class Stack { /*... */ void push( typename type_traits::param_t x); };  Univerzální definice traits template struct type_traits { typedef const T & param_t; };  Explicitní specializace traits template<> struct type_traits { typedef char param_t; };

180 Traits a kontejnery template struct for_each_traits; template void for_each( K & data) { typedef typename K::value_type T; for(... it =... ) for_each_traits ::f( it); } template class vector { public: typedef U value_type;... }; template<> struct for_each_traits { static void f(...) {... } }; typedef vector my_k; my_k data; for_each( data);

181 Traits a iterátory template struct for_each_traits; template void for_each( IT b, IT e) { typedef typename iterator_traits ::value_type T; for(... it =... ) for_each_traits ::f( it); } template<> struct for_each_traits { static void f(...) {... } }; typedef int my_k[ N]; my_k data; for_each( data, data + N); // IT = int*

182 Lambda (C++0x) template void modify( const P & x) { for(... it =... ) * it = x.operator()( * it); } struct functor_twice { int operator()( int x) const { return 2 * x; } }; modify( functor_twice());for_each( []( int x){ return 2 * x });

183 Lambda (C++0x vs. boost) for_each( []( int x){ return 2 * x });for_each( constant( 2) * _1);

184 Lambda (boost) struct functor_1 { template T operator()( T x) const { return x; } }; extern functor_1 _1; template struct functor_const { functor_const( T c) : c_( c) {} T operator()( T x) const { return c_; } T c_; }; template functor_const constant( T c) { return functor_const ( c); } template struct functor_mul { functor_mul( F1 f1, F2 f2) : f1_( f1), f2_( f2) {} T operator()( T x) const { return c_; } F1 f1_; F2 f2_; }; template functor_mul operator+ ( F1 f1, F1 f2) { return functor_const ( f1, f2); } for_each( constant( 2) * _1);

185 Lambda (boost) - problémy for_each( std::cout << "x=" << _1);for_each( constant( 2) * _1);

186 Šablony Templates

187 Šablony tříd - instanciace  Instanciace šablony: Šablonu lze použít jako typ pouze s explicitním uvedením skutečných parametrů odpovídajících druhů:  celé číslo: celočíselný konstantní výraz  ukazatel: adresa globální nebo statické proměnné či funkce kompatibilního typu  libovolný typ – jméno typu či typová konstrukce (včetně jiné instanciované šablony)  Užití instanciované šablony:  Instanciované šablony jsou stejného typu, pokud jsou stejného jména a jejich skutečné parametry obsahují stejné hodnoty konstantních výrazů, adresy stejných proměnných či funkcí a stejné typy

188 Šablony tříd – pravidla použití  Uvnitř těla šablony (nebo jako její předky) je možno užívat libovolné typy včetně:  Instancí jiných šablon  Téže šablony s jinými argumenty  Téže šablony se stejnými argumenty V tomto případě se argumenty mohou, ale nemusí opisovat  Ekvivalentní varianty šablony s copy-constructorem: template class X { X( const X &); }; template class X { X( const X &); }; Některé překladače připouštějí i tuto variantu template class X { X ( const X &); };

189 Šablony tříd – pravidla použití  Metody šablon mohou mít těla uvnitř třídy nebo vně  Vně uvedená těla metod musejí být připojena k šabloně takto: template void X ::f( int a, int b) { /*... */ }  V kvalifikovaném jméně metody je nutné uvést patřičný seznam argumentů, tj. X ::f a nikoliv X::f  Těla metod musejí být viditelná z každého místa, kde jsou pro nějakou instanci šablony volána  Musejí tedy typicky být v témže hlavičkovém souboru jako sama šablona.  Uvedení těla metody vně třídy tedy u šablon typicky nic nepřináší, může být však vynuceno rekurzivními odkazy mezi šablonami apod.

190 Šablony tříd – pravidla použití  Šablona třídy se překládá až v okamžiku instanciace, tj. použití s konkrétními parametry  Překladač instanciuje (tj. překládá) pouze ty metody, které jsou zapotřebí (tj. jsou volány nebo jsou virtuální)  Některá těla metod tedy nemusí být pro některé případy parametrů přeložitelná template class Proxy { public: void pop_front() { c->pop_front(); }// jen pro list/deque /*... */ private: Container * c; };  Explicitní instanciace  Překladač je možné donutit ke kompletní instanciaci šablony template class Array ;

191 Šablony tříd – triky  Dopředná deklarace šablony template class X; /*... zde může být použito X s jakýmikoliv argumenty U pouze v kontextech, kde kompilátor nepotřebuje znát tělo šablony... */ template class X { /*... */ };

192 Šablony funkcí  Pod stejným identifikátorem může být deklarováno několik různých šablon funkce a navíc několik obyčejných funkcí.  Obyčejné funkce mají přednost před generickými template T max( T a, T b) { return a < b ? b : a; }; char * max( char * a, char * b) { return strcmp( a, b) < 0 ? b : a; }; template T max( Array a) { /*... */ }  Příklad ze standardních knihoven: template void swap( T & a, T & b) { T tmp(a); a = b; b = tmp; }; K tomu řada chytřejších implementací swap pro některé třídy

193 Šablony – pokročilé konstrukce jazyka  Parciální specializace  Deklarovanou šablonu lze pro určité kombinace parametrů předefinovat jinak, než určuje její základní definice template class Array { /* specializace pro pole typu bool */ };  Krajním případem parciální specializace je explicitní specializace  Explicitní specializace template<> class Array { /*... */ };  U šablon funkcí nahrazena obyčejnou funkcí  Explicitní instanciace  Překladač je možné donutit ke kompletní instanciaci šablony template class Array ;

194 Parciální specializace  Parciální specializace  Deklarovanou šablonu lze pro určité kombinace parametrů předefinovat jinak, než určuje její základní definice Parciálně specializovat lze šablony funkcí, celé šablony tříd i jednotlivě těla jejich metod Obsah specializace šablony třídy (teoreticky) nemusí nijak souviset se základní definicí - může mít zcela jiné položky, předky apod. Základní definice dokonce nemusí vůbec existovat (ale musí být deklarována) template class C; // základní deklarace template class C { // specializace bool cmp( P *, Q *); }; template class C : public Z { // jin á specializace bool set( Z &); }; template class C { // základní definice X add( Y); };

195 Parciální specializace  Parciální specializace  Deklarovanou šablonu lze pro určité kombinace parametrů předefinovat jinak, než určuje její základní definice Parciální specializace může mít stejný, menší i větší počet formálních parametrů než základní definice, jejich hodnoty se odvozují ze skutečných parametrů šablony (kterých je vždy tolik, kolik určuje základní definice) template class C { /* specializace pro dvě pole stejné velikosti */ };

196 Teoretický pohled na šablony  Příklad template struct Fact { enum { value = Fact ::value * N }; }; template<> struct Fact { enum { value = 1 }; };

197 Teoretický pohled na šablony  Příklad template struct Fact { enum { value = Fact ::value * N }; }; template<> struct Fact { enum { value = 1 }; };  Použití enum { N = 10 }; int permutations[ Fact ::value];

198 Teoretický pohled na šablony  Příklad template struct Fact { enum { value = Fact ::value * N }; }; template<> struct Fact { enum { value = 1 }; };  Kontrolní otázka:  Kolik je Fact ::value

199 Teoretický pohled na šablony  Příklad template struct Fact { enum { value = Fact ::value * N }; }; template<> struct Fact { enum { value = 1 }; };  Kontrolní otázka:  Kolik je Fact ::value  MS Visual C++ 7.1: fatal error C1202: recursive type or function dependency context too complex  Řetěz instanciací Fact, Fact, Fact,... způsobí přetečení tabulek kompilátoru

200 Šablony tříd – explicitní instanciace  Je-li předem známa množina typů (formálních parametrů), pro něž se bude šablona instanciovat, není nutno publikovat těla metod ve zdrojové formě a je možné je předkompilovat do knihovny  Veřejný hlavičkový soubor X.h – hlavička třídy template class X { /*... */ void f(/*...*/); };  Nepublikovaný hlavičkový soubor XBody.h  Generická těla metod #include "X.h" template void X ::f(/*...*/) { /*...*/ }  Knihovní modul XBodyInt.cpp  Instanciace pro typ int #include "XBody.h" template X ;

201 Teoretický pohled na šablony  Šablona třídy je kompilátorem vyhodnocovaná funkce  f : T i  T  T je množina všech typů zkonstruovatelných v jazyce C

202 Teoretický pohled na šablony  Šablona třídy je kompilátorem vyhodnocovaná funkce  f : T i  T  T je množina všech typů zkonstruovatelných v jazyce C  f : T i × K j  T  šablona s celočíselnými parametry  K je množina všech celočíselných konstant

203 Teoretický pohled na šablony  Šablona třídy je kompilátorem vyhodnocovaná funkce  f : T i  T  T je množina všech typů zkonstruovatelných v jazyce C  f : T i × K j  T  šablona s celočíselnými parametry  K je množina všech celočíselných konstant  f : T i × K j  T m × K n  výsledná třída může obsahovat typy a konstanty

204 Teoretický pohled na šablony  Šablona třídy je kompilátorem vyhodnocovaná funkce  f : T i × K j  T m × K n  Taková funkce může být definována  Jedním předpisem  Základní šablonou  Po částech  Parciálními specializacemi šablony  V jednotlivých bodech  Explicitními specializacemi šablony

205 Exception handling Mechanismus výjimek

206 Exception-safe programming  Používat throw a catch je jednoduché  Těžší je programovat běžný kód tak, aby se choval korektně i za přítomnosti výjimek  Exception-safety  Exception-safe programming void f() { int * a = new int[ 100]; int * b = new int[ 200]; g( a, b); delete[] b; delete[] a; }  Pokud new int[ 200] způsobí výjimku, procedura zanechá naalokovaný nedostupný blok  Pokud výjimku vyvolá procedura g, zůstanou dva nedostupné bloky

207 Exception-safe programming  Používat throw a catch je jednoduché  Těžší je programovat běžný kód tak, aby se choval korektně i za přítomnosti výjimek  Exception-safety  Exception-safe programming T & operator=( const T & b) { if ( this != & b ) { delete body_; body_ = new TBody( b.length()); copy( body_, b.body_); } return * this; }  Pokud new TBody způsobí výjimku, operátor= zanechá v položce body_ původní ukazatel, který již míří na dealokovaný blok  Pokud výjimku vyvolá procedura copy, operátor zanechá třídu v neúplném stavu

208 Exception-safe programming  Pravidla vynucená jazykem  Destruktor nesmí skončit vyvoláním výjimky  Výjimka může být vyvolána uvnitř, ale musí být zachycena nejpozději uvnitř destruktoru  Zdůvodnění:  V rámci ošetření výjimek (ve fázi stack-unwinding) se volají destruktory lokálních proměnných  Výjimku zde vyvolanou nelze z technických i logických důvodů ošetřit (ztratila by se původní výjimka)  Nastane-li taková výjimka, volá se funkce terminate() a program končí

209 Exception-safe programming  Pravidla vynucená jazykem  Destruktor nesmí skončit vyvoláním výjimky  Výjimka může být vyvolána uvnitř, ale musí být zachycena nejpozději uvnitř destruktoru  Toto pravidlo jazyka sice platí pouze pro destruktory lokálních proměnných  A z jiných důvodů též pro globální proměnné  Je však vhodné je dodržovat vždy  Bezpečnostní zdůvodnění: Destruktory lokálních proměnných často volají jiné destruktory  Logické zdůvodnění: Nesmrtelné objekty nechceme

210 Exception-safe programming  Pravidla vynucená jazykem  Destruktor nesmí skončit vyvoláním výjimky  Konstruktor globálního objektu nesmí skončit vyvoláním výjimky  Zdůvodnění: Není místo, kde ji zachytit  Stane-li se to, volá se terminate() a program končí  Jiné konstruktory ale výjimky volat mohou (a bývá to vhodné)

211 Exception-safe programming  Pravidla vynucená jazykem  Destruktor nesmí skončit vyvoláním výjimky  Konstruktor globálního objektu nesmí skončit vyvoláním výjimky  Copy-constructor typu v hlavičce catch-bloku nesmí skončit vyvoláním výjimky  Zdůvodnění: Catch blok by nebylo možné vyvolat  Stane-li se to, volá se terminate() a program končí  Jiné copy-constructory ale výjimky volat mohou (a bývá to vhodné)

212 Exception-safe programming  Pravidla vynucená jazykem  Destruktor nesmí skončit vyvoláním výjimky  Konstruktor globálního objektu nesmí skončit vyvoláním výjimky  Copy-constructor typu v hlavičce catch-bloku nesmí skončit vyvoláním výjimky

213 Exception-safe programming  Poznámka: Výjimky při zpracování výjimky  Výjimka při výpočtu výrazu v throw příkaze  Tento throw příkaz nebude vyvolán  Výjimka v destruktoru při stack-unwinding  Povolena, pokud neopustí destruktor  Po zachycení a normálním ukončení destruktoru se pokračuje v původní výjimce  Výjimka uvnitř catch-bloku  Pokud je zachycena uvnitř, ošetření původní výjimky může dále pokračovat (přikazem throw bez výrazu)  Pokud není zachycena, namísto původní výjimky se pokračuje ošetřováním nové

214 Exception-safe programming  Kompilátory samy ošetřují některé výjimky  Dynamická alokace polí  Dojde-li k výjimce v konstruktoru některého prvku, úspěšně zkonstruované prvky budou destruovány Ve zpracování výjimky se poté pokračuje

215 Exception-safe programming  Kompilátory samy ošetřují některé výjimky  Dynamická alokace polí  Dojde-li k výjimce v konstruktoru některého prvku, úspěšně zkonstruované prvky budou destruovány Ve zpracování výjimky se poté pokračuje  Výjimka v konstruktoru součásti (prvku nebo předka) třídy  Sousední, již zkonstruované součásti, budou destruovány  Ve zpracování výjimky se poté pokračuje Uvnitř konstruktoru je možno výjimku zachytit speciálním try-blokem: X::X( /* formální parametry */) try : Y( /* parametry pro konstruktor součásti Y */) { /* vlastní tělo konstruktoru */ } catch ( /* parametr catch-bloku */ ) { /* ošetření výjimky v konstruktoru Y i ve vlastním těle */ }  Konstrukci objektu nelze dokončit Opuštění speciálního catch bloku znamená throw;

216 Exception-safe programming Bezpečné programování s výjimkami

217 Exception-safe programming  Definice  (Weak) exception safety  Funkce (operátor, konstruktor) je (slabě) bezpečná, pokud i v případě výjimky zanechá veškerá data v konzistentním stavu  Konzistentní stav znamená zejména: Nedostupná data byla korektně destruována a odalokována Ukazatele nemíří na odalokovaná data Platí další invarianty dané logikou aplikace

218 Exception-safe programming  Definice  (Weak) exception safety  Funkce (operátor, konstruktor) je (slabě) bezpečná, pokud i v případě výjimky zanechá veškerá data v konzistentním stavu  Konzistentní stav znamená zejména: Nedostupná data byla korektně destruována a odalokována Ukazatele nemíří na odalokovaná data Platí další invarianty dané logikou aplikace  Strong exception safety  Funkce je silně bezpečná, pokud v případě, že skončí vyvoláním výjimky, zanechá data ve stejném (pozorovatelném) stavu, ve kterém byla při jejím vyvolání Observable state - chování veřejných metod  Nazýváno též "Commit-or-rollback semantics"

219 Exception-safe programming  Poznámky  (Weak) exception safety  Tohoto stupně bezpečnosti lze většinou dosáhnout  Stačí vhodně definovat nějaký konzistentní stav, kterého lze vždy dosáhnout, a ošetřit pomocí něj všechny výjimky Konzistentním stavem může být třeba nulovost všech položek Je nutné upravit všechny funkce tak, aby je tento konzistentní stav nepřekvapil (mohou na něj ale reagovat výjimkou)  Strong exception safety  Silné bezpečnosti nemusí jít vůbec dosáhnout, pokud je rozhraní funkce navrženo špatně  Obvykle jsou problémy s funkcemi s dvojím efektem Příklad: funkce pop vracející odebranou hodnotu

220 Exception-safe programming  Příklad: String č. 2  operator=  Nebezpečná implementace:  Pokud new char způsobí výjimku, operátor= zanechá v položce str_ původní ukazatel, který již míří na dealokovaný blok class String { public: //... private: char * str_; }; String & String::operator=( const String & b) { if ( this != & b ) { delete[] str_; str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } return * this; }

221 Exception-safe programming  Příklad: String č. 2  operator=  Nebezpečná implementace:  Pokud new char způsobí výjimku, operátor= zanechá v položce str_ původní ukazatel, který již míří na dealokovaný blok  K jiné výjimce zde dojít nemůže: std::operator delete výjimky nikdy nevyvolává char je vestavěný typ a nemá tedy konstruktory které by mohly výjimku vyvolávat strlen a strcpy jsou C-funkce Parametry a návratová hodnota se předávají odkazem class String { public: //... private: char * str_; }; String & String::operator=( const String & b) { if ( this != & b ) { delete[] str_; str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } return * this; }

222 Exception-safe programming  Příklad: String č. 2  operator=  Naivní pokus o opravu:  Pokud new char způsobí výjimku, ošetří se  Objekt se uvede do konzistentního stavu  Výjimka se propaguje dál - ven z funkce  Problém:  V catch bloku teoreticky může vzniknout nová výjimka String & String::operator=( const String & b) { if ( this != & b ) { delete[] str_; try { str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } catch (... ) { str_ = new char[ 1]; * str_ = 0; throw; } return * this; }

223 Exception-safe programming  Příklad: String č. 2  operator=  Lepší pokus o opravu:  Pokud new char způsobí výjimku, ošetří se  Je nutné pozměnit invariant třídy String:  Položka str_ nyní smí obsahovat nulový ukazatel String & String::operator=( const String & b) { if ( this != & b ) { delete[] str_; try { str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } catch (... ) { str_ = 0; throw; } return * this; }

224 Exception-safe programming  Příklad: String č. 2  operator=  Lepší pokus o opravu:  Pokud new char způsobí výjimku, ošetří se  Je nutné pozměnit invariant třídy String:  Položka str_ nyní smí obsahovat nulový ukazatel  Takový exemplář String je považován za konzistentní  Konzistentnost nemusí znamenat, že to je z uživatelského pohledu platná hodnota  Může být považována i za chybovou a každá operace s takovou hodnotou může vyvolávat výjimku String & String::operator=( const String & b) { if ( this != & b ) { delete[] str_; try { str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } catch (... ) { str_ = 0; throw; } return * this; }

225 Exception-safe programming  Příklad: String č. 2  operator=  Ekvivalentní řešení:  Nulovat str_ po delete  Pokud new způsobí výjimku, v str_ zůstane nulový ukazatel String & String::operator=( const String & b) { if ( this != & b ) { delete[] str_; str_ = 0; str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } return * this; }

226 Exception-safe programming  Příklad: String č. 2  operator=  Chyba: změnili jsme invariant  str_ nyní může být nulové  delete _str je v pořádku operator delete je vždy proti nulovému ukazateli ošetřen (nedělá nic)  strlen a strcpy ale fungovat nebudou String & String::operator=( const String & b) { if ( this != & b ) { delete[] str_; str_ = 0; str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } return * this; }

227 Exception-safe programming  Příklad: String č. 2  operator=  Opraveno String & String::operator=( const String & b) { if ( this != & b ) { delete[] str_; str_ = 0; if ( b.str_ ) { str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } return * this; }

228 Exception-safe programming  Příklad: String č. 2  operator=  Vylepšení:  operator= může vyvolávat výjimku, pokud se přiřazuje neplatná hodnota  Tato výjimka může být definována např. takto: #include class InvalidString : public std::exception { virtual const char * what() const { return "Invalid string"; } String & String::operator=( const String & b) { if ( this != & b ) { delete[] str_; str_ = 0; if ( b.str_ ) { str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } else { throw InvalidString(); } return * this; }

229 Exception-safe programming  Příklad: String č. 2  operator=  Toto řešení je slabě bezpečné  Silně bezpečné ale není:  Pokud dojde k výjimce, nezachovává se původní stav dat  To bude pro uživatele nepříjemné: String x, y; /*... */ try { x = y + x; } catch (...) { /*... */ }  Uživatel nedokáže rozlišit mezi výjimkami v operátorech + a =  Náš operator= ale v případě výjimky ztratí hodnotu x String & String::operator=( const String & b) { if ( this != & b ) { delete[] str_; str_ = 0; if ( b.str_ ) { str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } else { throw InvalidString(); } return * this; }

230 Exception-safe programming  Příklad: String č. 2  operator=  Silně bezpečné řešení  Pokud dojde k výjimce v new, nestane se nic  Ani před throw nenastane žádná změna String & String::operator=( const String & b) { if ( this != & b ) { if ( b.str_ ) { char * aux = new char[ strlen( b.str_) + 1]; strcpy( aux, b.str_); delete[] str_; str_ = aux; } else { throw InvalidString(); } return * this; }

231 Exception-safe programming  Příklad: String č. 2  operator=  Silně bezpečné řešení  Pozorování:  Toto řešení je "shodou okolností" imunní proti this == & b String & String::operator=( const String & b) { if ( this != & b ) { if ( b.str_ ) { char * aux = new char[ strlen( b.str_) + 1]; strcpy( aux, b.str_); delete[] str_; str_ = aux; } else { throw InvalidString(); } return * this; }

232 Exception-safe programming  Příklad: String č. 2  operator=  Silně bezpečné řešení  Pozorování:  Toto řešení je "shodou okolností" imunní proti this == & b  Test je možno zrušit String & String::operator=( const String & b) { if ( b.str_ ) { char * aux = new char[ strlen( b.str_) + 1]; strcpy( aux, b.str_); delete[] str_; str_ = aux; } else { throw InvalidString(); } return * this; }

233 Exception-safe programming  Příklad: String č. 2  operator=  Silně bezpečné řešení  Pokud je copy-constructor silně bezpečný  Standardní řešení:  Copy-constructor naplní lokální proměnnou c kopií parametru b Zde může dojít k výjimce  Metoda swap vyměňuje obsah this a proměnné c Metoda swap je rychlá a nevyvolává výjimky  Před návratem z operatoru se volá destruktor c Tím zaniká původní obsah this void String::swap( String & x) { char * aux = str_; str_ = x.str_; x.str_ = aux; } String & String::operator=( const String & b) { String c( b); swap( c); return * this; }

234 Exception-safe programming  Příklad: String č. 2  operator=  Silně bezpečné řešení  Metodu swap je vhodné publikovat ve formě globální funkce  Některé algoritmy nad kontejnery obsahujícími String se tak zrychlí a stanou se bezpečnými vůči výjimkám void String::swap( String & x) { char * aux = str_; str_ = x.str_; x.str_ = aux; } String & String::operator=( const String & b) { String c( b); swap( c); return * this; } void swap( String & x, String & y) { x.swap( y); }

235 Exception-safe programming  Příklad: String č. 2  operator=  Silně bezpečné řešení  Metodu swap je vhodné publikovat ve formě globální funkce  Některé algoritmy nad kontejnery obsahujícími String se tak zrychlí a stanou se bezpečnými vůči výjimkám  Sama metoda swap může využívat šablonu swap pro typ char * #include void String::swap( String & x) { swap( str_, x.str_); } String & String::operator=( const String & b) { String c( b); swap( c); return * this; } void swap( String & x, String & y) { x.swap( y); }

236 Exception-safe programming  Příklad: String č. 2  copy-constructor  Silně bezpečné řešení  Pokud tělo dorazí na konec, budou datové položky korektně vyplněny  Tělo může vyvolávat výjimky V takovém případě není třeba datové položky vyplňovat Objekt nebude považován za platný a nebude používán ani destruován  Obecně je však třeba ošetřit try-blokem situace, kdy je v objektu více dynamicky alokovaných ukazatelů String( const String & b) { if ( b.str_ ) { str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } else { throw InvalidString(); }

237 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Slabě bezpečná implementace:  Při výjimce v konstruktoru proměnné s se nestane nic  operator delete nezpůsobuje výjimky struct Box { String v; Box * next; }; class StringStack { public: //... private: Box * top_; }; String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); Box * p = top_; String s = p->v; top_ = p->next; delete p; return s; }

238 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Slabě bezpečná implementace  Není silně bezpečná:  Funkce vrací hodnotou  Pokud při vracení dojde k výjimce v copy-constructoru, zásobník již bude zkrácen struct Box { String v; Box * next; }; class StringStack { public: //... private: Box * top_; }; String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); Box * p = top_; String s = p->v; top_ = p->next; delete p; return s; }

239 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Slabě bezpečná implementace  Není silně bezpečná:  Funkce vrací hodnotou  Pokud při vracení dojde k výjimce v copy-constructoru, zásobník již bude zkrácen  Tuto výjimku lze ošetřit try-blokem okolo příkazu return Uvést zásobník do původního stavu Ale: co když se uvedení do původního stavu nezdaří? String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); Box * p = top_; String s = p->v; top_ = p->next; delete p; try { return s; } catch (...) { p = new Box; p->v = s; p->next = top_; top_ = p; throw; }

240 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Nefunkční implementace  Není silně bezpečná:  Funkce vrací hodnotou  Pokud při vracení dojde k výjimce v copy-constructoru, zásobník již bude zkrácen  Tuto výjimku lze ošetřit try-blokem okolo příkazu return  Dokážeme udělat obnovení původního stavu bez nebezpečí výjimky Ale: jak zrušíme proměnnou p, když k výjimce nedojde? String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); Box * p = top_; String s = p->v; top_ = p->next; // tady bylo delete p; try { return s; // tady by delete p; nepomohlo } catch (...) { top_ = p; throw; }

241 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Silně bezpečná implementace  Jak zrušíme proměnnou p, když k výjimce nedojde?  std::auto_ptr  "chytrý" ukazatel na T, který se chová jako "jediný vlastník objektu": po zkopírování se vynuluje při zániku volá delete  Pozor: auto_ptr má nestandardní copy-constructor a operator= modifikují svůj parametr pro auto_ptr nefungují kontejnery apod. #include String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); std::auto_ptr p = top_; top_ = p->next; try { return p->v; } catch (...) { top_ = p; // toto přiřazení nuluje p throw; } // při návratu se automaticky zruší * p // pokud je p nenulové

242 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Silně bezpečná implementace  Uživatel ji nedokáže použít tak, aby to bylo silně bezpečné  Vracenou hodnotu je nutné okopírovat  Nedá se poznat, zda výjimku vyvolala metoda pop nebo operator= V prvním případě je zásobník nedotčen, ale ve druhém je již zkrácen StringStack stk; String a; /*... */ try { a = stk.pop(); } catch (...) { /* ??? */ }

243 Exception-safe programming  Poučení  Funkce, která má vedlejší efekty, smí vracet hodnotou pouze typy, jejichž kopírování nevyvolává výjimky a = stk.pop();  Pokud je třeba nějakou "nebezpečnou" hodnotu vracet, musí se předávat jako výstupní parametr stk.pop( a);  Funkcí bez vedlejších efektů se problém netýká a = b + c; StringStack stk; String a; /*... */ try { a = stk.pop(); } catch (...) { /* ??? */ }  Většiny operátorů se problém netýká: buď nemají vedlejší efekty, nebo vracejí trvale existující objekt odkazem Problematické jsou postfixové ++, --

244 Exception-safe programming  Použití  Nebezpečí nastává všude, kde je změna pozorovatelného stavu následovaná voláním funkce (operátoru), které může vyvolat výjimku data_ =...; f(...);  Problém lze někdy odstranit obrácením pořadí Pomocná proměnná není součástí pozorovatelného stavu T tmp =...; f(...); data_ =...;  Pokud pořadí nelze vyměnit, je v případě výjimky nutný rollback T old = data_; data_ =...; try { f(...); } catch(...) { data_ = old; }  Problém 1: Některé operace rollbackovat nelze  Problém 2: Rollback nemusí být spolehlivý

245 Exception-safe programming  Typické použití  Nebezpečný kód change_1(...); change_2(...); change_3(...); change_4(...); change_1(...); try { change_2(...); try { change_3(...); try { change_4(...); } catch(...) { rollback_change_3(...); } catch(...) { rollback_change_2(...); } catch(...) { rollback_change_1(...); }

246 Exception-safe programming  Použití  Operace, která rollbackovat nejde, musí být zařazena jako poslední  Pokud to není možné, je třeba rollback simulovat dodatečnými daty f1_.get( tmp); } try { f2_.put( tmp); } catch(...) { f1_.unget( tmp); // ??? } if ( additional_data_valid_ ) { tmp = additional_data_; additional_data_valid_ = false; } else { f1_.get( tmp); } try { f2_.put( tmp); } catch(...) { additional_data_ = tmp; additional_data_valid_ = true; }

247 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Řešení A  Jako v STL  Rozdělit pop na dvě funkce  top vrací vrchol zásobníku může jej vracet odkazem nemodifikuje data  pop pouze zkracuje je silně bezpečná StringStack stk; String a; /*... */ try { a = stk.top(); } catch (...) { /* chyba kopírování nebo prázdný zásobník, proměnná a nezměněna, zásobník nedotčen */ } try { stk.pop(); } catch (...) { /* chyba zkracování, proměnná a změněna, zásobník nedotčen */ }

248 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Řešení B  Namísto vracení hodnoty funkce pop vyplňuje parametr předávaný odkazem  tím se vyloučí nutnost kombinovat volání pop s dalším kopírováním  Pro uživatele jednodušší, implementace pop je však těžší StringStack stk; String a; /*... */ try { stk.pop( a); } catch (...) { /* chyba zkracování nebo kopírování, proměnná a nezměněna, zásobník nedotčen */ }

249 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován seznamem  Řešení B  Lze implementovat nad řešením A #include class StringStack { public: /* A */ String & top(); void pop(); /* B */ void pop( String & out) { String & t = top(); swap( out, t); try { pop(); } catch (...) { swap( out, t); throw; } };

250 Exception specifications  Exception specifications  U každé funkce (operátoru, metody) je možno určit seznam výjimek, kterými smí být ukončena  Na výjimky ošetřené uvnitř funkce se specifikace nevztahuje  Pokud není specifikace uvedena, povoleny jsou všechny výjimky  Specifikace respektuje dědičnost, to jest automaticky povoluje i všechny potomky uvedené třídy void a() { /* tahle smí všechno */ } void b() throw () { /* tahle nesmí nic */ } void c() throw ( std::bad_alloc) { /* tahle smí std::bad_alloc */ } void d() throw ( std::exception, MyExc) { /* tahle smí potomky std::exception a MyExc */ }

251 Exception specifications  Exception specifications  Kompilátor zajistí, že nepovolená výjimka neopustí funkci:  Pokud by se tak mělo stát, volá se unexpected() unexpected() smí vyvolat "náhradní" výjimku  Pokud ani náhradní výjimka není povolena, zkusí se vyvolat std::bad_exception  Pokud ani std::bad_exception není povoleno, volá se terminate() a program končí

252 Exception specifications  Exception specifications  Kompilátor zajistí, že nepovolená výjimka neopustí funkci  Toto je běhová kontrola  Kompilátor smí vydávat nejvýše varování  Funkce smí volat jinou, která by mohla vyvolat nepovolenou výjimku (ale nemusí) void f() throw ( std::exception) { } void g() throw () { f(); /* tohle se smí */ }

253 Exception specifications  Exception specifications  Kompilátor (a runtime) zajistí, že nepovolená výjimka neopustí funkci  Kompilátor to může využít  Speciálně při volání funkce s prázdným throw () se nemusí generovat ošetřující kód  Program se zmenší a možná i zrychlí  Užitek pro programátory:  Komentář  Ladicí prostředek

254 Reference vs. ukazatel

255 Reference a ukazatelé  Pravidla pro začátečníky  Kdy použít referenci: T &  Výstupní parametr  Návratová hodnota funkce zpřístupňující objekt T & vector ::at(size_t i)  Kdy použít konstantní referenci: const T &  Obvykle pouze kvůli rychlosti  Parametr typu struktura/třída  Návratová hodnota funkce zpřístupňující objekt ke čtení const T & vector ::at(size_t i) const  Kdy použít ukazatel (T *)  Je-li objekt dynamicky alokován  Je-li nutná schopnost přesměrování, null, nebo aritmetika  Nelze-li referenci správně namířit v okamžiku inicializace  Kdy použít konstantní ukazatel (const T *)  Sekundární odkaz na objekt, schopný pouze čtení

256 Reference a ukazatelé  Pravidla pro pokročilejší  Vlastník dynamicky alokovaného objektu  je zodpovědný za jeho zrušení - musí použít ukazatel “T *”  nelze-li jednoznačně určit vlastníka, použijte “shared_ptr ”  Uživatel objektu  Pokud je životnost pozorovatele kratší než životnost objektu lze použít referenci – “T &” nebo “const T &”  Pokud je životnost delší než životnost objektu nebo jinak komplikovaná je nutné použít ukazatel – “T *” nebo “const T *”

257 Reference a ukazatelé  Pravidla pro vracení hodnot odkazem  Pokud hodnota, kterou funkce vrací, existuje v nějakém objektu i po návratu z funkce, lze vrátit odkaz na tento objekt (konstantní) referencí T & vector ::back(); const T & vector ::back() const; T & T::operator+=(const T & b); T & T::operator++();// prefixová verze ++ vrací novou hodnotu  Pokud se hodnota, kterou funkce vrací, nově spočítala a není nikde uložena, funkce musí vracet hodnotou T operator+( const T & a, const T & b); T T::operator++(int);// postfixová verze ++ vrací starou hodnotu

258 Třída jako datový typ

259 Speciální metody tříd  Konstruktor bez parametrů (default constructor) XXX();  Používán u proměnných bez inicializace  Kompilátor se jej pokusí vygenerovat, je-li to třeba a pokud třída nemá vůbec žádný konstruktor: Položky, které nejsou třídami, nejsou generovaným konstruktorem inicializovány Generovaný konstruktor volá konstruktor bez parametrů na všechny předky a položky To nemusí jít např. pro neexistenci takového konstruktoru  Kopírovací konstruktor (copy constructor) XXX( const XXX &);  Používán pro předávání parametrů a návratových hodnot  Kompilátor se jej pokusí vygenerovat, je-li to třeba a pokud třída kopírovací konstruktor nemá: Položky, které nejsou třídami, jsou kopírovány Na předky a položky se volá kopírovací konstruktor To nemusí jít kvůli ochraně přístupu

260 Speciální metody tříd  Operátor přiřazení (assignment operator) const XXX & operator=( const XXX &);  Implementace operátoru = pro typ XXX na levé straně  Kompilátor se jej pokusí vygenerovat, je-li to třeba a třída jej nemá: Položky, které nejsou třídami, jsou kopírovány Na předky a položky se volá operátor přiřazení To nemusí jít kvůli ochraně přístupu  Destruktor ~XXX();  Používán při zániku objektu  Kompilátor se jej pokusí vygenerovat, je-li to třeba a třída jej nemá To nemusí jít kvůli ochraně přístupu  Pokud je objekt destruován operátorem delete aplikovaným na ukazatel na předka, musí být destruktor v tomto předku deklarován jako virtuální virtual ~XXX();

261 Speciální metody tříd  Konverzní konstruktory class XXX { XXX( YYY); };  Zobecnění kopírovacího konstruktoru  Definuje uživatelskou konverzi typu YYY na XXX  Je-li tento speciální efekt nežádoucí, lze jej zrušit: explicit XXX( YYY);  Konverzní operátory class XXX { operator YYY() const; };  Definuje uživatelskou konverzi typu XXX na YYY  Vrací typ YYY hodnotou (tedy s použitím kopírovacího konstruktoru YYY, pokud je YYY třída)  Kompilátor vždy použije nejvýše jednu uživatelskou konverzi

262 Virtuální funkce class Base { virtual void f() { /*... */ } }; class Derived : public Base { virtual void f() { /*... */ } };  Mechanismus virtuálních funkcí se uplatní pouze v přítomnosti ukazatelů nebo referencí Base * p = new Derived; p->f(); // volá Derived::f  V jiné situaci není virtuálnost funkcí užitečná Derived d; d.f(); // volá Derived::f i kdyby nebyla virtuální Base b = d;// slicing = kopie části objektu b.f(); // volá Base::f ikdyž je virtuální  Slicing je specifikum jazyka C++

263 Názvosloví  Abstraktní třída  Definice v C++: Třída obsahující alespoň jednu čistě virtuální funkci  Běžná definice: Třída, která sama nebude instanciována  Představuje rozhraní, které mají z ní odvozené třídy (potomci) implementovat  Konkrétní třída  Třída, určená k samostatné instanciaci  Implementuje rozhraní, předepsané abstraktní třídou, ze které je odvozena

264 Dědičnost a destruktor class Base { public: virtual ~Base() {} }; class Derived : public Base { public: virtual ~Derived() { /*... */ } }; Base * p = new Derived; delete p;  Pokud je objekt destruován operátorem delete aplikovaným na ukazatel na předka, musí být destruktor v tomto předku deklarován jako virtuální  Odvozené pravidlo:  Každá abstraktní třída má mít virtuální destruktor  Je to zadarmo  Může se to hodit

265 Dědičnost  Mechanismus dědičnosti v C++ je velmi silný  Bývá používán i pro nevhodné účely  Ideální použití dědičnosti je pouze toto  ISA hierarchie pro plnohodnotné objekty (first-class objects) Živočich-Obratlovec-Savec-Pes-Jezevčík Objekt-Viditelný-Editovatelný-Polygon-Čtverec  Vztah interface-implementace Readable-InputFile Writable-OutputFile (Readable+Writable)-IOFile  Jiná použití dědičnosti obvykle signalizují chybu v návrhu Výjimky samozřejmě existují (traits...)

266 Dědičnost  ISA hierarchie pro plnohodnotné objekty (first-class objects) C++: Jednoduchá nevirtuální veřejná dědičnost class Derived : public Base Abstraktní třídy někdy obsahují datové položky  Vztah interface-implementace C++: Násobná virtuální veřejná dědičnost class Derived : virtual public Base1, virtual public Base2 Abstraktní třídy obvykle neobsahují datové položky Abstraktní třídy nebývají využívány k destrukci objektu  Oba přístupy se často kombinují class Derived : public Base, virtual public Interface1, virtual public Interface2

267 Nesprávné užití dědičnosti  Nesprávné užití dědičnosti č. 1 class Real { public: double Re; }; class Complex : public Real { public: double Im; };  Porušuje pravidlo "každý potomek má všechny vlastnosti předka" např. pro vlastnost "má nulovou imaginární složku"  Důsledek - slicing: double abs( const Real & p) { return p.Re > 0 ? p.Re : - p.Re; } Complex x; double a = abs( x);// tento kód LZE přeložit, a to je špatně  Důvod: Referenci na potomka lze přiřadit do reference na předka Complex => Complex & => Real & => const Real &

268 Nesprávné užití dědičnosti  Nesprávné užití dědičnosti č. 1 class Real { public: double Re; }; class Complex : public Real { public: double Im; };  Slicing nastává i u předávání hodnotou double abs( Real p) { return p.Re > 0 ? p.Re : - p.Re; } Complex x; double a = abs( x);// tento kód LZE přeložit, a to je špatně  Důvod: Předání hodnoty x do parametru p je provedeno implicitně vytvořeným konstruktorem: Real::Real( const Real & y) { Re = y.Re; }  Parametr x typu Complex do tohoto konstruktoru lze předat Complex => Complex & => Real & => const Real &

269 Nesprávné užití dědičnosti  Nesprávné užití dědičnosti č. 2 class Complex { public: double Re, Im; }; class Real : public Complex { public: Real( double r); };  Vypadá jako korektní specializace: "každé reálné číslo má všechny vlastnosti komplexního čísla"  Chyba: Objekty v C++ nejsou hodnoty v matematice  Třída Complex má vlastnost "lze do mne přiřadit Complex" Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude

270 Nesprávné užití dědičnosti  Nesprávné užití dědičnosti č. 2 class Complex { public: double Re, Im; }; class Real : public Complex { public: Real( double r); };  Vypadá jako korektní specializace: "každé reálné číslo má všechny vlastnosti komplexního čísla"  Chyba: Objekty v C++ nejsou hodnoty v matematice  Třída Complex má vlastnost "lze do mne přiřadit Complex" Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude void set_to_i( Complex & p) { p.Re = 0; p.Im = 1; } Real x; set_to_i( x);// tento kód LZE přeložit, a to je špatně  Důvod: Referenci na potomka lze přiřadit do reference na předka Real => Real & => Complex &

271 Nesprávné užití dědičnosti  Nesprávné užití dědičnosti č. 2 class Complex { public: double Re, Im; }; class Real : public Complex { public: Real( double r); };  Vypadá jako korektní specializace: "každé reálné číslo má všechny vlastnosti komplexního čísla"  Chyba: Objekty v C++ nejsou hodnoty v matematice  Třída Complex má vlastnost "lze do mne přiřadit Complex" Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude  Poznámka: při přímem přiřazování tento problém nenastane Complex y; Real x; x = y;// tento kód NELZE přeložit Důvod: operátor = se nedědí Complex & Complex::operator=( const Complex &); // nezdědí se Real & Real::operator=( const Real &); // nesouhlasí typ argumentu

272 Ideální užití dědičnosti a virtuálních funkcí  Abstraktní třída  Definuje rozhraní objektu jako množinu předepsaných virtuálních funkcí class GraphicObject { public: virtual ~GraphicObject(); // každá abstraktní třída má mít v.d. virtual void paint() = 0; // čistě virtuální funkce virtual void move( int dx, int dy) = 0; // čistě virtuální funkce };

273 Příklad: dědičnost a virtuální funkce  ISA hierarchie  Osoba  Student  Zaměstnanec  Učitel Matikář Fyzikář Chemikář  Ředitel  Ostatní Školník Kuchař  Které abstraktní třídy potřebujeme?  Ty, jejichž specifické rozhraní někdo potřebuje Třída „Ostatní“ není potřeba

274 Příklad: dědičnost a virtuální funkce  ISA hierarchie  Osoba  Student  Zaměstnanec  Ředitel  Školník  Kuchař  Potřebujeme třídu Osoba?  Máme nějaký seznam osob?  Další rozhraní  Učitel  Matikář  Fyzikář  Chemikář  Má být Matikář odvozen z Učitele?  Mají Matikář a Fyzikář něco společného?

275 Nepoužité slajdy PRG032

276 Šablony tříd - příklad  Definice template class Array { T p[ n]; T dummy; public: T & operator[]( int x) { return x d; a[ 3] = b[ 3]; a = b; // chyba !!! b = c; // OK, implicitní copy-constructor d[ 2][ 3] = 1;

277 Poučení – konverzní operátor  Konverzní operátor? operator double() const;  Implementuje konverzi Complex => double  Není zde vhodný  Konverze je ztrátová  Může vést k nejednoznačnostem Complex a, b; double c; a = b + c; může znamenat volání Complex::Complex( double re, double im = 0.0); Complex operator+( const Complex & a, const Complex & b); Complex::operator double() const; double double::operator=( double b); // vestavěná operace ale také Complex::operator double() const; double operator+( double a, double b); // vestavěná operace double double::operator=( double b); // vestavěná operace Kompilátor si (z definice) neumí vybrat – hlásí chybu Výběr funkce/operátoru je řízen pouze typy argumentů

278 Poučení – konverzní operátor  Konverzní operátor? operator double() const;  Implementuje konverzi Complex => double  Není zde vhodný  Konverze je ztrátová  Může vést k nejednoznačnostem  Může vést k chybám Complex a, b; a = sin( b); může znamenat volání Complex sin( const Complex & x); // ale také Complex::operator double() const; double sin( double x);// Complex( double re, double im = 0.0); První varianta má přednost......ale když zapomenete #include...

279 C++

280 Třída a objekt  Třída (class)  Zobecnění pojmu struktura (struct)  Rozdíl mezi class a struct v C++ je nepatrný  Užívání class místo struct 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)  Objekt (instance třídy)  Běhová reprezentace jednoho exempláře třídy  Reprezentace objektu v paměti obsahuje  Datové položky  Skryté pomocné položky umožňující funkci virtuálních metod, výjimek a RTTI virtuální dědičnosti

281 Objekt a ukazatel na objekt  C++ důsledně odlišuje objekt a ukazatel na něj  Triviální důsledek: class T { /*...*/ }; T * p; // zde nevzniká objekt typu T

282 Objekt a ukazatel na objekt  C++ důsledně odlišuje objekt a ukazatel na něj  Triviální důsledek: class T { /*...*/ }; T * p; // zde nevzniká objekt typu T  Netriviální důsledek:  Proměnná typu T je vždy objekt typu T a žádného jiného, přestože do ní lze přiřadit i objekt odvozeného typu class U : public T { /*...*/ }; U y; T x = y; // toto je kopie části objektu y do vznikajícího objektu x

283 Objekt a ukazatel na objekt  C++ důsledně odlišuje objekt a ukazatel na něj  Triviální důsledek: class T { /*...*/ }; T * p; // zde nevzniká objekt typu T  Netriviální důsledek:  Proměnná typu T je vždy objekt typu T a žádného jiného, přestože do ní lze přiřadit i objekt odvozeného typu class U : public T { /*...*/ }; U y; T x = y; // toto je kopie části objektu y do vznikajícího objektu x  Poznámka pro znalce pravidel:  K tomuto přiřazení může dojít díky existenci automaticky vytvořeného copy-constructoru T::T( const T &);  a díky možnosti konvertovat odkaz na potomka na odkaz na předka: U => U & => T & => const T &

284 Objekt a ukazatel na objekt  C++ důsledně odlišuje objekt a ukazatel na něj  Triviální důsledek: class T { /*...*/ }; T * p; // zde nevzniká objekt typu T  Netriviální důsledek:  Proměnná typu T je vždy objekt typu T a žádného jiného, přestože do ní lze přiřadit i objekt odvozeného typu class U : public T { /*...*/ }; U y; T x = y; // toto je kopie části objektu y do vznikajícího objektu x  Poznámka pro znalce implementace:  Zde (ani nikde jinde) se nekopírují odkazy na tabulky virtuálních funkcí  Proměnná typu T tedy zůstane typem T včetně přiřazení těl virtuálních funkcí  Jiné chování by nemělo smysl

285 Objekt a ukazatel na objekt  C++ důsledně odlišuje objekt a ukazatel na něj  Triviální důsledek: class T { /*...*/ }; T * p; // zde nevzniká objekt typu T  Netriviální důsledek:  Proměnná typu T je vždy objekt typu T a žádného jiného, přestože do ní lze přiřadit i objekt odvozeného typu class U : public T { /*...*/ }; U y; T x = y; // toto je kopie části objektu y do vznikajícího objektu x  V tomto odlišování se C++ liší od většiny jazyků s objekty (Java, JavaScript, PHP, VisualBasic,...)

286 Objekt a ukazatel na objekt  C++ důsledně odlišuje objekt a ukazatel na něj  Nefunguje naivní implementace polymorfního typu: class Variant { enum { REAL, COMPLEX } t; }; class Real : public Variant { public: double Re; }; class Complex : public Variant { public: double Re, Im; }; Variant max( Variant a, Variant b); Real x, y, z = max( x, y);// nelze přeložit Complex u, v, w = max( u, v);// nelze přeložit

287 Objekt a ukazatel na objekt  C++ důsledně odlišuje objekt a ukazatel na něj  Nefunguje naivní implementace polymorfního typu: class Variant { enum { REAL, COMPLEX } t; }; class Real : public Variant { public: double Re; }; class Complex : public Variant { public: double Re, Im; }; Variant max( Variant a, Variant b); Real x, y, z = max( x, y);// nelze přeložit Complex u, v, w = max( u, v);// nelze přeložit  Parametry a, b nedokážou přenést atributy Re, Im  Návratovou hodnotu nelze (ani explicitně) přetypovat na potomka Real x, y, z = (Real)max( x, y);// nelze přeložit Complex u, v, w = (Complex)max( u, v);// nelze přeložit I kdyby to šlo, typ Variant vracený hodnotou nedokáže přenést atributy Re, Im

288 Objekt a ukazatel na objekt  C++ důsledně odlišuje objekt a ukazatel na něj  Nefunguje naivní implementace polymorfního typu: class Variant { enum { REAL, COMPLEX } t; }; class Real : public Variant { public: double Re; }; class Complex : public Variant { public: double Re, Im; };  V tomto případě lze tento problém řešit referencemi: Variant & max( Variant & a, Variant & b); vyžaduje ovšem explicitní přetypování, které je nebezpečné Real x, y, z = (Real &)max( x, y);// funguje Complex u, v, w = (Complex &)max( u, v);// funguje  Vracení referencí ovšem funguje pouze pro funkce max a min

289 Objekt a ukazatel na objekt  C++ důsledně odlišuje objekt a ukazatel na něj  Nefunguje naivní implementace polymorfního typu: class Variant { enum { REAL, COMPLEX } t; }; class Real : public Variant { public: double Re; }; class Complex : public Variant { public: double Re, Im; };  V tomto případě lze tento problém řešit referencemi: Variant & max( Variant & a, Variant & b); vyžaduje ovšem explicitní přetypování, které je nebezpečné Real x, y, z = (Real &)max( x, y);// funguje Complex u, v, w = (Complex &)max( u, v);// funguje  Vracení referencí ovšem funguje pouze pro funkce max a min Tyto funkce mají speciální vlastnost: vrací jeden ze svých parametrů

290 Třídy v C++  Konstrukce class, dědičnost a virtuální funkce jsou silný mechanismus, užívaný k různým účelům  Různé pohledy na třídy a různá pojmenování  Abstraktní a konkrétní třídy  Třídy jako datové typy  Kontejnery (třídy logicky obsahující jiné objekty)  Singletony (jednou instanciované třídy)  Traits (neinstanciované třídy)  Různé účely dědičnosti  Rozšíření požadovaného rozhraní  Implementace požadovaného rozhraní  Rozšíření implementované funkčnosti  Využití k implementaci

291 Nesprávné užití dědičnosti  Nesprávné užití dědičnosti č. 1 class Real { public: double Re; }; class Complex : public Real { public: double Im; };  Vypadá jako reusabilita kódu

292 Nesprávné užití dědičnosti  Nesprávné užití dědičnosti č. 1 class Real { public: double Re; }; class Complex : public Real { public: double Im; };  Vypadá jako reusabilita kódu  Porušuje pravidlo "každý potomek má všechny vlastnosti předka" např. pro vlastnost "má nulovou imaginární složku"

293 Nesprávné užití dědičnosti  Nesprávné užití dědičnosti č. 1 class Real { public: double Re; }; class Complex : public Real { public: double Im; };  Vypadá jako reusabilita kódu  Porušuje pravidlo "každý potomek má všechny vlastnosti předka" např. pro vlastnost "má nulovou imaginární složku"  Důsledek - slicing: double abs( const Real & p) { return p.Re > 0 ? p.Re : - p.Re; } Complex x; double a = abs( x);// tento kód LZE přeložit, a to je špatně  Důvod: Referenci na potomka lze přiřadit do reference na předka Complex => Complex & => Real & => const Real &

294 Nesprávné užití dědičnosti  Nesprávné užití dědičnosti č. 1 class Real { public: double Re; }; class Complex : public Real { public: double Im; };  Slicing nastává i u předávání hodnotou double abs( Real p) { return p.Re > 0 ? p.Re : - p.Re; } Complex x; double a = abs( x);// tento kód LZE přeložit, a to je špatně  Důvod: Předání hodnoty x do parametru p je provedeno implicitně vytvořeným konstruktorem: Real::Real( const Real & y) { Re = y.Re; }  Parametr x typu Complex do tohoto konstruktoru lze předat Complex => Complex & => Real & => const Real &

295 Nesprávné užití dědičnosti  Nesprávné užití dědičnosti č. 2 class Complex { public: double Re, Im; }; class Real : public Complex { public: Real( double r); };  Vypadá jako korektní specializace: "každé reálné číslo má všechny vlastnosti komplexního čísla"

296 Nesprávné užití dědičnosti  Nesprávné užití dědičnosti č. 2 class Complex { public: double Re, Im; }; class Real : public Complex { public: Real( double r); };  Vypadá jako korektní specializace: "každé reálné číslo má všechny vlastnosti komplexního čísla"  Chyba: Objekty v C++ nejsou hodnoty v matematice  Třída Complex má vlastnost "lze do mne přiřadit Complex" Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude

297 Nesprávné užití dědičnosti  Nesprávné užití dědičnosti č. 2 class Complex { public: double Re, Im; }; class Real : public Complex { public: Real( double r); };  Vypadá jako korektní specializace: "každé reálné číslo má všechny vlastnosti komplexního čísla"  Chyba: Objekty v C++ nejsou hodnoty v matematice  Třída Complex má vlastnost "lze do mne přiřadit Complex" Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude void set_to_i( Complex & p) { p.Re = 0; p.Im = 1; } Real x; set_to_i( x);// tento kód LZE přeložit, a to je špatně  Důvod: Referenci na potomka lze přiřadit do reference na předka Real => Real & => Complex &

298 Nesprávné užití dědičnosti  Nesprávné užití dědičnosti č. 2 class Complex { public: double Re, Im; }; class Real : public Complex { public: Real( double r); };  Vypadá jako korektní specializace: "každé reálné číslo má všechny vlastnosti komplexního čísla"  Chyba: Objekty v C++ nejsou hodnoty v matematice  Třída Complex má vlastnost "lze do mne přiřadit Complex" Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude  Poznámka: při přímem přiřazování tento problém nenastane Complex y; Real x; x = y;// tento kód NELZE přeložit Důvod: operátor = se nedědí Complex & Complex::operator=( const Complex &); // nezdědí se Real & Real::operator=( const Real &); // nesouhlasí typ argumentu

299 Třídy v C++  Třídy sloužící jako datové typy  Proměnné typu T  Časté kopírování, vracení hodnotou  Přiřazení bývá jediný způsob změny stavu objektu  Dědičnost nemá smysl  Bez virtuálních funkcí  Třídy reprezentující „živé“ objekty  Proměnné typu T *, případně T &  Objekty alokovány dynamicky  Kopírování nemívá smysl  Metody měnící stav objektu  Většinou s dědičností a virtuálními funkcemi

300 Ideální užití dědičnosti a virtuálních funkcí  Abstraktní třída  Definuje rozhraní objektu jako množinu předepsaných virtuálních funkcí  Abstraktní třídy se mohou dědit  Dědičnost jako rozšiřování předepsaného rozhraní class ClickableObject : public GraphicObject { public: virtual void click( int x, int y) = 0; // čistě virtuální funkce };

301 Ideální užití dědičnosti a virtuálních funkcí  Abstraktní třída  Definuje rozhraní objektu jako množinu předepsaných virtuálních funkcí  Konkrétní třída  Implementuje předepsané virtuální funkce  Je potomkem abstraktní třídy  Dědičnost jako vztah rozhraní-implementace

302 Ideální užití dědičnosti a virtuálních funkcí  Abstraktní třída  Definuje rozhraní objektu jako množinu předepsaných virtuálních funkcí  Konkrétní třída  Implementuje předepsané virtuální funkce class Button : public ClickableObject { public: Button( int x, int y, const char * text); protected: virtual void paint(); virtual void move( int dx, int dy); virtual void click( int x, int y); private: int x_, y_; char * text_; };

303 Ideální užití dědičnosti a virtuálních funkcí  Abstraktní třída  Definuje rozhraní objektu jako množinu předepsaných virtuálních funkcí  Konkrétní třída  Implementuje předepsané virtuální funkce  Polotovar třídy  Mezi abstraktní a konkrétní třídou - třída implementující část předepsaných virtuálních funkcí  Jejím potomkem je konkrétní třída nebo jiný polotovar  Dědičnost jako reusabilita kódu

304 Ideální užití dědičnosti a virtuálních funkcí  Polotovar třídy  Mezi abstraktní a konkrétní třídou - třída implementující část předepsaných virtuálních funkcí class PositionedObject : public ClickableObject { public: PositionedObject( int x, int y); protected: int get_x() const { return x_; } int get_y() const { return y_; } virtual void move( int dx, int dy); private: int x_, y_; };

305 Ideální užití dědičnosti a virtuálních funkcí  Konkrétní třída  Implementuje předepsané virtuální funkce  Konkrétní třídy mohou mít potomky - jiné konkrétní třídy se změněnými vlastnostmi (redefinovanými virtuálními funkcemi)  Dědičnost jako reusabilita kódu se změnou chování class IconButton : public Button { public: IconButton( int x, int y, const char * text, BitMap icon); protected: virtual void paint(); private: BitMap icon_; };

306 Ideální užití dědičnosti a virtuálních funkcí  Různé významy dědičnosti  Rozšiřování předepsaného rozhraní GraphicObject => ClickableObject  Vztah rozhraní-implementace ClickableObject => PositionedObject, Button  Reusabilita kódu PositionedObject => Button  Reusabilita se změnou chování (overriding) Button => IconButton  A to není zdaleka všechno...  C++ pro odlišné účely využívá tytéž mechanismy  Některé jazyky tyto účely rozlišují (Java)

307 Ideální užití dědičnosti a virtuálních funkcí  Ideální abstraktní třída  Pouze čistě virtuální funkce  Žádná data, žádná těla funkcí  Někdy (nesprávně) nazývána protokol  Pojem Protokol většinou znamená seznam funkcí a pravidla pro pořadí jejich volání, což C++ nedovede vyjádřit

308 Ideální užití dědičnosti a virtuálních funkcí  Ideální abstraktní třída  Pouze čistě virtuální funkce  Žádná data, žádná těla funkcí  Někdy (nesprávně) nazývána protokol  Pojem Protokol většinou znamená množinu funkcí a pravidla pro pořadí jejich volání, což C++ nedovede vyjádřit  Takové množiny funkcí má smysl kombinovat  Významem je sjednocení množin schopností  Příklad: Fyzikář+Matikář

309 Ideální užití dědičnosti a virtuálních funkcí  Ideální abstraktní třída  Pouze čistě virtuální funkce  Žádná data, žádná těla funkcí  Někdy (nesprávně) nazývána protokol  Pojem Protokol většinou znamená množinu funkcí a pravidla pro pořadí jejich volání, což C++ nedovede vyjádřit  Takové množiny funkcí má smysl kombinovat  Významem je sjednocení množin schopností  Příklad: Fyzikář+Matikář  V C++: násobná dědičnost  Obvykle musí být virtuální, aby odpovídala sjednocení:  Fyzikář = Pedagogika + Fyzika  Matikář = Pedagogika + Matematika  Fyzikář+Matikář nemá mít dvě rozhraní pro Pedagogiku

310 Ideální užití dědičnosti a virtuálních funkcí  Podmínka užitečnosti virtuálních funkcí  Funkce musí být volána na objektu, jehož skutečný typ není v době kompilace znám  Nesmí to být proměnná typu třída  Musí to být ukazatel nebo reference na třídu

311 Ideální užití dědičnosti a virtuálních funkcí  Typické použití virtuálních funkcí  Polymorfní datová struktura  Datová struktura (pole, seznam, strom,...) obsahující objekty různých typů  Tyto objekty musí být vázány odkazem Typicky bývají samostatně dynamicky alokovány  Na objektech se obvykle vykonávají hromadné abstraktní operace (např. vykreslení) Volání čistě virtuální funkce (paint) na každém objektu

312 Ideální užití dědičnosti a virtuálních funkcí  Typické použití virtuálních funkcí  Polymorfní datová struktura class Scene { public: void insert( GraphicObject * go); void paintAll() const; private: enum { MAX_ = 30 }; GraphicObject * objects_[ MAX_]; int n_; };  primitivní řešení: pole ukazatelů pole objektů by nefungovalo (ani nešlo přeložit) void Scene::paintAll() { for ( int i = 0; i paint(); }

313 Ideální užití dědičnosti a virtuálních funkcí  Typické použití virtuálních funkcí  Polymorfní datová struktura struct Item { GraphicObject * go; Item * next; }; class Scene { public: void insert( GraphicObject * go); void paintAll() const; private: Item * first_; };  lepší řešení: spojový seznam ukazatelů Item nemůže přímo obsahovat GraphicObject void Scene::paintAll() { for ( Item * p = first_; p; p = p->next) p->go->paint(); }

314 Ideální užití dědičnosti a virtuálních funkcí  Typické použití virtuálních funkcí  Polymorfní datová struktura  jiné lepší řešení: intrusivní spojový seznam samotný GraphicObject slouží jako prvek spojového seznamu nevýhoda: abstraktní rozhraní zároveň obsahuje implementaci class GraphicObject { /*... čistě virtuální funkce... */ private: friend class Scene; Item * next_; }; class Scene { public: void insert( GraphicObject * go); void paintAll() const; private: GraphicObject * first_; };

315 Ideální užití dědičnosti a virtuálních funkcí  Typické použití virtuálních funkcí  Polymorfní datová struktura  moderní řešení: STL kontejnery z STL vždy obsahují kopie vkládaných objektů std::list by nešlo ani přeložit kontejner je tedy třeba aplikovat na ukazatel, podobně jako pole class Scene { public: void insert( GraphicObject * go); void paintAll() const; private: std::list objects_; };

316 String Ukázková implementace č. 1 (nepoužitelná)

317 String č. 1 - Nepoužitelné řešení class String { public: String() { _str[ 0] = 0; } String( const char * s) { strcpy( _str, s); } const char * c_str() const { return _str; } friend String operator+( const String & a, const String & b); private: enum { MAX = 256 }; char _str[ MAX]; }; String operator+( const String & a, const String & b) { String c; strcpy( c._str, a._str); strcat( c._str, b._str); return c; }

318 Poučení - Konverzní operátor  Metoda c_str by mohla být nahrazena konverzním operátorem operator const char *() const;  V tomto případě to není vhodné: String x; if ( x > "A" )  Může být převedeno na String > String (pokud je definován takový operátor) ale také na const char * > const char * Porovnává adresy ! printf( "%s", x);  Zde k žádné konverzi nedojde ! Program bude pravděpodobně ukončen pro pokus o nedovolenou operaci

319 String Ukázková implementace č. 2 (naivní, neefektivní)

320 String č. 2 - Naivní řešení class String { public: String(); String( const String &); const String & operator=( const String &); ~String(); String( const char *); const char * c_str() const; String cat( const String &) const; private: char * _str; }; inline String operator+( const String & a, const String & b) { return a.cat( b); }

321 Poučení - konstruktory  Ve třídě String jsou odkazy na data uložená jinde  Chování kompilátorem vytvořených metod nevyhovuje: String();  Nedělá nic - je třeba naalokovat prázdný string String( const String &);  Kopíruje ukazatel - je třeba kopírovat data, na která ukazuje String & operator=( const String &);  Totéž, navíc je nutné před přiřazením uklidit ~String();  Nedělá nic - je třeba uklidit  Tyto metody je tedy třeba napsat vlastní

322 Poučení - konstruktory  Operátor přiřazení by měl udělat toto:  Zrušit starý obsah levé strany Totéž co destruktor  Okopírovat pravou stranu Totéž co konstruktor  Vrátit novou hodnotu levé strany To lze vrátit odkazem  Pozor - v některých případech to fungovat nebude: String a = "ha"; a = a;  Při kopírovaní pravá strana už nebude existovat

323 Poučení - konstruktory  Vzorový operátor přiřazení: String & String::operator=( const String & b) { if ( this != & b ) { clean(); fill( b); } return * this; }  Konstruktory nelze přímo volat, u destruktoru to není rozumné String::String( const String & b) { fill( b); } String::~String() { clean(); }

324 Poučení - konstruktory  Lepší řešení operátoru přiřazení: #include void String::swap( String & b) { std::swap( _str, b._str); } String & String::operator=( const String & b) { String tmp( b); swap( tmp); return * this; }  Toto řešení je navíc exception-safe (později...)  Metodu swap je vhodné publikovat takto void swap( String & a, String & b) { a.swap( b); } Kdyby někdo udělal třídu obsahující náš String...

325 String Ukázková implementace č. 3 (counted-pointers)

326 String č. 3 - Counted-pointers class StringBody; class String { public: String(); String( const String &); const String & operator=( const String &); ~String(); String( const char *); operator const char *() const; String cat( const String &) const; private: String( StringBody *); StringBody * _body; };

327 String č. 3 - Counted-pointers class StringBody { friend class String; private: StringBody(); StringBody( int); ~StringBody(); void inc() { _count++; }; void dec(); char * buffer() { return _str; }; char * _str; int _count; static StringBody empty; };

328 String č. 3 - Counted-pointers  Kopie a destrukce String::String( const String & b) { (_body = b._body)->inc(); } const String & String::operator=( const String & b) { if ( _body != b._body ) { _body->dec(); (_body = b._body)->inc(); } return * this; } String::~String() { _body->dec(); }

329 String č. 3 - Counted-pointers  Destrukce těla void StringBody::dec() { if ( ! --count ) delete this; }; StringBody::~StringBody() { delete[] _str; }

330 String č. 3 - Counted-pointers  Vytvoření neprázdného těla StringBody::StringBody( int l) { _count = 0; _str = new char[ l]; } String::String( StringBody * b) { (_body = b)->inc(); } String::String( const char * s) { _body = new StringBody( strlen( s) + 1); _body->inc(); strcpy( _body->_str, s); }

331 String č. 3 - Counted-pointers  Speciální implementace prázdného řetězce StringBody::StringBody() { _str = ""; _count = 1; } StringBody StringBody::empty; String::String() { (_body = &StringBody::empty)->inc(); }

332 String operator [ ]

333 String č. 4 - operator [ ] - čtení class String { public: char operator[]( int pos) const { if ( pos = _body->length() ) return 0; return _body->buffer()[ _pos]; }; /*... */ };

334 String č. 4 - operator [ ] - zápis class String { public: char & operator[]( int pos) { if ( pos = _body->length() ) return _dummy; return _body->buffer()[ _pos]; }; private: static char _dummy; /*... */ }; char String::_dummy = 0;

335 String č. 4 - operator [ ] - zápis class String { public: char & operator[]( int pos) { if ( pos = _body->length() ) return _dummy; return _body->buffer()[ _pos]; // chyba: _body je sdíleno ! }; private: static char _dummy; /*... */ }; char String::_dummy = 0;

336 String č. 4 - operator [ ] - zápis do privátní kopie class String { public: char & operator[]( int pos) { /*... */ make_private(); return _body->buffer()[ _pos]; }; private: void make_private() { if ( _body->count > 1 ) { StringBody * nb = new StringBody( * _body); // copy-constructor _body->dec(); _body = nb; _body->inc(); } };

337 String č. 4 - operator [ ] - čtení a zápis class String { public: char operator[]( int pos) const { /*... */ return _body->buffer()[ _pos]; }; char & operator[]( int pos) { /*... */ make_private(); return _body->buffer()[ _pos]; };

338 String č. 4 - operator [ ] - čtení a zápis class String { public: char operator[]( int pos) const { /*... */ return _body->buffer()[ _pos]; }; char & operator[]( int pos) { /*... */ make_private(); return _body->buffer()[ _pos]; }; String a, b; char c; const String d; a[ 1] = c; // char & operator[]( int) c = d[ 1]; // char operator[]( int) const c = b[ 1]; // char & operator[]( int) - vytváří privátní kopii

339 String č. 4 - operator [ ] – rozlišení čtení a zápisu class StringPos; class String { public: /*... */ char read_pos( int pos) const { /*... */ } void write_pos( int pos, char b) { /*... */ } char operator[]( int pos) const { return read_pos( pos); } StringPos operator[]( int pos) { return StringPos( this, pos); } }; class StringPos { public: StringPos( String * t, int p) : t_( t), p_( p) {} operator char() const { return t_->read_pos( p_); } const StringPos & operator =( char b) const { t_->write_pos( p_, b); return * this; } private: String * t_; int p_; };

340 Standardní knihovny C++

341 namespace  Konstrukce namespace umožňuje uzavřít několik deklarací typů, tříd, globálních proměnných a funkcí do zvláštního prostoru jmen  Konstrukci namespace lze otevírat vícenásobně namespace X { typedef char * ptr; ptr f( ptr a, ptr b); }; namespace X { ptr g(); };  Uzavřené identifikátory lze mimo tento prostor referencovat kvalifikovaným jménem X::ptr v = X::g();  Celý prostor jmen lze rozbalit do aktuálního bloku nebo modulu konstrukcí: using namespace X;

342 Standardní knihovny C++  V novějších implementacích má většina hlavičkových souborů dvě verze  Stará konvence – v budoucnu nebude podporována soubor vector.h obsahuje šablonu vector  Nová konvence soubor vector obsahuje šablonu vector uzavřenou do namespace std je tedy nutné používat identifikátor std::vector  Standardní knihovny C++ mají tyto hlavní součásti  Základní knihovny převzaté z C, podle nové konvence v přejmenovaných souborech  Rozšířené C++ knihovny  iostream: Systém znakového a formátovaného vstupu a výstupu  STL: Standard Template Library

343 Základní knihovny C a C++ - ladicí funkce (makro assert) - klasifikace znaků (isalpha, isspace,...) - chybové kódy (ENOMEM,...), proměnná errno - vlastnosti a limity reálných typů (DBL_MAX,...) - limity celočíselných typů (INT_MAX,...) - přizpůsobení národnímu prostředí - matematické funkce (sin,...) - meziprocedurální skoky (setjmp, longjmp) - signály operačního systému - makra pro funkce s proměnným počtem argumentů - užitečné typy a konstanty (NULL) - standardní a souborový vstup a výstup - užitečné funkce (malloc,...) - manipulace s řetězci (strcpy,...) - konverze data a času - 16-bitové řetězce (wchar_t) - klasifikace 16-bitových znaků

344 Rozšířené knihovny pro C++  -- šablona pro bitové pole (bitset )  - komplexní čísla různé přesnosti (complex,...)  - nastavení zpracování výjimek (set_unexpected,...)  - standardní výjimky (overflow_error,...)  - šablony různě inteligentních polí (valarray,...), vektorové operace (podpora paralelních výpočtů matematických funkcí)

345 STL – Ostatní  - užitečné algoritmy (for_each, sort, next_permutation,...)  - podpora funktorů  - podpora iterátorů  - alokátory pro kontejnery  - jednoduchá matematika na prvcích kontejnerů  - pomocné konstrukce (pair,...)

346 iostream vstupní a výstupní proudy

347 iostream #include using namespace std; f() { int X; double Y; cin >> X >> Y; cout << "X = " << hex << setw(4) << X << ", Y = " << Y << endl; }  Manipulátory  hex - šestnáctkový výpis, setw - počet míst platí pro daný stream (cout) trvale (do další změny)  endl - vloží oddělovač řádek

348 stringstream #include using namespace std; string f( int a) { ostringstream x; x << "a = " << a; return x.str(); }  - *stringstream – spolupracuje se std::string  - *strstream – spolupracuje s char *

349 iostream  Hlavičkové soubory  - souborový vstup a výstup (ifstream, ofstream, fstream)  - manipulátory pro nastavení parametrů formátovaného vstupu a výstupu (setw, setprecision, setfill, setbase,...)  - základní funkce abstraktního souboru, základní nastavení formátu (hex, left,...)  - standardní vstup a výstup (cin, cout, cerr)  - abstraktní vstupní a kombinované médium (istream, iostream)  - abstraktní výstupní médium (ostream)  - vnitřní paměť jako médium (istringstream, ostringstream, stringstream)

350 iostream  Abstraktní rozhraní  basic_... jsou šablony  T = char - 8-bitové znakové sady typedef: ios, istream, ostream, iostream, streambuf  T = wchar_t - 16-bitové znakové sady typedef: wios, wistream, wostream, wiostream, wstreambuf ios_base basic_ios basic_istream basic_ostream basic_iostream basic_streambuf nastavení formátovače stav média přímé a formátované čtení přímý a formátovaný zápis virtuální funkce čtení a zápisu dědičnost virtuální dědičnost ukazatel

351 iostream  Abstraktní rozhraní  funkce, které potřebují zapisovat, dostávají basic_ostream & void zapis_neco( ostream & o) { o << neco; } ios_base basic_ios basic_ostream nastavení formátovače stav média přímý a formátovaný zápis dědičnost virtuální dědičnost

352 iostream  Abstraktní rozhraní  funkce, které potřebují číst, dostávají basic_istream & pozor: čtení modifikuje stream (posunuje ukazovátko) void cti_neco( istream & i) { i >> neco; } ios_base basic_ios basic_istream nastavení formátovače stav média přímé a formátované čtení dědičnost virtuální dědičnost ukazatel

353 iostream  Abstraktní rozhraní  funkce, které potřebují číst i zapisovat totéž médium, dostávají basic_iostream & ukazovátko čtení a zápisu NENÍ společné void zmen_neco( iostream & io) { io.read( neco, N); io.write( neco2, N); } ios_base basic_ios basic_istream basic_ostream basic_iostream nastavení formátovače stav média přímé a formátované čtení přímý a formátovaný zápis dědičnost virtuální dědičnost ukazatel

354 iostream  Abstraktní rozhraní  basic_iostream obsahuje funkce čtení i zápisu  nastavení formátu i stav média jsou společné pro čtení i zápis  basic_ios zde musí být pouze v jedné instanci  dědění basic_ios musí být virtuální template class basic_istream : virtual public basic_ios ios_base basic_ios basic_istream basic_ostream basic_iostream nastavení formátovače stav média přímé a formátované čtení přímý a formátovaný zápis dědičnost virtuální dědičnost

355 iostream  Médium  Konkrétní médium je implementováno jako potomek třídy basic_streambuf  Standardní knihovna C++ nabízí: soubor (to, co umí OS, tedy včetně rour apod.) uložení v paměti  Lze implementovat vlastní (např. výstup do okna) ios_base basic_ios basic_istream basic_ostream basic_iostream basic_...buf basic_streambuf stav formátovače stav média přímé a formátované čtení přímý a formátovaný zápis virtuální funkce čtení a zápisu médium, implementace čtení a zápisu

356 sstream  Paměťové médium  Obálkové třídy...stringstream zařídí vznik basic_stringbuf  Umožňují přístup k médiu jako basic_string ios_base basic_ios basic_istream basic_ostream basic_iostream basic_stringbuf basic_istringstream basic_ostringstream basic_stringstream basic_streambuf stav formátovače stav média přímé a formátované čtení přímý a formátovaný zápis virtuální funkce čtení a zápisu médium, implementace čtení a zápisu str: přístup k médiu

357 fstream  Souborové médium, (cin, cout, cerr)  Obálkové třídy...fstream zařídí vznik basic_filebuf  Soubor se otevírá(zavírá) metodou open(close) těchto tříd ios_base basic_ios basic_istream basic_ostream basic_iostream basic_filebuf basic_ifstream basic_ofstream basic_fstream basic_streambuf stav formátovače stav média přímé a formátované čtení přímý a formátovaný zápis virtuální funkce čtení a zápisu médium, implementace čtení a zápisu open, close Operační systém

358 iostream  ios: stav média  good, eof, fail, bad - metody indikující stavy  istream/ostream - neformátované čtení/zápis  read/write - čtení/zápis n znaků  get/put - čtení/zápis jednotlivých znaků a čtení po řádkách  seekg/seekp, tellg/tellp - posun ukazovátka, zjištění pozice funkce...g manipulují s ukazovátkem pro čtení (get, istream) funkce...p manipulují s ukazovátkem pro čtení (put, ostream) u některých médií nefunguje (roury)

359 iostream  formátované čtení basic_istream & operator>>( basic_istream & s, D & x)  operátor přečte několik znaků ve tvaru určeném typem D  naplní výstupní parametr x  formátovaný zápis basic_ostream & operator & s, D x)  operátor vypíše x jako několik znaků ve tvaru určeném typem D  Oba operátory vrací levý operand  Tím je umožněno zřetězené použití s << x << y << z;  je ekvivalentní s << x; s << y; s << z;

360 iostream  formátované čtení/zápis basic_istream & operator>>( basic_istream & s, D & x) basic_ostream & operator & s, D x)  Knihovna istream/ostream implementuje operátory pro typy  (unsigned) short, (unsigned) int, (unsigned) long - dec, hex, oct  float, double, long double - desetinný a/nebo exponenciální tvar  bool, void * - pro ladicí účely  char/wchar_t - znak  char * / wchar_t * - řetězec v C tvaru  Další typy lze dodefinovat (jako globální operátory)  Standardní knihovny C++ je definují pro string/wstring - řetězec complex - komplexní číslo

361 iostream  Definování vlastních formátovacích operátorů class Souradnice { public: int x, y; }; std::ostream & operator<<( std::ostream & s, const Souradnice & a) { return s << '[' << a.x << ',' << a.y << ']'; }  Použití Souradnice p; std::cout << "p = " << p << std::endl;

362 iostream  Jak fungují manipulátory cout << "X = " << hex << setw(4) << X << ", Y = " << Y << endl;  Bez parametrů (hex, endl,...)  Definovány jako funkce manipulující s ios_base Proto ios_base musí být třída a nikoliv šablona jako basic_ios ios_base & hex( ios_base & s) { s.setf( ios_base::hex); return s; }  Akceptovány jako ukazatel na funkci zvláštní verzí operátoru << basic_ostream & operator<<( basic_ostream & s, ios_base & (* f)( ios_base & s)) { f( s); return s; }

363 iostream  Jak fungují manipulátory cout << "X = " << hex << setw(4) << X << ", Y = " << Y << endl;  S parametry (setw, setprecision, setfill,...)  Definovány jako funkce vracející speciální třídu struct setw_manip { int x; explicit setw_manip( int p) : x( p) {} }; setw_manip setw( int p) { return setw_manip( p); }  Akceptovány zvláštními verzemi operátoru << basic_ostream & operator<<( basic_ostream & s, const setw_manip & p) { s.width( p.x); return s; }

364 iostream  Vztah proudů a kontejnerů  stringstream umožňuje přístup k médiu jako string  pro string jsou definovány operátory >  definuje iterátory nad proudy: istream_iterator  vstupní iterátor, čte typ U pomocí operátoru >>  parametrem konstruktoru je istream & ostream_iterator  výstupní iterátor, zapisuje typ U pomocí operátoru <<  parametrem konstruktoru je ostream &

365 iostream  Iterátory nad proudy - příklady  naplnění kontejneru ze vstupu std::vector a; std::copy( std::istream_iterator ( std::cin),// aktuální pozice std::istream_iterator (),// "konec souboru" std::back_inserter( a));// vkládací iterátor kontejneru  vysypání kontejneru na výstup  nevýhoda: neodděluje elementy výstupu std::vector b; std::copy( b.begin(), b.end(), std::ostream_iterator ( std::cout));

366 RTTI Typová informace za běhu

367 RTTI  Operátor typeid typeid(T)  Vrací identifikaci typu T typeid(e)  Pokud výraz e je typu reference na třídu s alespoň jednou virtuální funkcí Vrací identifikaci typu objektu určeného výrazem e Pokud je reference nulová, vyvolává výjimku std::bad_typeid  Jinak Vrací identifikaci statického typu výrazu e  Identifikace typu const std::type_info &   Lze porovnávat na rovnost  Má metodu name() vracející řetězec s nějakou formou jména typu

368 RTTI  Typické použití  Alternativa místo dynamic_cast #include class Base { public: virtual ~Base(); /* alespoň jedna virtuální funkce */ }; class X : public Base { /*... */ }; class Y : public Base { /*... */ }; Base * p = /*... */; if ( typeid( * p) == typeid( X) ) { X * xp = static_cast ( p); /*... */ } if ( typeid( * p) == typeid( Y) ) { Y * yp = static_cast ( p); /*... */ }

369 RTTI  Typické použití  Alternativa místo dynamic_cast  Pozor: rovnost typeid nereflektuje dědičnost Zatímco dynamic_cast ano #include class Base { public: virtual ~Base(); /* alespoň jedna virtuální funkce */ }; class X : public Base { /*... */ }; class Z : public X { /*... */ }; Base * p = new Z; if ( typeid( * p) == typeid( X) ) // neplatí ! { X * xp = static_cast ( p); /*... */ } if ( typeid( * p) == typeid( Z) ) // platí { Z * zp = static_cast ( p); /*... */ }

370 RTTI  RTTI obvykle něco stojí  Každá třída s virtuálními funkcemi někde musí mít své type_info a odkaz na něj ve své tabulce virtuálních funkcí To platí i pro instance šablon, jejichž jména bývají dlouhá  RTTI se příliš nevyužívá  Je slabší než dynamic_cast  Je nové Mezitím se programátoři naučili dělat si RTTI vlastními prostředky  Většina překladačů zapíná RTTI na vyžádání  Někdy se takový přepínač vztahuje i na dynamic_cast  U některých překladačů souvisí s RTTI i zpracování výjimek

371 Visitor  Visitor  Abstraktní třída  Určena k předání jiné funkci, která vybere a zavolá vhodnou virtuální metodu podle skutečného typu nějakého objektu  Často používána ve spojení s procházením polymorfní datovou strukturou  Příklad  Datová struktura Scene obsahuje objekty tří druhů (Ellipse, Rectangle, Line)  Metoda doitforall projde všechny prvky scény a aplikuje na každý z nich vhodnou virtuální metodu zadaného Visitoru class Ellipse; class Rectangle; class Line; class Visitor { public: virtual void visitEllipse( Ellipse *)=0; virtual void visitRectangle( Rectangle *)=0; virtual void visitLine( Line *)=0; virtual ~Visitor() {} }; class Scene { public: void doitforall( Visitor &); /*... */ };

372 Visitor  Visitor - použití class PrintVisitor : public Visitor { public: PrintVisitor( Printer * p) : p_( p) {} virtual void visitEllipse( Ellipse *); virtual void visitRectangle( Rectangle *); virtual void visitLine( Line *); private: Printer * p_; }; Scene s; Printer * p; s.doitforall( PrintVisitor( p)); class Ellipse; class Rectangle; class Line; class Visitor { public: virtual void visitEllipse( Ellipse *)=0; virtual void visitRectangle( Rectangle *)=0; virtual void visitLine( Line *)=0; virtual ~Visitor() {} }; class Scene { public: void doitforall( Visitor &); /*... */ };

373 Visitor  Visitor  Visitor umožňuje definovat tolik variant nějaké akce, kolik je druhů procházených objektů  Pomocí visitoru je možné definovat další akce  Příklad  Vyskytne-li se potřeba výstupu na plotter, stačí definovat další potomek visitoru class Ellipse; class Rectangle; class Line; class Visitor { public: virtual void visitEllipse( Ellipse *)=0; virtual void visitRectangle( Rectangle *)=0; virtual void visitLine( Line *)=0; virtual ~Visitor() {} }; class Scene { public: void doitforall( Visitor &); /*... */ };

374 Visitor  Srovnání  Virtuální funkce  Podobného efektu jako u visitoru lze dosáhnout virtuální funkcí deklarovanou ve společném předku objektů s odlišnými těly pro každý druh objektu  Sada virtuálních funkcí ve společném předku není rozšiřitelná class AbstractObject { public: virtual void print( Printer *)=0; virtual void display( Display *)=0; virtual ~AbstractObject() {} }; class Ellipse : public AbstractObject { /*... */ }; class Ellipse; class Rectangle; class Line; class Visitor { public: virtual void visitEllipse( Ellipse *)=0; virtual void visitRectangle( Rectangle *)=0; virtual void visitLine( Line *)=0; virtual ~Visitor() {} }; class Scene { public: void doitforall( Visitor &); /*... */ };

375 Visitor  Visitor  Výhoda: Přidávání další akce nevyžaduje změnu společného rozhraní  Nevýhoda: Přidání dalšího druhu objektu si vynutí změnu visitoru (přidání virtuální funkce)  Poučení  V případě stabilní množiny akcí na nestabilní množině druhů objektů použijte virtuální funkce  V případě nestabilní množiny akcí na stabilní množině druhů objektů použijte visitor  V případě nestabilní množiny akcí i druhů... ? class Ellipse; class Rectangle; class Line; class Visitor { public: virtual void visitEllipse( Ellipse *)=0; virtual void visitRectangle( Rectangle *)=0; virtual void visitLine( Line *)=0; virtual ~Visitor() {} }; class Scene { public: void doitforall( Visitor &); /*... */ };

376 Visitor  Visitor - implementace class AbstractObject { public: virtual void apply( Visitor &) = 0; virtual ~AbstractObject() {} }; class Ellipse { protected: virtual void apply( Visitor & v) { v.visitEllipse( v); } /*... */ }; void Scene::doitforall( Visitor & v) { for ( my_vector_::iterator it = elements_.begin(); it != elements_.end(); ++it ) (*it)->apply( v); } class Ellipse; class Rectangle; class Line; class Visitor { public: virtual void visitEllipse( Ellipse *)=0; virtual void visitRectangle( Rectangle *)=0; virtual void visitLine( Line *)=0; virtual ~Visitor() {} }; class Scene { public: void doitforall( Visitor &); /*... */ private: typedef vector my_vector_; my_vector_ elements_; };

377 Design Patterns Návrhové vzory

378 Design patterns  Design patterns  Abstract factory  Adapter  Bridge  Builder  Chain of responsibility  Command  Composite  Decorator  Facade  Factory method  Flyweight  Generation gap  Interpreter  Iterator  Mediator  Memento  Multicast  Observer  Prototype  Proxy  Singleton  State  Strategy  Template method  Typed message  Visitor  a mnoho dalších...  Gamma, Helm, Johnson, Vlissides: Design Patterns, 1995

379 Design patterns  Design patterns - proč?  Vše již bylo vynalezeno - nevynalézat kolo  Moudrost věků - neopakovat chyby  Výuka a poučení - jak to dělají jiní  Zpřístupnění objektového programování "méně kreativním" programátorům  Společná terminologie  Ekvivalenty v různých jazycích (C#, Java, CORBA,...)

380 Design patterns  Design patterns  Původním záměrem je jejich používání při návrhu  Je to návod, případně vzorová implementace  Není to knihovna ani polotovar k úpravě  Klasické návrhové vzory nevyužívají C++ šablony  Ani "template function" není šablona  Některé (ne všechny) vzory lze univerzálně implementovat jako šablony  Zkušenost je obsažena především v pravidlech určujících, kdy je vhodné daný vzor použít  V originále: "Intent" + "Applicability"

381 Design patterns  Struktura  Adapter Objekt upravující rozhraní objektu  Bridge Oddělení implementace objektu od jeho rozhraní  Composite Rozhraní umožňující jednotný přístup k celkům i částem určité hierarchické struktury  Decorator Třída podílející se na implementaci objektu  Facade Objekt reprezentující část rozhraní objektu  Flyweight Objekt snižující objem dat pomocí sdílení  Proxy Objekt zpřístupňující jiný objekt se stejným rozhraním

382 Design patterns  Vytváření objektů  Abstract factory Vytváření rodin objektů bez určení jejich konkrétních tříd  Builder Vytváření složených objektů  Factory method Vytváření objektu bez určení konkrétní třídy  Prototype Objekt se schopností vytvářet vlastní kopie  Singleton Třída s jedinou instancí a globálním přístupem k ní

383 Design patterns  Stav a synchronizace  Iterator Objekt umožňující procházení datovou strukturou  Mediator Objekt evidující vztahy jiných objektů  Memento Záznam vnitřního stavu objektu určený k pozdějšímu obnovení  Observer Synchronizace objektu a jeho pozorovatele  State Oddělení funkcí a stavu objektu

384 Design patterns  Zprávy a příkazy  Command Zhmotněný pokyn k volání funkce/metody s parametry  Interpreter Převodník zpráv  Multicast Předávání zpráv dynamicky registrovaným příjemcům  Typed message Typově bezpečné předávání zpráv různého druhu  Visitor Rozšiřitelná náhrada virtuální funkce

385 Design patterns  Implementace funkcí  Chain of responsibility Soustava funkcí podílejících se na implementaci jedné akce  Generation gap Úpravy chování neupravitelné třídy  Strategy Objekt určený ke specializaci univerzálního postupu  Template method Univerzální algoritmus s modifikovatelnými částmi

386 Šablony Hlubší pohled

387 ***Nepoužité slajdy***

388 Teoretický pohled na šablony  Triky s typovým konstrukcemi  Seznam typů template struct List { typedef H Head; typedef R Rest; }; struct EmptyList {};  Použití typedef List< char *, List< const char *, List< std::string, EmptyList> > > StringTypes;

389 Teoretický pohled na šablony  Triky s typovým konstrukcemi  Seznam typů template struct List { typedef H Head; typedef R Rest; }; struct EmptyList {};  Jiné použití struct Apple {}; struct Pear {}; struct Plum {}; typedef List< Apple, List< Pear, List< Plum, EmptyList> > > Fruits;

390 Teoretický pohled na šablony  Triky s typovým konstrukcemi  Seznam typů template struct List { typedef H Head; typedef R Rest; }; struct EmptyList {};  Funkce na seznamu typů template struct First { typedef typename L::Head Result; };

391 Teoretický pohled na šablony  Triky s typovým konstrukcemi  Seznam typů template struct List { typedef H Head; typedef R Rest; }; struct EmptyList {};  Funkce na seznamu typů template struct First { typedef typename L::Head Result; }; struct NullType {}; template<> struct First { typedef NullType Result; };

392 Teoretický pohled na šablony  Triky s typovým konstrukcemi  Seznam typů template struct List { typedef H Head; typedef R Rest; }; struct EmptyList {};  Funkce na seznamu typů template struct Nth { typedef typename Nth ::Result Result; }; template struct Nth { typedef typename L::Head Result; };

393 Teoretický pohled na šablony  Triky s typovým konstrukcemi  Jiná implementace seznamu typů template struct List; struct EmptyList;  Funkce na seznamu typů template struct Nth; template struct Nth, n> { typedef typename Nth ::Result Result; }; template struct Nth, 0> { typedef H Result; };

394 Teoretický pohled na šablony  Výpočty při kompilaci  Data:  celá čísla  typy  Funkcionální programování:  Funkce bez vedlejších efektů  Neexistuje přiřazovací příkaz "Proměnné" se nemění  Rekurze  Odlišného chování funkcí pro různé hodnoty parametrů se dociluje definováním několika těl funkcí (tj. šablon)  Výpočty za běhu  Data:  celá i reálná čísla, struktury  ukazatelé  Procedurální programování:  Procedury s vedlejšími efekty  Destruktivní přiřazení Proměnné se mění  Podmínky, cykly, rekurze  Odlišného chování procedur pro různé hodnoty parametrů se dociluje podmínkami uvnitř

395 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován polem  Slabě bezpečná implementace:  Při výjimce v konstruktoru proměnné top se nestane nic  Při výjimce v new korektně zanikne proměnná top a zůstane zachován původní stav  Funkce swap ani operator delete nezpůsobují výjimky class StringStack { public: //... private: String * p_; int n_; }; String StringStack::pop() { if ( ! n_ ) return String(); String top = p_[ n_-1]; String * p2 = new String[ n_-1]; for ( int i = 0; i < n_-1; ++i) swap( p2[ i], p_[ i]); swap( p_, p2); --n_; delete p2; return top; }

396 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován polem  Slabě bezpečná implementace  Není silně bezpečná:  Funkce vrací hodnotou  Pokud při vracení dojde k výjimce v copy-constructoru, zásobník již bude zkrácen class StringStack { public: //... private: String * p_; int n_; }; String StringStack::pop() { if ( ! n_ ) return String(); String top = p_[ n_-1]; String * p2 = new String[ n_-1]; for ( int i = 0; i < n_-1; ++i) swap( p2[ i], p_[ i]); swap( p_, p2); --n_; delete p2; return top; }

397 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován polem  Slabě bezpečná implementace  Není silně bezpečná:  Funkce vrací hodnotou  Pokud při vracení dojde k výjimce v copy-constructoru, zásobník již bude zkrácen  Tuto výjimku lze ošetřit try-blokem okolo příkazu return Uvést zásobník do původního stavu Ale: co když se uvedení do původního stavu nezdaří? String StringStack::pop() { if ( ! n_ ) return String(); String top = p_[ n_-1]; String * p2 = new String[ n_-1]; for ( int i = 0; i < n_-1; ++i) swap( p2[ i], p_[ i]); swap( p_, p2); --n_; delete p2; try { return top; } catch (...) { push( top); throw; }

398 Exception-safe programming  Příklad: StringStack::pop  Zásobník prvků typu String  Implementován polem  Nefunkční implementace  Není silně bezpečná:  Funkce vrací hodnotou  Pokud při vracení dojde k výjimce v copy-constructoru, zásobník již bude zkrácen  Tuto výjimku lze ošetřit try-blokem okolo příkazu return  Dokážeme udělat obnovení původního stavu bez nebezpečí výjimky Ale: jak zrušíme proměnnou p2, když k výjimce nedojde? String StringStack::pop() { if ( ! n_ ) return String(); String top = p_[ n_-1]; String * p2 = new String[ n_-1]; for ( int i = 0; i < n_-1; ++i) swap( p2[ i], p_[ i]); swap( p_, p2); --n_; // tady bylo delete p2; try { return top; } catch (...) { for ( int i = 0; i < n_-1; ++i) swap( p2[ i], p_[ i]); ++n_; swap( p_, p2); delete p2; throw; }

399 Oblasti platnosti identifikátorů  Oblasti platnosti identifikátorů  Modul/namespace: Funkce, proměnné, typy a výčtové konstanty  Třída: Metody, položky (příp. typy a výčtové konstanty)  Funkce (blok): Parametry a lokální proměnné (příp. typy a výčtové konstanty)  Pořadí vyhledávání identifikátoru  Nejprve ve funkci: Od nejvnitřnějšího k hlavnímu bloku  Poté v třídách (uvnitř metod): Od potomků k předkům  Nakonec v modulu resp. aktuálním namespace S uplatněním direktiv using

400 Ochrana přístupu  Vztah vyhledávání identifikátoru a kontroly přístupu  V definovaném pořadí se najde první oblast platnosti, ve které se identifikátor vyskytuje Namespace slité direktivami using se považují za jednu oblast platnosti  Jedná-li se o přetížený identifikátor funkce, vybere se v nalezené oblasti odpovídající varianta Neuplatní se tudíž jiné varianty v jiných oblastech, které by jinak byly viditelné  Aplikuje se mechanismus ochrany přístupových práv. Pokud tedy nejlépe padnoucí varianta není přístupná, kompilátor ohlásí chybu bez ohledu na případnou existenci jiné aplikovatelné a přístupné varianty

401 Preprocesor a překlad Code... push call... call... ret Import _printf _getch Export _main Data ‘H’,’e’,’l’,’l’, ’o’,10,0 hello.o hello.i /*...*/ int printf( const char *,...); /*...*/ int getch(); int putch(); /*...*/ int main( int argc, char * * argv) { printf( “Hello\n”); getch(); return 0; } stdio.h /*...*/ int printf( const char *,...); /*...*/ hello.c #include int main( int argc, char * * argv) { printf( “Hello\n”); getch(); return 0; } conio.h int getch(); int putch(); /*...*/

402 Spojování modulů Code... push call... call... ret Import _printf _getch Export _main Data ‘H’,’e’,’l’,’l’, ’o’,10,0 hello.o Code... call... call Import _main _exit Export entry point Data cstart.o Code... Import Export _printf Data printer.o Code... Import Export _getch Data creader.o Code syscall... Import Export _exit Data exit.o

403 Spustitelný program a spuštěný proces... push call... call... ret ‘H’,’e’,’l’,’l’, ’o’,10,0 hello... call... call entry point... syscall... Code Data Code Data Heap Stack Adresový prostor IP R0 R1... FFFFFFFF

404 ***** C++ *****  Identifikátory a kontexty  Zapouzdření  Dědičnost  Přetěžování funkcí  Předefinování operátorů  Objekty  Konstruktory a destruktory  Pozdní vazba (late binding)  Virtuální funkce  Neviditelné konverze  Drobná vylepšení  Striktnější typová kontrola oproti C  new a delete  Komentáře  Šablony  Zpracování výjimek  Knihovny  Streams

405 Virtuální funkce – Specializace datové struktury class HashTable { public: void add( const void * key, int keylen, const void * data, int datalen); protected: virtual long hash( const void * key, int keylen); private: SmartArray _tbl; }; class StringTable : public HashTable { public: void add( const char * key, const void * data, int datalen); protected: virtual long hash( const void * key, int keylen); };

406 Virtuální funkce – Užití datové struktury class Catalog : private StringTable { public: void add( const char * isbn, const char * author, const char * title); private: virtual long hash( const void * key, int keylen); }; class Catalog { public: void add( const char * isbn, const char * author, const char * title); private: StringTable _t; };

407 Virtuální funkce - Řešení v C-stylu  Abstraktní třída struct File { int handle; int (* readf)( File * p); void (* writef)( File * p, int x); }; int read( File * p) { return p->readf( p); }  Specializace int bread( File * p) { /*... */ } File * bopen() { File * p = new File; p->handle = /*...*/; p->readf = bread; p->writef = bwrite; return p; }

408 Virtuální funkce - Řešení v C-stylu  Jiná specializace struct TFile { File f; int info; }; int tread( File * p) { TFile * tp = (TFile *)p; /*... */ } File * topen() { TFile * tp = new TFile; tp->f.handle = /*... */; tp->f.readf = tread; tp->f.writef = twrite; tp->info = /*... */ return &tp->f; }

409 Virtuální funkce - Řešení v C-stylu  Lepší implementace struct VTFile { int (* readf)( File * p); void (* writef)( File * p, int x); }; struct File { int handle; const VTFile * vt; }; const VTFile VTBFile = { bread, bwrite }; const VTFile VTTFile = { tread, twrite };

410 Virtuální funkce - Řešení v C++  Abstraktní třída class File { int handle; public: virtual int readf() = 0; virtual void writef( int x) = 0; }; int read( File * p) { return p->readf(); }  Specializace class BFile : public File { virtual int readf(); virtual void writef( int x); }; int BFile::readf() { /*... */ } File * bopen() { BFile * p = new BFile; p->handle = /*... */; return p; }

411 Virtuální funkce - Řešení v C++  Jiná specializace class TFile : public File { int info; virtual int readf(); virtual void writef( int x); }; int TFile::readf() { /*... */ } File * topen() { TFile * p = new TFile; p->handle = /*... */; p->info = /*... */; return p; }

412 Statické a dynamické volání virtuálních metod void x() { A a;/* A::A */ B b;/* B::B */ A & raa = a; A & rab = b; B & rbb = b; A * paa = &a; A * pab = &b; B * pbb = &b; a.f(); /* A::f (c) */ b.f(); /* B::f (c) */ raa.f();/* A::f (r) */ rab.f();/* B::f (r) */ rbb.f();/* B::f (r) */ paa->f();/* A::f (r) */ pab->f();/* B::f (r) */ pbb->f();/* B::f (r) */ b.A::f();/* A::f (c) */ b.B::f();/* B::f (c) */ paa->A::f(); /* A::f (c) */ pab->A::f(); /* A::f (c) */ pbb->A::f(); /* A::f (c) */ pbb->B::f(); /* B::f (c) */ raa.A::f();/* A::f (c) */ rab.A::f();/* A::f (c) */ rbb.A::f();/* A::f (c) */ rbb.B::f();/* B::f (c) */ }