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

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

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

Podobné prezentace


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

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

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

3 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ů

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

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

6 Ř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

7 Ř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

8 Ř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

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

10 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

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

12 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

13 copy/move  Nepoužívejte T * s vlastnickou semantikou  Použijte unique_ptr nebo vector  Třída s hodnotovou semantikou pomocí unique_ptr  Napište vlastní copy-metody  Vynuťte default pro move-metody C++11

14 copy/move  R-value reference jsou nutné  pokud typ parametru nepodporuje kopírování  R-value reference jsou užitečná optimalizace  bude-li hodnota parametru někde uložena  lze-li prostor okupovaný parametrem využít pro výsledek  v těchto případech je nutné napsat dvě varianty funkce  v ostatních případech stačí varianta "const T &"  Předání hodnotou nemusí být horší než odkazem  pokud je hodnota použita k inicializaci (s pomocí move)  my_class( std::string a, std::string b)  : a_( std::move( a)), b_( std::move( b))  {} C++11

15 lvalue/rvalue Pravidla

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

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

18 Předávání odkazem Skutečný parametr Deklarovaný parametr Použití parametru T & const T & T && lvalue T lvalue const T rvalue T lvalue T lvalue const T rvalue T std::move

19 Variadic templates

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

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

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

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

24 lvalue/rvalue Perfect forwarding

25 template iterator emplace( const_iterator p, TList &&... plist) { void * q = /* místo pro nový prvek */; value_type * r = new( q) value_type( plist...); /*... */ }  new( q) je starý trik: placement new  spuštění konstruktoru na dříve vyhrazeném místě zároveň mění void * na T *  využívá možnost napsat si vlastní alokátor s parametrem navíc void * operator new( std::size, void * q) { return q; } C++11

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

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

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

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

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

31 Perfect forwarding  Univerzální reference  jako parametr funkce template void f( T && x) { g( std::forward ( x)); }  jako proměnná auto && x = cont.at( some_position);  Pozor – jinde to tak nefunguje  zde nelze přizpůsobit T mechanismu skládání referencí template class C { void f( T && x) { g( std::forward ( x)); } }; C++11

32 Univerzální reference Skutečný parametr Typ parametru p template void f( U && p) Použití parametru p T & U = T & T & U = T & const T & U = const T & const T & U = const T & T && U = T T && U = T lvalue T lvalue const T rvalue T lvalue T lvalue const T rvalue T std::move std::forward

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

34 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

35 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

36 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

37 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

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

39 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

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

41 Podpora move ve funkcích/metodách  Parametr typu T && se vyplatí, pokud funkce dokáže využít prostor přinesený parametrem  Typicky se prostor použije pro návratovou hodnotu funkce  Příklad: sčítání matic  Nevyplatí se pro násobení matic Algoritmus násobení neumí pracovat "na místě"  Nevyplatí se pro zřetězení řetězců Výsledek má jinou velikost  Funkce pak musí mít dvě varianty  Parametr typu const T &  Parametr typu T &&  Pokud je variabilních parametrů víc, exponenciální počet variant Viz "perfect forwarding" C++11

42 lvalue/rvalue Nové rozhraní STL

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

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

45 Smart pointers

46  Chytré ukazatele  Automatické uvolnění dynamicky alokovaného objektu  Jinak se chovají jako T * Operátory *, -> Porovnání ukazatelů, nullptr  Výlučné vlastnictví  Vyžaduje move-semantiku std::unique_ptr Varianta pro pole: Operátor [] namísto * a -> std::unique_ptr  Sdílené vlastnictví  Založeno na počítání odkazů std::shared_ptr  Vedlejší odkaz, který dovoluje zánik sdíleného objektu std::weak_ptr Lze povýšit na shared_ptr, pokud objekt dosud nezanikl C++11

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

