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

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

Pokročilé programování v C++ (část B)

Podobné prezentace


Prezentace na téma: "Pokročilé programování v C++ (část B)"— Transkript prezentace:

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

2 C++11

3 C++11 Jazyk Knihovny && (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< formální-parametry> lze použít před několika formami deklarací; oblastí platnosti formálních parametrů je celá prefixovaná deklarace

6 Šablony funkcí lze volat dvěma způsoby
Š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< typename T, int k> // parametry šablony int f( T * p, int q); // parametry funkce template< typename T, typename U> // parametry šablony int g( T * p, vector< U> q); // parametry funkce Šablony funkcí lze volat dvěma způsoby Explicitně f< int, 729>( 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< char, false, true> my_file; template< typename P> 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_policy> my_file;

8 Policy class Policy class
template< typename P> 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_policy> 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< typename T> class X { typedef typename T::B U; // T::B je typ typename U::D p; // T::B::D je typ typename Y<T>::C q; // Y<T>::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 Pokud je předkem třídy závislé jméno
Š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< typename T> 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ů Při ekvivalenci typů rozhodují jména tříd/struktur
template< typename P> 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_policy> my_file; struct my_policy2 { typedef char T; static const bool binary = false; static const bool cached = true; }; void f( File< my_policy2> & 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< my_policy> a File< my_policy2>

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

13 Využití neekvivalence typů
template< typename P> class Value { double v; // ... }; struct mass {}; struct energy {}; Value< mass> m; Value< energy> 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< int kg, int m, int s> class Value { double v; // ... }; template< int kg1, int m1, int s1, int kg2, int m2, int s2> Value< kg1+kg2, m1+m2, s1+s2> operator*( const Value< kg1, m1, s1> & a, const Value< kg2, m2, s2> & b); typedef Value< 1, 0, 0> Mass; typedef Value< 0, 1, -1> Velocity; typedef Value< 1, 2, -2> 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< int kg, int m, int s> struct Unit { static void print( ostream & os) { os << ”kg^” << kg << ”.m^” << m << ”.s^” << s; } }; template<> struct Unit< 1, 2, -2> { 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< int kg, int m, int s> ostream & operator<<( ostream & os, const Value< kg, m, s> & x) { os << x << “ “; Unit< kg, m, s>::print( os); return os; }

16 Parciální specializace
template< class X, class Y> 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 T, class U, int n> class C< T[n], U[n]> { /* specializace pro dvě pole stejné velikosti */ }; Explicitní specializace template<> class C< char, int[ 8]> { /* ... */ }; 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<char> 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< bool> 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<T> volá šablonu char_traits<T>, 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<T> volá šablonu char_traits<T>, ve které je např. definována porovnávací funkce template< class T> struct char_traits; template< class T> class basic_string { /* ... */ int compare( const basic_string< T> & b) const { /*...*/ char_traits< T>::compare( /* ... */) /*...*/ } }; template<> struct char_traits< char> { /* ... */ 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<T> doplňuje informace o typu T, např. porovnávací funkci

20 Šablony, ze kterých nejsou vytvářeny objekty Obsahují pouze:
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<T> 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< typename P, typename K>
void for_each( K & data) { for( ... it = ... ) P::f( * it); } struct policy_add { static void f(...) ... }; my_k data; for_each< policy_add>( data); template< typename K> struct for_each_traits; void for_each( K & data) { for( ... it = ... ) for_each_traits< K>::f( * it); } template<> struct for_each_traits< my_k> { static void f(...) ... }; my_k data; for_each( data);

22 Polymorfismus Kompilační a běhový

23 Stejný zdrojový kód v různých situacích dělá různé věci
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 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< typename P> 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< typename P> void for_each( P & x) { for( ... it = ... ) x.f( it); // metoda } struct functor_add { void f(...) ... }; for_each( functor_add()); template< typename P> void for_each() { for( ... it = ... ) P::f( it); // statická funkce } struct policy_add { static void f(...) ... }; for_each< policy_add>();

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< int N> struct Fib { static const int value = Fib< N-1>::value + Fib< N-2>::value; }; template<> struct Fib< 0> { static const int value = 1; template<> struct Fib< 1> { Kontrolní otázka: Jak dlouho trvá výpočet (tj. kompilace) Fib< 1000>::value

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

30 C++11: <type_traits>
Triky s šablonami Podmíněný výraz nad typy template< bool C, typename A, typename B> struct conditional { typedef A type; }; template< typename A, typename B> struct conditional< false, A, B> { typedef B type; conditional< C, A, B>::type je typ Použití template< bool wide> class File { typename conditional< wide, wchar_t, char>::type get(); /* ... */ C++11: <type_traits>

31 C++11: <type_traits>
Triky s šablonami Porovnání typů s booleovským výstupem template< class A, class B> struct is_same { static const bool value = false; }; template< class A> struct is_same< A, A> { static const bool value = true; is_same< X, Y>::value je konstantní výraz Použití template< class T1> class Test { static const bool very_long = is_same< long long, T1>::value; typedef conditional< is_long, unsigned long long, unsigned long> T; /* ... */ C++11: <type_traits>

32 C++11: vestavěno v jazyce
Triky s šablonami Kompilační ověření invariantu template< bool x> struct static_assert { struct type {}; }; template<> struct static_assert< false> { template< int x> struct Assert { typename static_assert< (x > 0)>::type ignore_me(); 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< typename ... TList> class C { /* ... */ }; pojmenovaný parametr zastupující seznam typů lze i kombinovat s pevnými parametry template< typename T1, int c2, typename ... TList> class D { /* ... */ }; platí i pro hlavičky parciálních specializací template< typename T1, typename ... TList> class C< T1, TList ...> { /* ... */ }; C++11

35 Šablony s proměnlivým počtem parametrů
template< typename ... TList> 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< TList ...> Y< int, TList ..., double> 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< typename ... TList> 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< typename ... TList> 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< std::pair< int, TList *> ...> class E : public U< TList> ... void f( const TList & ... plist); seznam výrazů ve volání funkce/metody/konstruktoru g( make_pair( 1, & plist) ...); h( static_cast< TList *>( plist) ...); i( sizeof( TList) ...); seznam inicializátorů v konstruktoru další okrajové případy C++11

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

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

40 Doplnění rvalue referencí do standardních knihoven
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 Srozumitelný způsob použití
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. 2002 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 Operátor přiřazení by měl udělat toto:
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< Matrix> b; /* ... */ a = a; b[ i] = b[ j]; Při kopírovaní pravá strana už nebude existovat

46 Amatérské řešení operátoru přiřazení:
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) Matrix::~Matrix() clean();

47 Lepší řešení operátoru přiřazení:
Poučení - operator= Lepší řešení operátoru přiřazení: #include <algorithm> 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 před C++11 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 před C++11 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 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 před C++11 Poučení - operator+ implementováno předáním ukazatele
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 před C++11 Poučení - operator+ implementováno takto: a = b + c + d;
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 před C++11 Poučení - operator+ šikovný překladač eliminuje proměnnou t
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 před C++11 Poučení - operator+ po eliminaci t3, t4: a = b + c + d;
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 před C++11 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 před C++11 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( & t1); před C++11

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

57 C++11 Ř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 C++11 Ř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 C++11 Ř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 Speciální metody tříd – C++11
copy/move Speciální metody tříd – C++11

61 C++11 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 C++11 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 C++11 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 C++11 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( T && x); T & operator=( T && x); C++11

65 C++11 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< U>) 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 C++11 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 C++11 lvalue/rvalue U výrazu překladač zkoumá Lvalue typu T
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 C++11 lvalue/rvalue Pravidla předávání parametrů
Kategorie a typ výrazu lvalue rvalue T const T Typ parametru T, const T OK T & OK (+) --- const T & OK (-) T && const T && 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, ...)

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) C++11

77 lvalue/rvalue Nové rozhraní STL

78 C++11 lvalue/rvalue - STL Nová implementace swap move
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< T &&>( b); C++11

79 C++11 lvalue/rvalue - STL Copy/move insertion Emplace
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< typename ... TList> 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 C++11 Perfect forwarding new( q) je starý trik: placement new
template< typename ... TList> 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 C++11 Perfect forwarding Jak dokáže emplace předat parametry?
template< typename ... TList> 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 C++11 Perfect forwarding Trik: Skládání referencí
Jazyk definuje tato pravidla Použijí se u šablon funkcí s parametrem typu T && template< typename T> 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 X & & X & X && & X & && X && && X &&

84 C++11 Perfect forwarding Forwarding
template< typename T> 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 C++11 Perfect forwarding Perfect forwarding
template< typename T> void f( T && p) { g( std::forward< T>( p)); } std::forward< T> vrací T && X lv; f( lv); T = X & skládání referencí zajistí, že std::forward< T> vrací X & f( std::move( lv)); T = X std::forward< T> vrací X && v tomto případě se std::forward< T> chová jako std::move C++11

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

87 Smart pointers

88 C++11 Smart pointers Chytré ukazatele Výlučné vlastnictví
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< T> Varianta pro pole: Operátor [] namísto * a -> std::unique_ptr< T[]> Sdílené vlastnictví Založeno na počítání odkazů std::shared_ptr< T> Vedlejší odkaz, který se nepočítá (počítání zdržuje) std::weak_ptr< T> Lze povýšit na shared_ptr, jinak je ekvivalentní s T * C++11

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

90 Lambda

91 C++11 Lambda výrazy Motivace Řešení class ftor { public:
ftor(int a, int b) : a_(a),b_(b) { } bool operator()(int x) const { return x*a_<b_; } private: int a_, b_; }; typedef std::vector<int> v_t; v_t v; v_t::iterator vi=remove_if(v.begin(), v.end(), ftor(m, n)); Řešení std::vector<int> v; auto vi=remove_if(v.begin(), v.end(), [=](int x){ return x*m<n; }); C++11

92 C++11 Lambda výrazy Lambda výraz Deklaruje třídu ve tvaru
[ 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 ]( 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(); Co to vypíše? C++11 123234

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< int> v; for_each( v.begin(), v.end(), clr); std::array< int, N> a; for_each( a.begin(), a.end(), clr); int p[ N]; for_each( p, p + N, clr); C++11 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, 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< iterator> 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::ostream_iterator( o)); input category istream_iterator std::istream & i = ...; std::copy( std::istream_iterator( i), std::istream_iterator(),

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<class Iterator> 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<class T> struct iterator_traits<T*> { 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 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< T> #include <memory> class String { /*...*/ std::unique_ptr< char[]> 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 <algorithm> 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 <algorithm> 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 <algorithm> String::String( String && b) : str_( std::move( b.str_)) { } String & String::operator=( 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< T> #include <memory> String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); std::unique_ptr< Box> 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 */ stk.pop(); /* chyba zkracování, proměnná a změněna,

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 <memory> class StringStack { public: /* A */ String & top(); void pop(); /* B */ void pop( String & out) { String & t = top(); swap( out, t); try { pop(); } catch (...) { 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< typename T> void swap( T & a, T & b) noexcept( is_nothrow_move_constructible<T>::value && is_nothrow_move_assignable<T>::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 Oba případy jsou překládány správně
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 Koenigovo vyhledávání (zjednodušeno)
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 Koenigovo vyhledávání (zjednodušeno)
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 Zpřístupníme téměř vše, co dokáže „hardware“
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 Real programmers can write assembly code in any language.
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. 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 Číselné typy (char, int, double,...) jsou dobře zapouzdřené
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 Kanonické tvary tříd 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+(

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+(

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< int> 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 { PrintVisitor( Printer * p) : p_( p) {} virtual void visitEllipse( Ellipse *); virtual void visitRectangle( Rectangle *); virtual void visitLine( Line *); private: Printer * p_;

168 Dynamicky alokovaná data
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< node_id> nodes_; multimap< node_id, edge_id> edges_; map< edge_id, color> 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 { 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 { 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; }; class NodeRef { TreeNode * p; Tree() : root( 0) {} ~Tree() { /* ... */ } NodeRef create_node( /* ... */); void insert_node( NodeRef p); NodeRef find_node( /* ... */ ); 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=( };

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 Neinstanciované třídy
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< typename T> struct type_traits; template<> struct type_traits< char> { 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 policy> 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_policy> my_tree; Traits template – použití Deklarace traits template< class T> struct type_traits; Univerzální šablona template< class T> class Stack { /* ... */ void push( typename type_traits::param_t x); }; Univerzální definice traits struct type_traits { typedef const T & param_t; Explicitní specializace traits template<> struct type_traits< char> { typedef char param_t;

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

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

182 Lambda (C++0x) template< typename P> 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< typename T>
T operator()( T x) const { return x; } }; extern functor_1 _1; struct functor_const { functor_const( T c) : c_( c) {} return c_; T c_; functor_const< T> constant( T c) { return functor_const< T>( c); } template< typename F1, typename F2> struct functor_mul { functor_mul( F1 f1, F2 f2) : f1_( f1), f2_( f2) {} T operator()( T x) const { return c_; } F1 f1_; F2 f2_; }; functor_mul< F1, F2> operator+ ( F1 f1, F1 f2) return functor_const< F1, F2> ( 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< typename T> class X { X( const X< T> &); }; X( const X &); Některé překladače připouštějí i tuto variantu X< T>( const X< T> &);

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< typename T> void X< T>::f( int a, int b) { /* ... */ } V kvalifikovaném jméně metody je nutné uvést patřičný seznam argumentů, tj. X< T>::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< typename Container> 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< 128, char>;

191 Dopředná deklarace šablony
Šablony tříd – triky Dopředná deklarace šablony template< typename T> class X; /* ... zde může být použito X<U> s jakýmikoliv argumenty U... ... pouze v kontextech, kde kompilátor nepotřebuje znát tělo šablony ... */ template< typename T> 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< class T> 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< int n, class T> T max( Array< n, T> a) { /* ... */ } Příklad ze standardních knihoven: template< class T> 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< int n> class Array< n, bool> { /* specializace pro pole typu bool */ }; Krajním případem parciální specializace je explicitní specializace Explicitní specializace template<> class Array< 32, bool> { /* ... */ }; U šablon funkcí nahrazena obyčejnou funkcí Explicitní instanciace Překladač je možné donutit ke kompletní instanciaci šablony template class Array< 128, char>;

194 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 X, class Y> class C; // základní deklarace template< class P, class Q> class C< P *, Q *> { // specializace bool cmp( P *, Q *); }; template< class Z> class C< Z, Z> : public Z { // jiná specializace bool set( Z &); template< class X, class Y> class C { // základní definice X add( Y);

195 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 T, class U, int n> class C< T[n], U[n]> { /* specializace pro dvě pole stejné velikosti */ };

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

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

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

199 Teoretický pohled na šablony
Příklad template< int N> struct Fact { enum { value = Fact< N-1>::value * N }; }; template<> struct Fact< 0> { enum { value = 1 }; Kontrolní otázka: Kolik je Fact< -1>::value MS Visual C++ 7.1: fatal error C1202: recursive type or function dependency context too complex Řetěz instanciací Fact< -1>, Fact< -2>, Fact< -3>, ... 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 T> class X { /* ... */ void f(/*...*/); }; Nepublikovaný hlavičkový soubor XBody.h Generická těla metod #include "X.h" template< class T> void X< T>::f(/*...*/) { /*...*/ } Knihovní modul XBodyInt.cpp Instanciace pro typ int #include "XBody.h" template X< int>;

201 Teoretický pohled na šablony
Šablona třídy je kompilátorem vyhodnocovaná funkce f : Ti  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 : Ti  T T je množina všech typů zkonstruovatelných v jazyce C f : Ti × Kj  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 : Ti  T T je množina všech typů zkonstruovatelných v jazyce C f : Ti × Kj  T šablona s celočíselnými parametry K je množina všech celočíselných konstant f : Ti × Kj  Tm × Kn výsledná třída může obsahovat typy a konstanty

204 Teoretický pohled na šablony
Šablona třídy je kompilátorem vyhodnocovaná funkce f : Ti × Kj  Tm × Kn 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 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 <exception> 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 <algorithm> 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< T> "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 <memory> String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); std::auto_ptr< Box> 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 = ...; 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(...); change_3(...); change_4(...); } catch(...) { rollback_change_3(...); rollback_change_2(...); 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 */ stk.pop(); /* chyba zkracování, proměnná a změněna,

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 <memory> class StringStack { public: /* A */ String & top(); void pop(); /* B */ void pop( String & out) { String & t = top(); swap( out, t); try { pop(); } catch (...) { throw; };

250 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
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
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
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 Pravidla pro začátečníky Kdy použít referenci: T &
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<T>::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<T>::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 Pravidla pro pokročilejší Vlastník dynamicky alokovaného objektu
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<T>” 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 Pravidla pro vracení hodnot odkazem
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<T>::back(); const T & vector<T>::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 Konstruktor bez parametrů (default constructor)
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 Operátor přiřazení (assignment operator)
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á 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 Konverzní konstruktory
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 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 V jiné situaci není virtuálnost funkcí užitečná
Virtuální funkce class Base { virtual void f() { /* ... */ } }; class Derived : public Base { 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 { 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 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
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
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
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
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
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 Použití
template< int n, class T> class Array { T p[ n]; T dummy; public: T & operator[]( int x) { return x<n ? p[x] : dummy; } }; Použití Array< 5, int> a; Array< 7, int> b; Array< 5 + 2, int> c; Array< 3, Array< 7, int> > d; a[ 3] = b[ 3]; a = b; // chyba !!! b = c; // OK, implicitní copy-constructor d[ 2][ 3] = 1;

277 Poučení – 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é double operator+( double a, 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
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); // <complexmath.h> ale také Complex::operator double() const; double sin( double x); // <math.h> Complex( double re, double im = 0.0); První varianta má přednost... ...ale když zapomenete #include <complexmath.h> ...

279 C++

280 Zobecnění pojmu struktura (struct)
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 Různé pohledy na třídy a různá pojmenování
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
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
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
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
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
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
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
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
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 sloužící jako datové typy Proměnné typu T
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 < n_; i++) objects_[ 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; 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< GraphicObject> 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< GraphicObject *> objects_; };

316 Ukázková implementace č. 1 (nepoužitelná)
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 Ukázková implementace č. 2 (naivní, neefektivní)
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) String::~String() clean();

324 Poučení - konstruktory
Lepší řešení operátoru přiřazení: #include <algorithm> 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 Ukázková implementace č. 3 (counted-pointers)
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(); return * this; String::~String()

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 < 0 || pos >= _body->length() ) return 0; return _body->buffer()[ _pos]; }; /* ... */

334 String č. 4 - operator [ ] - zápis
class String { public: char & operator[]( int pos) { if ( pos < 0 || 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 < 0 || 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();

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(); 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 { 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); }; 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++
<assert.h> <cassert> - ladicí funkce (makro assert) <ctype.h> <cctype> - klasifikace znaků (isalpha, isspace, ...) <errno.h> <cerrno> - chybové kódy (ENOMEM, ...), proměnná errno <float.h> <cfloat> - vlastnosti a limity reálných typů (DBL_MAX, ...) <limits.h> <limits> <climits> - limity celočíselných typů (INT_MAX, ...) <locale.h> <locale> <clocale> - přizpůsobení národnímu prostředí <math.h> <cmath> - matematické funkce (sin, ...) <setjmp.h> <csetjmp> - meziprocedurální skoky (setjmp, longjmp) <signal.h> <csignal> - signály operačního systému <stdarg.h> <cstdarg> - makra pro funkce s proměnným počtem argumentů <stddef.h> <cstddef> - užitečné typy a konstanty (NULL) <stdio.h> <cstdio> - standardní a souborový vstup a výstup <stdlib.h> <cstdlib> - užitečné funkce (malloc, ...) <string.h> <cstring> - manipulace s řetězci (strcpy, ...) <time.h> <ctime> - konverze data a času <wchar.h> <cwchar> - 16-bitové řetězce (wchar_t) <wctype.h> <cwctype> - klasifikace 16-bitových znaků

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

345 STL – Ostatní <algorithm.h> <algorithm> - užitečné algoritmy (for_each, sort, next_permutation, ...) <functional.h> <functional> - podpora funktorů <iterator.h> <iterator> - podpora iterátorů <memory.h> <memory> - alokátory pro kontejnery <numeric.h> <numeric> - jednoduchá matematika na prvcích kontejnerů <utility.h> <utility> - pomocné konstrukce (pair,...)

346 vstupní a výstupní proudy
iostream vstupní a výstupní proudy

347 iostream Manipulátory hex - šestnáctkový výpis, setw - počet míst
#include <iostream> #include <iomanip> 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 <sstream> - *stringstream – spolupracuje se std::string
#include <string> #include <sstream> #include <iomanip> using namespace std; string f( int a) { ostringstream x; x << "a = " << a; return x.str(); } <sstream> - *stringstream – spolupracuje se std::string <strstream> - *strstream – spolupracuje s char *

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

350 basic_...<T> jsou šablony
iostream Abstraktní rozhraní basic_...<T> 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 dědičnost virtuální dědičnost nastavení formátovače ios_base ukazatel stav média basic_ios<T> basic_streambuf<T> virtuální funkce čtení a zápisu basic_istream<T> basic_ostream<T> přímé a formátované čtení přímý a formátovaný zápis basic_iostream<T>

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

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

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

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

355 iostream Médium Konkrétní médium je implementováno jako potomek třídy basic_streambuf<T> 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) stav formátovače ios_base stav média basic_ios<T> basic_streambuf<T> virtuální funkce čtení a zápisu basic_istream<T> basic_ostream<T> přímé a formátované čtení přímý a formátovaný zápis basic_...buf<T> basic_iostream<T> médium, implementace čtení a zápisu

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

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

358 istream/ostream - neformátované čtení/zápis
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 Oba operátory vrací levý operand
iostream formátované čtení basic_istream<T> & operator>>( basic_istream<T> & 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<T> & operator<<( basic_ostream<T> & 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 formátované čtení/zápis
iostream formátované čtení/zápis basic_istream<T> & operator>>( basic_istream<T> & s, D & x) basic_ostream<T> & operator<<( basic_ostream<T> & 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 Definování vlastních formátovacích operátorů
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 Jak fungují manipulátory
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<T> & operator<<( basic_ostream<T> & s, ios_base & (* f)( ios_base & s)) { f( s);

363 Jak fungují manipulátory
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<T> & operator<<( basic_ostream<T> & s, const setw_manip & p) { s.width( p.x); return s;

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

365 Iterátory nad proudy - příklady naplnění kontejneru ze vstupu
iostream Iterátory nad proudy - příklady naplnění kontejneru ze vstupu std::vector< double> a; std::copy( std::istream_iterator< double>( std::cin), // aktuální pozice std::istream_iterator< double>(), // "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< std::string> b; b.begin(), b.end(), std::ostream_iterator< std::string>( std::cout));

366 Typová informace za běhu
RTTI Typová informace za běhu

367 RTTI Operátor typeid Identifikace typu Vrací identifikaci typu T
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 & <typeinfo> Lze porovnávat na rovnost Má metodu name() vracející řetězec s nějakou formou jména typu

368 Alternativa místo dynamic_cast
RTTI Typické použití Alternativa místo dynamic_cast #include <typeinfo> 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< X *>( p); /* ... */ } if ( typeid( * p) == typeid( Y) ) { Y * yp = static_cast< Y *>( p); /* ... */ }

369 Alternativa místo dynamic_cast
RTTI Typické použití Alternativa místo dynamic_cast Pozor: rovnost typeid nereflektuje dědičnost Zatímco dynamic_cast ano #include <typeinfo> 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< X *>( p); /* ... */ } if ( typeid( * p) == typeid( Z) ) // platí { Z * zp = static_cast< Z *>( p); /* ... */ }

370 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 Příklad
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 { void doitforall( Visitor &); /* ... */

372 Visitor Visitor - použití class Ellipse; class Rectangle; class Line;
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 { 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 { 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 { 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 { void doitforall( Visitor &); /* ... */

376 Visitor Visitor - implementace class Ellipse; class Rectangle;
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 { void doitforall( Visitor &); /* ... */ private: typedef vector< AbstractObject *> 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 Původním záměrem je jejich používání při návrhu
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 Bridge Composite Decorator Facade
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 Builder
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 Mediator Memento
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 Interpreter Multicast
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< class H, class R> 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< class H, class R> 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< class H, class R> struct List { typedef H Head; typedef R Rest; }; struct EmptyList {}; Funkce na seznamu typů template< class L> struct First { typedef typename L::Head Result;

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

392 Teoretický pohled na šablony
Triky s typovým konstrukcemi Seznam typů template< class H, class R> struct List { typedef H Head; typedef R Rest; }; struct EmptyList {}; Funkce na seznamu typů template< class L, int n> struct Nth { typedef typename Nth< typename L::Rest, n-1>::Result Result; template< class L> struct Nth< L, 0> { typedef typename L::Head Result;

393 Teoretický pohled na šablony
Triky s typovým konstrukcemi Jiná implementace seznamu typů template< class H, class R> struct List; struct EmptyList; Funkce na seznamu typů template< class L, int n> struct Nth; template< class H, class R, int n> struct Nth< List< H, R>, n> { typedef typename Nth< R, n-1>::Result Result; }; template< class H, class R> struct Nth< List< H, R>, 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 ( ...) ++n_; delete p2; throw;