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
Pokročilý pohled na kontejnery
3
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
4
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
5
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()); }
6
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()); } 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
7
Kontejnery a iterátory 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(); }); } Lambda nedokonalá alternativa Nefunguje pro vector template void clr_all( K & k) { typedef decltype( * begin( k)) TR; typedef typename std::remove_reference ::type TV; for_each( begin( k), end( k), []( TR x) { x = TV(); }); } C++11
8
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
9
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));
10
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; };
11
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; };
12
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
13
Š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
14
Š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(); } }
15
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; //... };
16
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
17
Rvalue reference Francisco de Goya. Sv. František Borgiáš u lože umírajícího (detail). 1788. const T & T && T
18
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ů
19
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?
20
Zbytečná kopírování Řešení pomocí rvalue referencí
21
Ř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
22
Ř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
23
Ř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
24
copy/move Speciální metody tříd – C++11
25
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
26
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
27
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
28
copy/move Nejběžnější kombinace Neškodná třída Nedeklaruje žádnou copy/move metodu ani destruktor Neobsahuje složky vyžadující zvláštní péči (ukazatele) Složky vyžadující zvláštní péči Překladačem generované chování (default) nevyhovuje Bez podpory move (před C++11) T( const T & x); T & operator=( const T & x); ~T(); Plná podpora copy/move T( const T & x); T( T && x); T & operator=( const T & x); T & operator=( T && x); ~T(); C++11
29
copy/move Další kombinace Nekopírovatelná třída Např. dynamicky alokované živé objekty v simulacích T( const T & x) = delete; T & operator=( const T & x) = delete; delete zakazuje generování překladačem Destruktor může ale nemusí být nutný Přesouvatelná nekopírovatelná třída Např. unikátní vlastník jiného objektu (viz std::unique_ptr ) T( T && x); T & operator=( T && x); ~T(); Pravidla jazyka zakazují generování copy metod překladačem Destruktor typicky bývá nutný C++11
30
Třídy obsahující velká data Řešení podle C++11
31
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
32
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
33
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
34
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
35
lvalue/rvalue Pravidla
36
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
37
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
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
Variadic templates
46
Š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
47
Š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
48
Š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
49
Š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
50
Generická N-tice template class tuple { public: tuple( const Types &...); /* black magic */ }; template class tuple_element { public: typedef /* black magic */ type; }; template typename tuple_element >::type & get( tuple & t); použití typedef tuple my_tuple; typedef typename tuple_element ::type alias_to_double; my_tuple t1( 1, 2.3, 4); double v = get ( t1); C++11:
51
lvalue/rvalue Perfect forwarding
52
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
53
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
54
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
55
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
56
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
57
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
58
Smart pointers
59
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
60
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
61
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
62
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
63
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
64
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
65
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
66
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
67
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
68
Policy class, traits Pokročilé použití šablon
69
Š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;
70
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
71
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
72
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
73
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í
74
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
75
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í
76
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; }
77
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ů
78
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
79
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); } };
80
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
81
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
82
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);
83
Polymorfismus Kompilační a běhový
84
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
85
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
86
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());
87
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 ();
88
Generické programování Výpočty při překladu
89
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
90
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
91
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ř
92
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:
93
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:
94
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); }; C++11: vestavěno v jazyce
95
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:
96
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:
97
Šablony Pokročilé použití šablon
98
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);
99
Ř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á
100
Ř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;
101
Š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
102
Š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
103
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);// ??? };
104
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 };
105
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 } };
106
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;
107
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;
108
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; };
109
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; };
110
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; };
111
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; };
112
Lambda
113
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
114
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
115
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
116
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
117
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
118
Exception handling Mechanismus výjimek
119
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 ) { /*...*/ }
120
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 &) { /*...*/ }
121
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 (...) { /*...*/ }
122
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
123
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
124
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
125
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á
126
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
127
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
128
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
129
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
130
Exception-safe programming Bezpečné programování s výjimkami
131
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
132
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
133
Exception-safe programming Pravidla vynucená jazykem Destruktor nesmí skončit vyvoláním výjimky Výjimka může být vyvolána uvnitř, ale musí být zachycena nejpozději uvnitř destruktoru Zdůvodnění: V rámci ošetření výjimek (ve fázi stack-unwinding) se volají destruktory lokálních proměnných Výjimku zde vyvolanou nelze z technických i logických důvodů ošetřit (ztratila by se původní výjimka) Nastane-li taková výjimka, volá se funkce terminate() a program končí
134
Exception-safe programming Pravidla vynucená jazykem Destruktor nesmí skončit vyvoláním výjimky Výjimka může být vyvolána uvnitř, ale musí být zachycena nejpozději uvnitř destruktoru Toto pravidlo jazyka sice platí pouze pro destruktory lokálních proměnných A z jiných důvodů též pro globální proměnné Je však vhodné je dodržovat vždy Bezpečnostní zdůvodnění: Destruktory lokálních proměnných často volají jiné destruktory Logické zdůvodnění: Nesmrtelné objekty nechceme
135
Exception-safe programming Pravidla vynucená jazykem Destruktor nesmí skončit vyvoláním výjimky Konstruktor globálního objektu nesmí skončit vyvoláním výjimky Zdůvodnění: Není místo, kde ji zachytit Stane-li se to, volá se terminate() a program končí Jiné konstruktory ale výjimky volat mohou (a bývá to vhodné)
136
Exception-safe programming Pravidla vynucená jazykem Destruktor nesmí skončit vyvoláním výjimky Konstruktor globálního objektu nesmí skončit vyvoláním výjimky Copy-constructor typu v hlavičce catch-bloku nesmí skončit vyvoláním výjimky Zdůvodnění: Catch blok by nebylo možné vyvolat Stane-li se to, volá se terminate() a program končí Jiné copy-constructory ale výjimky volat mohou (a bývá to vhodné) V catch-bloku je vhodnější předávání referencí
137
Exception-safe programming Pravidla vynucená jazykem Destruktor nesmí skončit vyvoláním výjimky Konstruktor globálního objektu nesmí skončit vyvoláním výjimky Copy-constructor typu v hlavičce catch-bloku nesmí skončit vyvoláním výjimky
138
Exception-safe programming Poznámka: Výjimky při zpracování výjimky Výjimka při výpočtu výrazu v throw příkaze Tento throw příkaz nebude vyvolán Výjimka v destruktoru při stack-unwinding Povolena, pokud neopustí destruktor Po zachycení a normálním ukončení destruktoru se pokračuje v původní výjimce Výjimka uvnitř catch-bloku Pokud je zachycena uvnitř, ošetření původní výjimky může dále pokračovat (přikazem throw bez výrazu) Pokud není zachycena, namísto původní výjimky se pokračuje ošetřováním nové
139
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
140
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;
141
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
142
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"
143
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
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_ ) { str_.reset( new char[ strlen( b.str_) + 1]); strcpy( str_.get(), b.str_.get()); } else { throw InvalidString(); } C++11
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_with vyměňuje obsah this a proměnné c Knihovní funkce swap je rychlá a (na ukazatelích) nevyvolává výjimky Před návratem z operatoru se volá destruktor c Tím zaniká původní obsah this #include void String::swap_with( String & x) { swap( str_, x.str_); } String & String::operator=( const String & b) { String c( b); swap_with( c); return * this; }
148
Exception-safe programming operator= Silně bezpečné řešení Metodu swap_with je vhodné publikovat ve formě globální funkce se standardním jménem swap Některé algoritmy nad kontejnery obsahujícími String se tak zrychlí a stanou se bezpečnými vůči výjimkám #include void String::swap_with( String & x) { swap( str_, x.str_); } String & String::operator=( const String & b) { String c( b); swap_with( c); return * this; } void swap( String & x, String & y) { x.swap_with( y); } před C++11
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 Vlastní implementace globální funkce swap není zapotřebí Knihovní implementace swap volá move metody #include String::String( String && b) : str_( std::move( b.str_)) { } String & String::operator=( String && b) { str_ = std::move( b.str_); return * this; } C++11
150
Exception-safe programming Funkce s vedlejšími efekty
151
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; }
152
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; }
153
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é
154
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 (...) { /* ??? */ }
155
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é ++, --
156
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 */ }
157
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 */ }
158
Exception-safe programming Příklad: StringStack::pop Zásobník prvků typu String Implementován seznamem Řešení B Lze implementovat nad řešením A #include class StringStack { public: /* A */ String & top(); void pop(); /* B */ void pop( String & out) { String & t = top(); swap( out, t); try { pop(); } catch (...) { swap( out, t); throw; } };
159
Kompilační a běhový polymorfismus Příklad
160
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
161
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
162
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
163
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
164
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
165
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
166
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
167
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
168
Koenig lookup
169
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
170
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
171
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
172
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
173
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)
174
-----------------------------------
Podobné prezentace
© 2024 SlidePlayer.cz Inc.
All rights reserved.