48 Smart pointers – použití u velkých datových typů  Cíl: Zabalit velká data tak, aby se chovala jako hodnota, ale efektivně class BigBody { // velká data // metody }; class Big { public: // konstruktory, operátory, metody private: kind_of_smart_ptr b_; }; Big a, b, c; a = b + c;  Proč nepracujeme přímo s BigBody?  Kopírování BigBody je příliš drahé  Implementace move-semantiky pro BigBody může být složitá  Třída BigBody může mít virtuální funkce a potomky C++11

49 Smart pointers – použití u velkých datových typů  Řešení A  BigBody se kopíruje vždy, když se kopíruje Big Každé BigBody má jediného vlastníka Neušetříme žádné kopírování  Snadná implementace move-semantiky Default pro move metody vyhovuje, ale je třeba jej vynutit, protože existují copy metody Copy metody nemají default (unique_ptr nemá copy-semantiku) class Big { public: Big( /*...*/) : b_( new BigBody( /*...*/) {} Big( const Big & x) : b_( new BigBody( * x.b_)) {} Big & operator=( const Big & x) { return * this = Big( x); } Big( Big && x) = default; // : b_( std::move( x.b_)) {} Big & operator=( Big && x) = default; // { b_ = std::move( x.b_); } ~Big() = default; // {} Big & operator+=( const Big & x) { b_->add( x.b_.get()); return * this; } private: std::unique_ptr b_; }; C++11

50 Smart pointers – použití u velkých datových typů  Řešení A + ošetření dědičnosti  Abstraktní třída AbstractBody má různé potomky Virtuální metoda clone pro kopírování, virtuální destruktor AbstractBody * ConcreteBody1::clone() const { return new ConcreteBody1( * this); } Virtuální funkce implementující jednotné rozhraní Nemá smysl publikovat funkce formou operátorů  Metody/operátory třídy Big nikdy nejsou virtuální! class Big { public: Big( /*...*/) : b_( new ConcreteBody1( /*...*/) {} Big( /*...*/) : b_( new ConcreteBody2( /*...*/) {} Big( const Big & x) : b_( x.b_->clone())) {} Big & operator=( const Big & x) { return * this = Big( x); } Big( Big && x) = default; Big & operator=( Big && x) = default; ~Big() = default; private: std::unique_ptr b_; }; C++11

51 Smart pointers – použití u velkých datových typů  Řešení B  BigBody se kopíruje, jenom když je to nutné BigBody může být ve společném vlastnictví Před modifikujícími operacemi je nutné BigBody privatizovat  Default pro copy/move metody vyhovuje class Big { public: Big( /*...*/) : b_( std::make_shared ( /*...*/)) {} Big & operator+=( const Big & x) { if ( ! b_.unique() ) b_ = std::make_shared ( * b_); b_->add( x.b_.get()); } private: std::shared_ptr b_; }; C++11

52 Hodnotová vs. referenční semantika Přiznaná semantikaImplementační strategieUkazatelmove metody copy metody Hodnota Kopírování vytváří nezávislé objekty Deep copyunique_ptrdefaultalokace+kopírování Lazy copy Privatizace (alokace+kopírování) před každým zápisem shared_ptrdefault Reference Kopírování vytváří odkazy na týž objekt Shallow copyshared_ptrdefault

53 Smart pointers – typy s referenční semantikou  Referenční semantika  Uživatel třídy Ref ví, že jde o odkaz na Body Kopírování Ref nezpůsobuje kopírování Body  Default pro copy/move metody vyhovuje Ve srovnání s Big/BigBody chybí privatizace  Proč nepoužíváme přímo shared_ptr ? Na třídě Ref lze definovat hezčí rozhraní včetně operátorů class Ref { public: Ref( /*...*/) : b_( std::make_shared ( /*...*/)) {} Ref & operator+=( const Ref & x) { b_->add( x.b_.get()); } private: std::shared_ptr b_; }; C++11

54 weak_ptr – nepovinné odkazy  Příklad: sdílené soubory typedef std::shared_ptr FileRef; typedef std::weak_ptr WeakFileRef; class Pool { public: FileRef open( const std::string & name) { FileRef fs; auto it = files_.find( name); if ( it != files_.end() ) fs = it->second.lock(); if ( ! fs ) { fs = std::make_shared ( name); files_.emplace( name, fs); } return fs; } void clear() { files_.clear(); } private: std::map files_; };  Soubor otevřený metodou open bude uzavřen (FileBody zanikne) při zániku posledního FileRef  WeakFileRef přetrvají i po zániku FileBody, nedovolují však přístup Metoda lock konvertuje na FileRef (který může být nulový) C++11

55 Pokročilý pohled na kontejnery

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

57 Kontejnery a iterátory  Iterátory jsou inspirovány ukazatelovou aritmetikou  Ukazatele do pole se chovají jako iterátory  Algoritmy mohou pracovat  s iterátory libovolného kontejneru  s ukazateli do pole  s čímkoliv, co má potřebné operátory void clr( int & x) { x = 0; } std::vector v; for_each( v.begin(), v.end(), clr); std::array a; for_each( a.begin(), a.end(), clr); int p[ N]; for_each( p, p + N, clr);  Ukazatele do pole se chovají jako iterátory  Celé pole se ale nechová jako kontejner  Nemá k.begin(), k.end()  Řešení: begin(k), end(k) std::vector v; for_each( begin( v), end( v), clr); std::array a; for_each( begin( a), end( a), clr); int p[ N]; for_each( begin( p), end( p), clr); template void clr_all( K && k) { for_each( begin( k), end( k), clr); } C++11

58 Kontejnery a iterátory  Ukazatele do pole se chovají jako iterátory  Celé pole se ale nechová jako kontejner  Nemá k.begin(), k.end()  Řešení: begin(k), end(k) void clr( int & x) { x = 0; } template void clr_all( K & k) { for_each( begin( k), end( k), clr); }  Toto řešení funguje pouze pro prvky typu int  Generalizování funkce clr nestačí Šablonu funkce nelze předat  Univerzální funktor struct clr { template void operator()( T && x) const { x = T(); } }; template void clr_all( K && k) { for_each( begin( k), end( k), clr()); }

59 Kontejnery a iterátory  Univerzální funktor struct clr { template void operator()( T && x) const { x = T(); } }; template void clr_all( K && k) { for_each( begin( k), end( k), clr()); } template void clr_all( K & k) { for_each( begin( k), end( k), []( auto && x) { x = /*???*/; }); }  Lambda template void clr_all( K & k) { typedef decltype( begin( k)) IT; typedef typename std::iterator_traits ::reference TR; typedef typename std::iterator_traits ::value_type TV; for_each( begin( k), end( k), []( TR x) { x = TV(); }); } C++11 C++14

60 Kontejnery a iterátory  Lambda template void clr_all( K & k) { using IT = decltype( begin( k)); using TR = typename std::iterator_traits ::reference; using TV = typename std::iterator_traits ::value_type; for_each( begin( k), end( k), []( TR x) { x = TV(); }); }  Lambda  nedokonalá alternativa Nefunguje pro vector template void clr_all( K & k) { using TR = decltype( * begin( k)); using TV = std::remove_reference_t ; for_each( begin( k), end( k), []( TR x) { x = TV(); }); } C++11 C++14 C++11

61 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

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

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

64 std::iterator  Pomůcka pro vytváření vlastních iterátorů  šablona std::iterator použitelná jako předek třídy template struct iterator { typedef T value_type; typedef Distance difference_type; typedef Pointer pointer; typedef Reference reference; typedef Category iterator_category; };

65 std::iterator  Příklad my_iterator b, e; std::distance( b, e)  distance potřebuje znát iterator_category a difference_type std::iterator_traits ::iterator_category  iterator_traits vyřeší případ ukazatelů a přesměruje problém na samotný iterátor my_iterator::iterator_category  uživatelský iterátor dědí instanci std::iterator s vhodnými parametry std::iterator ::iterator_category

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

67 Š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 volání funkce template class X : public T { void f() { return m(); }// globální funkce m() void g() { return this->m(); }// metoda třídy T (nebo předka) void h() { return T::m(); }// metoda třídy T (nebo předka) }  problém lze také vyřešit pomocí using template class X : public T { using T::m; void h() { return m(); } }

68 my_iterator  Iterator je obvykle šablona template class my_iterator : public std::iterator { private: typedef std::iterator base_; public: using typename base_::reference; using typename base_::pointer; reference operator*() const; pointer operator->() const; //... };

69 my_const_iterator  Kompletní kontejner musí zvládat čtení nemodifikovatelného kontejneru void f( const my_container & k) { for ( my_container ::const_iterator it = k.begin(); it != k.end; ++it) //... }  Kontejner musí poskytovat dvojice metod ošetřující const template class my_container { public: typedef my_iterator iterator; typedef my_const_iterator const_iterator; iterator begin(); const_iterator begin() const; const_iterator cbegin() const;// podle vzoru C++11 }  Vyžaduje dvě třídy implementující iterátor

70 Policy class, traits Pokročilé použití šablon

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

72 Policy class template class File { //... typename P::T get() { if ( P::binary ) //... } }; struct my_policy { typedef char T; static const bool binary = false; static const bool cached = true; }; File my_file;  Policy class (politika)  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  V tomto příkladě politika slouží jako balíček vytvořený uživatelem  Zpřehlednění zápisu

73 Policy class template< typename T, typename caching_policy, typename converting_policy> class File { //... typename T get() { caching_policy::read(/*...*/); converting_policy::convert(/*...*/); } }; struct cached { static void read(/*...*/) {/*...*/} }; struct binary { static void convert(/*...*/){/*...*/} }; File my_file;  Policy class (politika)  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  V tomto příkladě je politika vytvořena autorem šablony  Uživatel si vybírá z předdefinovaných politik

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

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

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

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

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

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

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

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

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

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

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

85 Šablony Pokročilé použití šablon

86 Motivace k použití šablon  Společná implementace příbuzných problémů  Autor šablony šetří práci sobě  vector vs. vector implementace se liší pouze záměnou int/double  vector vs. vector odlišné problémy - dělení bajtů na bity drobná odlišnost interface - problém reference na bit  vector vs. vector > odlišný způsob použití - copy vs. move semantika  Jednotný interface příbuzných implementací  Autor šablony šetří práci autorům jiných šablon template T scalar_product( const vector & x, const vector & y); template typename K::value_type scalar_product( const K & x, const K & y);

87 Řešení nepravidelností v šablonách  Řešení příbuzných problémů s odlišnými detaily  Šablony různých jmen Neušetří práci implementátorovi Zkomplikuje použití uživateli Dovoluje odlišnosti interface  Parciální/explicitní specializace šablony Neušetří práci implementátorovi Zvenčí nerozlišitelné Dovoluje odlišnosti interface  Přídavné parametry šablony, policy classes Ušetří práci implementátorovi Odlišnosti jsou zvenku explicitně vynuceny Odlišnost interface je obtížně dosažitelná  Automatické odvození parametrů - traits Ušetří práci implementátorovi Zvenčí nerozlišitelné Odlišnost interface je obtížně dosažitelná

88 Řešení nepravidelností v šablonách  Řešení příbuzných problémů s odlišnými detaily  Šablony různých jmen template class vector { /*...*/ }; template class list { /*...*/ }; podobnost interface dovoluje použití ve společném algoritmu  Parciální/explicitní specializace šablony template class vector { /*...*/ }; template<> class vector { /*...*/ };  Přídavné parametry šablony, policy classes template class map { /*...*/ comparator::cmp() /*...*/};  Automatické odvození parametrů - traits template class less; template<> class less { /*...*/ strcmp() /*...*/ }; template class map { /*...*/ less ::cmp() /*...*/ };  Kombinace policy classes a traits template > class map;

89 Š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  seznam typů - deklarováno zápisem class T... nebo typename T...  Prefix definice šablony template  lze použít před několika formami deklarací; oblastí platnosti formálních parametrů je celá prefixovaná deklarace

90 Šablony funkcí  Šablona funkce je generická funkce (globální nebo metoda) prefixovaná konstrukcí template  se stejnými druhy formálních parametrů šablony jako u šablon tříd template // parametry šablony int f( T * p, int q);// parametry funkce template // parametry šablony int g( T * p, vector q);// parametry funkce  Šablony funkcí lze volat dvěma způsoby  Explicitně f ( a, b)  Automaticky g( a, b) Překladač dopočte parametry šablony z typů parametrů funkce Pokud to jde  Kombinace - počáteční parametry explicitně, zbytek automaticky

91 Nepříjemnosti spojené se šablonami class T { class c {}; template class tc {}; void f( int) {} template void tf( int) {} static int v; }; int * m1, * m2, * m3, * m4, * m5; void t1() { T::c(* m1);// deklarace T::f(* m2);// příkaz T::tc (* m3);// deklarace T::tf (* m4);// příkaz T::v (* m5);// příkaz };  Překladače mohou provádět částečnou (syntaktickou) analýzu šablon při jejich deklaraci  Problém: Poruzumění syntaxi v C++ vyžaduje částečnou znalost vlastností identifikátorů int * m1, * m2, * m3, * m4, * m5; template void t1() { T::c(* m1);// ??? T::f(* m2);// ??? T::tc (* m3);// ??? T::tf (* m4);// ??? T::v (* m5);// ??? };

92 Nepříjemnosti spojené se šablonami class T { class c {}; template class tc {}; void f( int) {} template void tf( int) {} static int v; }; int * m1, * m2, * m3, * m4, * m5; void t1() { T::c(* m1);// deklarace T::f(* m2);// příkaz T::tc (* m3);// deklarace T::tf (* m4);// příkaz T::v (* m5);// příkaz };  Řešení: Nápověda syntaktickému analyzátoru pomocí typename a template int * m1, * m2, * m3, * m4, * m5; template void t1() { typename T::c(* m1);// deklarace T::f(* m2);// příkaz typename T::template tc (* m3); // deklarace T::template tf (* m4); // příkaz T::v (* m5);// příkaz };

93 Nepříjemnosti spojené se šablonami class T { class c {}; template class tc {}; void f( int) {} template void tf( int) {} static int v; }; class X : public T { void t1() { c(* m1);// deklarace f(* m2);// příkaz tc (* m3);// deklarace tf (* m4);// příkaz v (* m5);// příkaz } };  Problém 2: Překladač nezná prvky parametru šablony  Děděné prvky nemají přednost před globálními deklaracemi class X : public T { void t1() { typename T::c(* m1);// deklarace this->f(* m2);// příkaz typename T::template tc (* m3); // deklarace this->template tf (* m4); // příkaz T::v (* m5);// příkaz } };

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

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

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

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

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

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

100 Lambda

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

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

103 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

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

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

106 Exception handling Mechanismus výjimek

107 Exception handling  Mechanismus výjimek  Start: příkaz throw  Cíl: try-catch blok  Určen za běhu  Skok může opustit proceduru  Proměnné korektně zaniknou voláním destruktorů  Předává hodnotu libovolného typu  Typ hodnoty se podílí na určení cíle skoku  Obvykle se používají pro tento účel zhotovené třídy  Mechanismus výjimek respektuje hierarchii dědičnosti class AnyException { /*...*/ }; class WrongException : public AnyException { /*...*/ }; class BadException : public AnyException { /*...*/ }; void f() { if ( something == wrong ) throw WrongException( something); if ( anything != good ) throw BadException( anything); } void g() { try { f(); } catch ( const AnyException & e1 ) { /*...*/ }

108 Exception handling  Mechanismus výjimek  Start: příkaz throw  Cíl: try-catch blok  Určen za běhu  Skok může opustit proceduru  Proměnné korektně zaniknou voláním destruktorů  Předává hodnotu libovolného typu  Typ hodnoty se podílí na určení cíle skoku  Obvykle se používají pro tento účel zhotovené třídy  Mechanismus výjimek respektuje hierarchii dědičnosti  Hodnotu není třeba využívat class AnyException { /*...*/ }; class WrongException : public AnyException { /*...*/ }; class BadException : public AnyException { /*...*/ }; void f() { if ( something == wrong ) throw WrongException(); if ( anything != good ) throw BadException(); } void g() { try { f(); } catch ( const AnyException &) { /*...*/ }

109 Exception handling  Mechanismus výjimek  Start: příkaz throw  Cíl: try-catch blok  Určen za běhu  Skok může opustit proceduru  Proměnné korektně zaniknou voláním destruktorů  Předává hodnotu libovolného typu  Typ hodnoty se podílí na určení cíle skoku  Obvykle se používají pro tento účel zhotovené třídy  Mechanismus výjimek respektuje hierarchii dědičnosti  Hodnotu není třeba využívat  Existuje univerzální catch blok class AnyException { /*...*/ }; class WrongException : public AnyException { /*...*/ }; class BadException : public AnyException { /*...*/ }; void f() { if ( something == wrong ) throw WrongException(); if ( anything != good ) throw BadException(); } void g() { try { f(); } catch (...) { /*...*/ }

110 Exception handling  Zhmotněné výjimky  std::exception_ptr je chytrý ukazatel na objekt výjimky  Objekt zanikne při zániku posledního ukazatele  std::current_exception()  Vrací aktuálně řešenou výjimku  std::rethrow_exception( p)  Vyvolává uloženou výjimku  Tento mechanismus umožňuje odložit ošetřování výjimky, zejména:  Propagace výjimky do jiného vlákna  Řešení výjimek v promise/future std::exception_ptr p; void g() { try { f(); } catch (...) { p = std::current_exception(); } void h() { std::rethrow_exception( p); } C++11

111 Exception handling  Zhmotněné výjimky  std::exception_ptr je chytrý ukazatel na objekt výjimky  Objekt zanikne při zániku posledního ukazatele  std::current_exception()  Vrací aktuálně řešenou výjimku  std::rethrow_exception( p)  Vyvolává uloženou výjimku  Tento mechanismus umožňuje odložit ošetřování výjimky, zejména:  Propagace výjimky do jiného vlákna  Řešení výjimek v promise/future std::promise p; std::future f = p.get_future(); void g() { try { p.set_value( do_something()); } catch (...) { p.set_exception( std::current_exception()); } void h() { try { T x = f.get(); //... } catch (...) { //... } C++11

112 Exception handling  Standardní výjimky   Všechny standardní výjimky jsou potomky třídy exception  metoda what() vrací řetězec s chybovým hlášením  bad_alloc: vyvolává operátor new při nedostatku paměti  V režimu "bez výjimek" new vrací nulový ukazatel  bad_cast, bad_typeid: Chybné použití RTTI  Odvozené z třídy logic_error:  domain_error, invalid_argument, length_error, out_of_range  vyvolávány např. funkcí vector::operator[]  Odvozené z třídy runtime_error:  range_error, overflow_error, underflow_error

113 Exception handling  Standardní výjimky   Všechny standardní výjimky jsou potomky třídy exception  metoda what() vrací řetězec s chybovým hlášením  bad_alloc: vyvolává operátor new při nedostatku paměti  V režimu "bez výjimek" new vrací nulový ukazatel  bad_cast, bad_typeid: Chybné použití RTTI  Odvozené z třídy logic_error:  domain_error, invalid_argument, length_error, out_of_range  vyvolávány např. funkcí vector::operator[]  Odvozené z třídy runtime_error:  range_error, overflow_error, underflow_error  Aritmetické ani ukazatelové operátory na vestavěných typech NEHLÁSÍ běhové chyby prostřednictvím výjimek  např. dělení nulou nebo dereference nulového ukazatele

114 Exception specifications  Exception specifications  Před C++11 – nyní zastaralé  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 */ } pre-C++11

115 Exception specifications  Exception specifications  throw( T) specifikace se příliš nepoužívaly  C++11 definuje novou syntaxi  noexcept odpovídá throw()  noexcept( c) podmíněné kompilátorem vyhodnocenou podmínkou c je Booleovský konstantní výraz void f() noexcept { } template void g( T & y) noexcept( std::is_nothrow_copy_constructible ::value) { T x = y; } C++11

116 Exception handling  Použití mechanismu výjimek  Vyvolání a zpracování výjimky je relativně časově náročné  Používat pouze pro chybové nebo řídké stavy Např. nedostatek paměti, ztráta spojení, chybný vstup, konec souboru  Připravenost na výjimky také něco (málo) stojí  Za normálního běhu je třeba zařídit, aby výjimka dokázala najít cíl a zrušit proměnné Výjimky se týkají i procedur, ve kterých není ani throw, ani try-blok  Kompilátory často umí překládat v režimu "bez výjimek" Z historických důvodů Kromě speciálních aplikací je dnes režim „bez“ nepoužitelný Ani v případech programů, ve kterých není jediné throw ani catch Velká část knihoven na výjimky spoléhá

117 Exception handling  Fáze zpracování výjimky  Vyhodnocení výrazu v příkaze throw  Hodnota je uložena "stranou"  Stack-unwinding  Postupně se opouštějí bloky a funkce, ve kterých bylo provádění vnořeno  Na zanikající lokální a pomocné proměnné jsou volány destruktory  Stack-unwinding končí dosažením try-bloku, za kterým je catch-blok odpovídající typu výrazu v příkaze throw  Provedení kódu v catch-bloku  Původní hodnota throw je stále uložena pro případné pokračování: Příkaz throw bez výrazu pokračuje ve zpracování téže výjimky počínaje dalším catch-blokem - začíná znovu stack-unwinding  Zpracování definitivně končí opuštěním catch-bloku  Běžným způsobem nebo příkazy return, break, continue, goto Nebo vyvoláním jiné výjimky

118 Exception handling - pravidla  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čí

119 Exception handling - pravidla  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

120 Exception handling - pravidla  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é)

121 Exception handling - pravidla  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é)  V catch-bloku je vhodnější předávání referencí

122 Exception handling - pravidla  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

123 Exception handling - pravidla  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é

124 Exception handling - pravidla  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

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

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

127 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

128 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

129 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

130 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"

131 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

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

133 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; }

134 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; }

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

136 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 (...) { /* ??? */ }

137 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é ++, --

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

139 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 */ }

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

141 Exception-safe programming Chytré ukazatele apod.

142 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 void f() { unique_ptr a( new int[ 100]); unique_ptr b( new int[ 100]); g( a.get(), b.get()); }  Chytré ukazatele  ušetří psaní delete  zajistí bezpečnost vůči výjimkám

143 Exception-safe programming mutex my_mutex; void f() { my_mutex.lock(); something_dangerous(); my_mutex.unlock(); }  Výjimka v kritické sekci zanechá sekci věčně zamčenou mutex my_mutex; void f() { lock_guard my_g( my_mutex); something_dangerous(); }  lock_guard  zamkne při vzniku  odemkne při zániku korektně zaniká i v případě výjimky  RAII = Resource Acquisition Is Initialization

144 Exception-safe programming Konstruktory a operator=

145 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(); }

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

147 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 vyměňuje obsah this a proměnné c Knihovní funkce swap je rychlá a (na ukazatelích) nevyvolává výjimky  Před návratem z operatoru se volá destruktor c Tím zaniká původní obsah this #include void String::swap( String & x) { using std::swap; swap( str_, x.str_); } String & String::operator=( const String & b) { String c( b); swap( c); return * this; }

148 Exception-safe programming  operator=  Silně bezpečné řešení  Metodu swap bývalo 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  V C++11 je std::swap implementováno pomocí move- semantiky  Je rychlé a bezpečné #include void String::swap( String & x) { using std::swap; 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); } před C++11

149 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 #include String::String( String && b) : str_( std::move( b.str_)) { } String & String::operator=( String && b) { str_ = std::move( b.str_); return * this; } // alternativa: String & String::operator=( String && b) { String c( std::move(b)); swap( c); return * this; } C++11

150 Exception-safe programming  Copy & Move metody  Další varianta class String { public: String() {} String( String &&) = default; String & operator=( String &&) = default; String( const String & b); String & operator=( const String & b) { return operator=( String( b)); } private: std::unique_ptr str_; }; #include String::String( const String & b) { if ( b.str_ ) { str_ = std::make_unique (strlen( b.str_.get()) + 1); strcpy( str_.get(), b.str_.get()); }  str_ == nullptr nyní reprezentuje prázdný string C++14

151 Koenig lookup

152 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

153 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

154 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

155 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

156 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)

157 Polymorfismus Kompilační a běhový

158 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

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

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

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

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

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

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

165 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ř

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

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

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

169 Teoretický pohled na šablony  Triky s typovým konstrukcemi  Moderní implementace seznamu typů template struct tuple;  Funkce na seznamu typů template struct tuple_element; template struct tuple_element > { typedef typename tuple_element >::type type; }; template struct tuple_element > { typedef H type; }; C++11:

170 Teoretický pohled na šablony  Triky s typovým konstrukcemi  Zlomková aritmetika template struct ratio;  Příklad použití typedef ratio_add, ratio > platform; static_assert( ratio_equal >::value);  Vyhodnocuje překladač Včetně výpočtu největšího společného dělitele! C++11:

171 Metaprogramování

172 Příklad  Transformace typů  Cíl: Transformovat např. tuto trojici... typedef std::tuple my_triple; ... na trojici std::tuple  K čemu to je?  Např. pro sloupcově implementované struktury: std::tuple, std::vector, std::vector >  Nebo pro předávání referencí: std::tuple

173 Příklad  Transformace typů  Cíl: Transformovat např. tuto trojici... typedef std::tuple my_triple; ... na trojici std::tuple  Jak zapsat volání transformace?  Standardní knihovna používá konvenci “::type”: typedef type_transform ::type my_transformed_triple; V případě závislých typů vyžaduje navíc „typename“  V C++14 je navíc konvence “_t”: typedef type_transform_t my_transformed_triple;

174 Příklad  Transformace typů  Cíl: Transformovat např. tuto trojici... typedef std::tuple my_triple; ... na trojici std::tuple  Jak zapsat funkci F?  Se stejnou konvencí “::type”: template struct F { typedef std::pair type; };  Nebo snad pomocí šablonovaných aliasů? template using F = std::pair ; To nebude fungovat: samotný alias F nemůže být parametrem šablony type_transform

175 Příklad  Transformace typů  Cíl: Transformovat např. tuto trojici... typedef std::tuple my_triple; ... na trojici std::tuple  Jak zapsat funkci F?  Metoda “::type” umožňuje specializaci: template struct F { typedef std::pair type; }; template<> struct F { typedef std::complex type; };

176 Příklad  Transformace typů  Cíl: Transformovat např. tuto trojici... typedef std::tuple my_triple; ... na trojici std::tuple  Jak implementovat type_transform? template class F> struct type_transform; template class F> struct type_transform, F> { typedef std::tuple ::type...> type; }; Prvním parametrem má být tuple To vyžaduje specializaci, jinak se nedostaneme k seznamu prvků Druhým parametrem má být šablona C++ dovoluje pouze šablony tříd, nikoliv aliasů či funkcí

177 Příklad  Práce s prvky n-tic podle vzoru algoritmů typedef std::tuple my_triple; typedef type_transform ::type my_transformed_triple; my_triple a, b; my_transformed_triple c; static_transform(a, b, c, my_value_function());  my_value_function je polymorfní funktor  Polymorfní lambda v C++14? static_transform(a, b, c, [](auto && x, auto && y){ return std::make_pair( x, y); }); V tomto případě lambda nefunguje dobře Jak explicitně uvést vrácený typ? Jak zapsat std::forward ( x)?

178 Příklad  Polymorfní binární funktor  Příklad: vrací pair struct my_value_function { template std::pair< typename std::remove_reference ::type, typename std::remove_reference ::type> operator()(T1 && x, T2 && y) const { return std::make_pair( std::forward (x), std::forward (y)); } };  Pozor na správnou konstrukci výsledného typu při předávání referencí

179 Příklad  Polymorfní binární funktor  Dovoluje i specializaci (přetížení operátoru ())  Příklad: vrací pair struct my_value_function { template std::pair< typename std::remove_reference ::type, typename std::remove_reference ::type> operator()(T1 && x, T2 && y) const { return std::make_pair( std::forward (x), std::forward (y)); } std::complex operator()(double x, double y) const { return std::complex (x, y); } };

180 Příklad  Implementace průchodu prvky n-tice template void static_transform(A && a, B && b, C && c, F && f) { static const auto al = std::tuple_size< typename std::remove_reference ::type>::value; static const auto bl = std::tuple_size< typename std::remove_reference ::type>::value; static const auto cl = std::tuple_size< typename std::remove_reference ::type>::value; static_assert(al == bl && bl == cl, "tuples must be of equal length"); static_for_each_index ( transform_ftor ( std::forward (a), std::forward (b), std::forward (c), std::forward ( f))); }

181 Příklad  Interface statického for cyklu template struct static_index { static const std::size_t value = i; }; template void static_for_each_index(F && f) { // volá f( static_index ) pro b <= i < e }

182 Příklad  Funktor pro static_transform template struct transform_ftor { transform_ftor(A && a, B && b, C && c, F && f) : a_(std::forward (a)), b_(std::forward (b)), c_(std::forward (c)), f_(std::forward (f)) {} template void operator()(I &&) { static const auto i = I::value; std::get (std::forward ( c_)) = f_( std::get (std::forward ( a_)), std::get (std::forward ( b_))); } private: A && a_; B && b_; C && c_; F && f_; };

183 Příklad  Implementace statického for cyklu template struct for_each_index_helper : for_each_index_helper { template for_each_index_helper(F & f) : for_each_index_helper ( f) { f(static_index ()); } }; template struct for_each_index_helper { template for_each_index_helper(F &) {} }; template void static_for_each_index(F && f) { for_each_index_helper tmp(f); }

184 Příklad  Implementace statického for cyklu bez rekurze template struct for_each_index_helper2; template struct for_each_index_helper2 > { template void call(F & f) { sink( (f( static_index ()), 0)...); } template void sink( X &&...) {} }; template void static_for_each_index(F && f) { for_each_index_helper2 >::call(f); }

185 Kompilační a běhový polymorfismus Příklad

186 Příklad: Vektorové operace template std::vector for_vector( P g, std::vector x, const std::vector & y) { transform( make_move_iterator( begin( x)), make_move_iterator( end( x)), begin( y), begin( x), g); return std::move( x); }  result_type, first_argument_type a second_argument_type jsou součástí binárních funktorů definovaných normou C++ zde jsou použity k určení typů vektorových operandů a především výsledku  make_move_iterator umožňuje vykrást původní elementy vektoru x operátor * vrací r-value C++11

187 Příklad: Vektorové operace typedef std::vector my_vector; my_vector x( 3, 7), y( 3, 2); auto op = std::plus (); my_vector z = for_vector( op, x, y);  std::plus je binární funktor definovaný normou C++ obaluje operátor + do funktoru, doplňuje typové položky C++11

188 Polymorfismus: Dynamicky volené operace std::string s =...; typedef std::vector my_vector; my_vector x( 3, 7), y( 3, 2); auto op = s == ”+” ? std::plus () : std::minus (); my_vector z = for_vector( op, x, y);  Chyba: std::plus a std::minus jsou rozdílné typy Operátor ? : je nedokáže převést na společný typ C++11

189 Polymorfismus: Dynamicky volené operace std::string s =...; typedef std::vector my_vector; my_vector x( 3, 7), y( 3, 2); typedef std::function my_function; auto op = s == "+" ? my_function( std::plus ()) : my_function( std::minus ()); my_vector z = for_vector( op, x, y);  Chyba odstraněna: std::function je polymorfní obálka schopná pojmout všechny funktory se signaturou int( int, int) Tato obálka se opět chová jako funktor  Řešení je velmi neefektivní Polymorfní funktor je vyvoláván pro každý prvek vektoru Nepřímé volání funkce stojí výrazně více než samotné sečtení C++11

190 Polymorfismus: Dynamicky volené operace std::string s =...; typedef std::vector my_vector; my_vector x( 3, 7), y( 3, 2); auto opplus = vectorize( std::plus ()); auto opminus = vectorize( std::minus ()); typedef std::function my_vector_function; auto op = s == "+" ? my_vector_function( opplus) : my_vector_function( opminus); z = op( x, y);  Polymorfismus se odehrává až na vektorizovaných operacích Polymorfní obálka je funktor nad vektory Nepřímé volání bude jen jedno  Zbývá napsat funkci vectorize Transformuje skalární funktor na vektorový C++11

191 Polymorfismus: Dynamicky volené operace template struct vectorized { vectorized( P g) : g_( g) {} typedef std::vector first_argument_type; typedef std::vector second_argument_type; typedef std::vector result_type; result_type operator()( first_argument_type x, const second_argument_type & y) const { return for_vector( g_, std::move( x), y); } private: P g_; }; template vectorized vectorize( P g) { return vectorized ( g); }  vectorized je vektorová verze funktoru P  Funkce vectorize umožňuje automatické odvození typu P z parametru g C++11

192 Polymorfismus: Dynamicky volené operace auto opplus = vectorize( std::plus ()); auto opminus = vectorize( std::minus ()); auto op = s == "+" ? dynamize( opplus) : dynamize( opminus);  Funkce dynamize umožňuje automatické odvození typu polymorfní obálky template std::function< typename P::result_type( typename P::first_argument_type, typename P::second_argument_type)> dynamize( P g) { return std::function< typename P::result_type( typename P::first_argument_type, typename P::second_argument_type)> ( g); }  Tento zápis je mimořádně neprůhledný C++11

193 Polymorfismus: Dynamicky volené operace  Srozumitelnější zápis  Trait dynamize_type umožňuje automatické odvození typu polymorfní obálky template struct dynamize_type { typedef std::function< typename P::result_type( typename P::first_argument_type, typename P::second_argument_type)> type; };  Nepřímé využití dynamize_type template typename dynamize_type ::type dynamize( P g) { return dynamize_type ::type( g); }  Přímé využití dynamize_type typedef std::binary_function my_function; std::map ::type> operator_map; operator_map.emplace( “+”, vectorize( std::plus ())); C++11

194


Stáhnout ppt "Pokročilé programování v C++ (část B) David Bednárek"

Podobné prezentace


Reklamy Google