Stáhnout prezentaci
Prezentace se nahrává, počkejte prosím
1
Pokročilé programování v C++ (část B) David Bednárek www.ksi.mff.cuni.cz
2
Rvalue reference Francisco de Goya. Sv. František Borgiáš u lože umírajícího (detail). 1788. 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. 2002 Ještě v r. 2010 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<class Category, class T, class Distance = ptrdiff_t, class Pointer = T*, class Reference = T&> 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_<b_; } private: int a_, b_; }; typedef std::vector v_t; v_t v; v_t::iterator vi=remove_if(v.begin(), v.end(), ftor(m, n)); Řešení std::vector v; auto vi=remove_if(v.begin(), v.end(), [=](int x){ return x*m<n; }); C++11
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? 123234 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
-----------------------------------
Podobné prezentace
© 2024 SlidePlayer.cz Inc.
All rights reserved.