Pokro čilé p rogramování v C++ (část B)
description
Transcript of Pokro čilé p rogramování v C++ (část B)
Pokročilé programování v C++(část B)
David Bednárek
www.ksi.mff.cuni.cz
Pokročilý pohled na kontejnery
Kontejnery a iterátoryStandardní knihovna definuje
Mnoho druhů kontejnerů basic_string, vector, deque, list array, forward_list map, multimap, set, multiset unordered_map, unordered_multimap, unordered_set,
unordered_multisetOdlišné přidávání/odebíráníShodný způsob procházeníKaždý kontejner definuje
Typy iterator, const_iterator Metody begin(), end()
Iterátory jsou inspirovány ukazatelovou aritmetikou Ukazatele do pole se chovají jako iterátory
Algoritmy mohou pracovat s iterátory libovolného kontejneru s ukazateli do pole s čímkoliv, co má potřebné operátory
void clr( int & x){ x = 0;}std::vector< int> v;for_each( v.begin(), v.end(), clr);std::array< int, N> a;for_each( a.begin(), a.end(), clr);int p[ N];for_each( p, p + N, clr);
C++11
C++11
Kontejnery a iterátoryIterátory jsou inspirovány ukazatelovou aritmetikou
Ukazatele do pole se chovají jako iterátoryAlgoritmy mohou pracovat
s iterátory libovolného kontejneru s ukazateli do pole s čímkoliv, co má potřebné operátory
void clr( int & x){ x = 0;}std::vector< int> v;for_each( v.begin(), v.end(), clr);std::array< int, N> a;for_each( a.begin(), a.end(), clr);int p[ N];for_each( p, p + N, clr);
Ukazatele do pole se chovají jako iterátoryCelé pole se ale nechová jako kontejner
Nemá k.begin(), k.end() Řešení: begin(k), end(k)
std::vector< int> v;for_each( begin( v), end( v), clr);std::array< int, N> a;for_each( begin( a), end( a), clr);int p[ N];for_each( begin( p), end( p), clr);
template< typename K>void clr_all( K & k){ for_each( begin( k), end( k), clr);}
C++11
Kontejnery a iterátoryUkazatele do pole se chovají jako iterátoryCelé pole se ale nechová jako kontejner
Nemá k.begin(), k.end() Řešení: begin(k), end(k)
void clr( int & x){ x = 0;}
template< typename K>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< typename T> void operator()( T & x) const { x = T(); }};
template< typename K>void clr_all( K & k){ for_each( begin( k), end( k), clr());}
Kontejnery a iterátoryUniverzální funktor
struct clr { template< typename T> void operator()( T & x) const { x = T(); }};
template< typename K>void clr_all( K & k){ for_each( begin( k), end( k), clr());}
Lambda
template< typename K>void clr_all( K & k){ typedef decltype( begin( k)) IT; typedef typename std::iterator_traits< IT>::reference TR; typedef typename std::iterator_traits< IT>::value_type TV;
for_each( begin( k), end( k), []( TR x) { x = TV(); });}
C++11
Kontejnery a iterátoryLambda
template< typename K>void clr_all( K & k){ typedef decltype( begin( k)) IT; typedef typename std::iterator_traits< IT>::reference TR; typedef typename std::iterator_traits< IT>::value_type TV;
for_each( begin( k), end( k), []( TR x) { x = TV(); });}
Lambda nedokonalá alternativa
• Nefunguje pro vector< bool>
template< typename K>void clr_all( K & k){ typedef decltype( * begin( k)) TR; typedef typename std::remove_reference< TR>::type TV;
for_each( begin( k), end( k), []( TR x) { x = TV(); });}
C++11
C++11
C++11
Kategorie iterátorůStandardní knihovna definuje
Algoritmy mohou pracovat s čímkoliv, co má potřebné operátory
Různé algoritmy mají různé nárokyNorma 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++ = xrandom_access, bidirectional, forward, input I1 = I2 I1 == I2, I1 != I2 I->m /* pokud existuje (*I).m */input *I /* pouze pro čtení */random_access, bidirectional, forward iterator() *I /* čtení i zápis */random_access, bidirectional --I, I--random_access I += n, I + n, n + I I -= n, I - n, I1 - I2 I[ n] I1 < I2, I1 > I2, I1 <= I2, I1 >= I2
Standardem definované iterátoryIterá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átoryoutput category
back_inserter, front_inserter, inserterstd::vector a;std::copy( x.begin(), x.end(), std::back_inserter( a));
ostream_iteratorstd::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));
iterator_traitsSlož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átoruKaždý iterator ji musí definovatObvykle nepřímo definováním těchto typů uvnitř třídy iterátoru
•Pokud to nejde, explicitní specializací šablony
template<class Iterator> struct iterator_traits { typedef typename Iterator::difference_type difference_type; typedef typename Iterator::value_type value_type; typedef typename Iterator::pointer pointer; typedef typename Iterator::reference reference; typedef typename Iterator::iterator_category iterator_category;};
template<class T> struct iterator_traits<T*> { typedef ptrdiff_t difference_type; typedef T value_type; typedef T* pointer; typedef T& reference; typedef random_access_iterator_tag iterator_category;};
std::iteratorPomů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;};
std::iteratorPříklad
my_iterator b, e;std::distance( b, e)
distance potřebuje znát iterator_category a difference_typestd::iterator_traits< my_iterator>::iterator_category
iterator_traits vyřeší případ ukazatelů a přesměruje problém na samotný iterátormy_iterator::iterator_category
uživatelský iterátor dědí instanci std::iterator s vhodnými parametrystd::iterator<...>::iterator_category
Šablony tříd – závislé typyŠablony tříd (včetně těl metod) se při deklaraci kontrolují pouze částečně
•Překladač může kontrolovat jen to, co nezávisí na parametru•Některé překladače nedělají ani to
Překladač potřebuje odlišit jména typů od ostatních jmen•U jmen ve tvaru A::B to překladač někdy nedokáže•Programátor musí pomoci klíčovým slovem typename
template< typename T> class X{ typedef typename T::B U; // T::B je typ typename U::D p; // T::B::D je typ typename Y<T>::C q; // Y<T>::C je typ void f() { T::D(); } // T::D není typ}
•typename je nutné uvést před jmény typu ve tvaru A::B, kde A je závislé jméno•Závislé jméno je jméno obsahující přímo či nepřímo parametr šablony
Šablony tříd - thisPokud 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í funkcetemplate< typename T> class X: public T{ void f() { return m(); } // globální funkce m() void g() { return this->m(); } // metoda třídy T (nebo předka) void h() { return T::m(); } // metoda třídy T (nebo předka)}
problém lze také vyřešit pomocí usingtemplate< typename T> class X: public T{ using T::m; void h() { return m(); }}
my_iteratorIterator je obvykle šablona
template< typename T> class my_iterator: public std::iterator< std::forward_iterator_tag, T>{private: typedef std::iterator< std::forward_iterator_tag, T> base_;public: using typename base_::reference; using typename base_::pointer; reference operator*() const; pointer operator->() const; //...};
my_const_iteratorKompletní kontejner musí zvládat čtení
nemodifikovatelného kontejneru
void f( const my_container<T> & k){ for ( my_container<T>::const_iterator it = k.begin(); it != k.end; ++it) // ...}
Kontejner musí poskytovat dvojice metod ošetřující consttemplate< typename T> class my_container{public: typedef my_iterator< T> iterator; typedef my_const_iterator< T> 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
Rvalue reference
Francisco de Goya. Sv. František Borgiáš u lože umírajícího (detail). 1788.
const T &
T &&
T
Rvalue referenceRvalue reference
Nová typová konstrukceT &&
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ů
Rvalue referenceRvalue 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?
Zbytečná kopírováníŘešení pomocí rvalue referencí
Ř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
Ř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
Řešení – inicializaceTotéž 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
copy/moveSpeciální metody tříd – C++11
copy/moveSpeciální metody tříd
Copy constructorT( const T & x);
Move constructorT( T && x);
Copy assignment operatorT & operator=( const T & x);
Move assignment operatorT & operator=( T && x);
C++11
copy/movePřekladačem definované chování (default)
Copy constructorT( const T & x) = default;
aplikuje copy constructor na složkyMove constructor
T( T && x) = default; aplikuje move constructor na složky
Copy assignment operatorT & operator=( const T & x) = default;
aplikuje copy assignment operator na složkyMove assignment operator
T & operator=( T && x) = default; aplikuje move assignment operator na složky
default umožňuje vynutit defaultní chování
C++11
copy/movePodmí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
copy/moveNejběž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/moveT( const T & x);T( T && x);T & operator=( const T & x);T & operator=( T && x);~T();
C++11
copy/moveDalší kombinace
Nekopírovatelná třída Např. dynamicky alokované živé objekty v simulacích
T( const T & x) = delete;T & operator=( const T & x) = delete;
delete zakazuje generování překladačem Destruktor může ale nemusí být nutný
Přesouvatelná nekopírovatelná třída Např. unikátní vlastník jiného objektu (viz std::unique_ptr< U>)
T( T && x);T & operator=( T && x);~T();
Pravidla jazyka zakazují generování copy metod překladačem Destruktor typicky bývá nutný
C++11
Třídy obsahující velká dataŘešení podle C++11
Matrix - Řešení s podporou moveclass 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
Matrix – copy metody a destruktorMatrix::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
Matrix – move metodyMatrix::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
Matrix – move metody - variantaMatrix::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
lvalue/rvaluePravidla
lvalue/rvalueU 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
lvalue/rvalue
Pravidla předávání parametrů
Kategorie a typ výrazu
lvalue rvalue
T const T T const T
Typ parametru
T, const T OK OK OK OK
T & OK (+) --- --- ---
const T & OK (-) OK OK (-) OK (-)
T && --- --- OK (+) ---
const T && --- --- OK (+) OK (+)OK (+) má přednost před OK (-)
Preferované případy, neobvyklé případyPlatí 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
Třídy obsahující velká dataOperátory podle C++11
Matrix - Řešení s podporou moveclass 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
Matrix - Řešení s podporou moveMatrix 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
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
lvalue/rvalueNové rozhraní STL
lvalue/rvalue - STLNová 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
movestd::move( b);
Zkratka za:static_cast< T &&>( b);
C++11
lvalue/rvalue - STLCopy/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
Emplacetemplate< typename ... TList>iterator emplace( const_iterator p, TList && ... plist);
Zkonstruuje objekt přímo na místě plist se předá jako parametry konstruktoru Existuje i emplace_back/emplace_front
Další optimalizace uvnitř implementace kontejnerů Realokace vektoru používá move namísto copy
C++11
Variadic templates
Šablony s proměnlivým počtem parametrůHlavička šablony
s proměnlivým počtem typových argumentů
template< typename ... TList>class C { /* ... */ };
pojmenovaný parametr zastupující seznam typůlze i kombinovat s pevnými parametry
template< typename T1, int c2, typename ... TList>class D { /* ... */ };
platí i pro hlavičky parciálních specializací
template< typename T1, typename ... TList>class C< T1, TList ...> { /* ... */ };
C++11
Šablony s proměnlivým počtem parametrůtemplate< typename ... TList>
pojmenovaný parametr - seznam typůlze uvnitř šablony použít v těchto konstrukcích:
•vždy se suffixem ...typové argumenty v použití (jiné) šablony
X< TList ...> Y< int, TList ..., double>
seznam předků třídy class E : public TList ...
deklarace parametrů funkce/metody/konstruktoru void f( TList ... plist); double g( int a, 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
Šablony s proměnlivým počtem parametrůtemplate< typename ... TList> void f( TList ... plist);
pojmenovaný parametr - seznam hodnotlze 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
Šablony s proměnlivým počtem parametrůtemplate< typename ... TList> void f( TList ... plist);
při použití je možno prvky seznamu obalit •suffix ... slouží jako kompilační for_each•(každý) výskyt názvu seznamu je nahrazen jeho i-tým prvkem
výsledkem jeseznam typů v parametrech šablony nebo deklaraci funkce
X< std::pair< int, TList *> ...> class E : public U< TList> ... void f( const TList & ... plist);
seznam výrazů ve volání funkce/metody/konstruktoru g( make_pair( 1, & plist) ...); h( static_cast< TList *>( plist) ...); i( sizeof( TList) ...); // pozor, sizeof...( TList) je něco jiného
seznam inicializátorů v konstruktorudalší okrajové případy
C++11
Generická N-ticetemplate <class ... Types> class tuple {public: tuple( const Types & ...); /* black magic */};template < size_t I, class T> class tuple_element {public: typedef /* black magic */ type;};template < size_t I, class ... Types> typename tuple_element< I, tuple< Types ...> >::type & get( tuple< Types ...> & t);
použitítypedef tuple< int, double, int> my_tuple;typedef typename tuple_element< 1, my_tuple>::type alias_to_double;
my_tuple t1( 1, 2.3, 4);double v = get< 1>( t1);
C++11: <utility>
lvalue/rvaluePerfect forwarding
Perfect forwarding
template< typename ... TList>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
Perfect forwarding
template< typename ... TList>iterator emplace( const_iterator p, TList && ... plist){ void * q = /* místo pro nový prvek */;
pointer r = new( q) value_type( plist ...);
/* ... */}
Jak dokáže emplace předat parametry? Co když je skutečným parametrem lvalue?
• lvalue nesmí být předána jako rvalue-reference Proč neexistuje verze s const TList & Bylo by jich exponenciálně mnoho!
C++11
Perfect forwardingTrik: Skládání referencí
Jazyk definuje tato pravidla (pouze pro šablony)
Použijí se u šablon funkcí s parametrem typu T &&
template< typename T> void f( T && p);
X lv;f( lv);
Parametr typu T && lze navázat na lvalue typu X Dosadí se T = X &, 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++11X & & X &X && & X &X & && X &X && && X &&
Perfect forwardingForwarding
template< typename T> void f( T && p){ g( p);}
X lv;f( lv);
Parametr typu T && lze navázat na lvalue typu X Dosadí se T = X &
f( std::move( lv)); Je-li skutečným parametrem rvalue typu X Dosadí se T = X Parametr p 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
Perfect forwardingPerfect forwarding
template< typename T> void f( T && p){ g( std::forward< T>( p));}
std::forward< T> vrací T &&
X lv;f( lv);
T = X & skládání referencí zajistí, že std::forward< T> vrací X &
f( std::move( lv)); T = X std::forward< T> vrací X && v tomto případě se std::forward< T> chová jako std::move
C++11
Perfect forwarding
Správná implementace emplace
template< typename ... TList>iterator emplace( const_iterator p, TList && ... plist){ void * q = /* místo pro nový prvek */;
pointer r = new( q) value_type( std::forward< TList>( plist) ...);
/* ... */}
C++11
Smart pointers
Smart pointersChytré 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< T>• Varianta pro pole: Operátor [] namísto * a ->
std::unique_ptr< T[]>Sdílené vlastnictví
Založeno na počítání odkazůstd::shared_ptr< T>
Vedlejší odkaz, který dovoluje zánik sdíleného objektustd::weak_ptr< T>• Lze povýšit na shared_ptr, pokud objekt dosud nezanikl
C++11
Smart pointersVýlučné vlastnictví
{ std::unique_ptr< T> q; // obsahuje nullptr { std::unique_ptr< T> p = new T( /*...*/); q = std::move( p); } q->f(); h( * q);} // zde zaniká q i objekt T
Sdílené vlastnictví{ std::shared_ptr< T> q; // obsahuje nullptr { std::shared_ptr< T> p = std::make_shared< T>( /*...*/); q = p; // zde se zvětšuje čítač p->f(); } // zde se zmenšuje čítač { std::weak_ptr< T> r = q; h( * r); } } // zde zaniká q i objekt T
C++11
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, metodyprivate: kind_of_smart_ptr< BigBody> 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
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< BigBody> b_;};
C++11
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< AbstractBody> b_;};
C++11
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< BigBody>( /*...*/)) {}
Big & operator+=( const Big & x) { if ( ! b_.unique() ) b_ = std::make_shared< BigBody>( * b_); b_->add( x.b_.get()); }private: std::shared_ptr< BigBody> b_;};
C++11
Smart pointers – typy s referenční semantikouReferenč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< body>?• Na třídě Ref lze definovat hezčí rozhraní včetně operátorů
class Ref {public: Ref( /*...*/) : b_( std::make_shared< Body>( /*...*/)) {}
Ref & operator+=( const Ref & x) { b_->add( x.b_.get()); }private: std::shared_ptr< Body> b_;};
C++11
weak_ptr – nepovinné odkazyPříklad: soubory s cache
typedef std::shared_ptr< FileBody> FileRef;typedef std::weak_ptr< FileBody> WeakFileRef;
class Pool {public: FileRef open(/*...*/) { FileRef r = std::make_shared< FileBody>( this); files_.push_back( r); return r; } void panic() { for_each( files_.begin(), files_.end(), []( const WeakFileRef & fw) { FileRef fs = fw.lock(); if ( fs ) fs->release_cache(); }); }private: std::vector< WeakFileRef > 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
Policy class, traitsPokročilé použití šablon
Motivace k použití šablonSpolečná implementace příbuzných problémů
Autor šablony šetří práci sobě vector< int> vs. vector< double>
• implementace se liší pouze záměnou int/double vector< int> vs. vector< bool>
• odlišné problémy - dělení bajtů na bity• drobná odlišnost interface - problém reference na bit
vector< int> vs. vector< unique_ptr< X>>• 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< typename T>T scalar_product( const vector< T> & x, const vector< T> & y);
template< typename K>typename K::value_type scalar_product( const K & x, const K & y);
Ř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á
Řešení nepravidelností v šablonách Řešení příbuzných problémů s odlišnými detaily
Šablony různých jmentemplate< typename T> class vector { /*...*/ };template< typename T> class list { /*...*/ };
• podobnost interface dovoluje použití ve společném algoritmu
Parciální/explicitní specializace šablonytemplate< typename T> class vector { /*...*/ };template<> class vector< bool> { /*...*/ };
Přídavné parametry šablony, policy classestemplate< typename T, typename comparator> class map
{ /*...*/ comparator::cmp() /*...*/};
Automatické odvození parametrů - traitstemplate< typename T> class less;template<> class less< char *> { /*...*/ strcmp() /*...*/ };template< typename T> class map { /*...*/ less< T>::cmp() /*...*/ };
Kombinace policy classes a traitstemplate< typename T, typename comparator = less< T>> class map;
Š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< formální-parametry> lze použít před několika formami deklarací; oblastí platnosti
formálních parametrů je celá prefixovaná deklarace
Š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řídtemplate< typename T, int k> // parametry šablony int f( T * p, int q); // parametry funkcetemplate< typename T, typename U> // parametry šablony int g( T * p, vector< U> q); // parametry funkce
Šablony funkcí lze volat dvěma způsoby Explicitně
f< int, 729>( a, b) Automaticky
g( a, b)• Překladač dopočte parametry šablony z typů parametrů funkce
• Pokud to jde Kombinace - počáteční parametry explicitně, zbytek automaticky
Nepříjemnosti spojené se šablonami
class T{ class c {}; template< int U> class tc {}; void f( int) {} template< int U> 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<1>(* m3); // deklarace T::tf<1>(* m4); // příkaz T::v<1>(* m5); // příkaz};
Překladače mohou provádět částečnou (syntaktickou) analýzu šablon při jejich deklaraci
Problém: Poruzumění syntaxiv C++ vyžaduje částečnou znalost vlastností identifikátorů
int * m1, * m2, * m3, * m4, * m5;
template< typename T>void t1(){ T::c(* m1); // ??? T::f(* m2); // ??? T::tc<1>(* m3); // ??? T::tf<1>(* m4); // ??? T::v<1>(* m5); // ???};
Nepříjemnosti spojené se šablonami
class T{ class c {}; template< int U> class tc {}; void f( int) {} template< int U> 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<1>(* m3); // deklarace T::tf<1>(* m4); // příkaz T::v<1>(* m5); // příkaz};
Řešení: Nápověda syntaktickému analyzátoru pomocí typename a template
int * m1, * m2, * m3, * m4, * m5;
template< typename T>void t1(){ typename T::c(* m1); // deklarace T::f(* m2); // příkaz typename T::template tc<1>(* m3); // deklarace T::template tf<1>(* m4); // příkaz T::v<1>(* m5); // příkaz};
Nepříjemnosti spojené se šablonami
class T{ class c {}; template< int U> class tc {}; void f( int) {} template< int U> void tf( int) {} static int v;};
class X : public T{ void t1() { c(* m1); // deklarace f(* m2); // příkaz tc<1>(* m3); // deklarace tf<1>(* m4); // příkaz v<1>(* 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<1>(* m3); // deklarace this->template tf<1>(* m4); // příkaz T::v<1>(* m5); // příkaz }};
Šablony s mnoha parametry vs. policy class
template< typename T, bool binary, bool cached>class File { // ... T get() { if ( binary ) // ... }};
File< char, false, true> my_file;
template< typename P>class File { // ... typename P::T get() { if ( P::binary ) // ... }};
struct my_policy { typedef char T; static const bool binary = false; static const bool cached = true;};
File< my_policy> my_file;
Policy class
template< typename P>class File { // ... typename P::T get() { if ( P::binary ) // ... }};
struct my_policy { typedef char T; static const bool binary = false; static const bool cached = true;};
File< my_policy> my_file;
Policy class (politika)Třída (class/struct) použitá pouze jako parametr šablonyNeexistuje objekt tohoto typuObsahuje 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
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< char, cached, binary> my_file;
Policy class (politika)Třída (class/struct) použitá pouze jako parametr šablonyNeexistuje objekt tohoto typuObsahuje 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
Ekvivalence typů
template< typename P>class File { // ... typename P::T get() { if ( P::binary ) // ... }};
struct my_policy { typedef char T; static const bool binary = false; static const bool cached = true;};
File< my_policy> my_file;
struct my_policy2 { typedef char T; static const bool binary = false; static const bool cached = true;};
void f( File< my_policy2> & p);
f( my_file); // error
Při ekvivalenci typů rozhodují jména tříd/struktur obsah typových konstrukcí včetně typedef
my_policy a my_policy2jsou různé typy, tudíž se liší iFile< my_policy>a File< my_policy2>
Využití neekvivalence typů
template< typename P>class Value { typename P::T v; // ...};
struct mass { typedef double T;};
struct energy { typedef double T;};
Value< mass> m;Value< energy> e;
e = m; // error
Silná typová kontrola
Odlišně pojmenované typy mají stejný obsah nejsou kompatibilní
Využití neekvivalence typů
template< typename P>class Value { double v; // ...};
struct mass {};
struct energy {};
Value< mass> m;Value< energy> e;
e = m; // error
Silná typová kontrola
Odlišně pojmenované třídy mají stejný obsah nejsou kompatibilní
Lze použít i třídy bez obsahu tag class
Využití ekvivalence typů
template< int kg, int m, int s>class Value { double v; // ...};
template< int kg1, int m1, int s1, int kg2, int m2, int s2>Value< kg1+kg2, m1+m2, s1+s2> operator*( const Value< kg1, m1, s1> & a, const Value< kg2, m2, s2> & b);
typedef Value< 1, 0, 0> Mass;typedef Value< 0, 1, -1> Velocity;typedef Value< 1, 2, -2> Energy;
Mass m;Velocity c;Energy e;e = m * c * c; // OK
Instance šablony Value se stejnými hodnotami číselných parametrů jsou ekvivalentní
Konstrukce typedef je transparentní
Explicitní specializace
template< int kg, int m, int s>struct Unit { static void print( ostream & os) { os << ”kg^” << kg << ”.m^” << m << ”.s^” << s; }};
template<>struct Unit< 1, 2, -2> { 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< int kg, int m, int s>ostream & operator<<( ostream & os, const Value< kg, m, s> & x){ os << x << “ “; Unit< kg, m, s>::print( os); return os;}
Parciální specializacetemplate< class X, class Y> class C { /* základní definice */ };
Parciální specializace Deklarovanou šablonu lze pro určité kombinace parametrů
předefinovat jinak, než určuje její základní definice• Parciální specializace může mít stejný, menší i větší počet formálních
parametrů než základní definice, jejich hodnoty se odvozují ze skutečných parametrů šablony (kterých je vždy tolik, kolik určuje základní definice)
template< class T, class U, int n> class C< T[n], U[n]> { /* specializace pro dvě pole stejné velikosti */ };
Explicitní specializace template<> class C< char, int[ 8]> { /* ... */ };
Explicitní specializace šablony není šablona Podléhá trochu jiným (jednodušším) pravidlům
• Překlad se neodkládá• Těla metod se nemusí psát do hlavičkových souborů
Parciální specializaceTypická použití parciální a explicitní specializace
Výhodnější implementace ve speciálních případech Programátor - uživatel šablony o specializaci nemusí vědět Příklad: Implementace vector<char> může být jednodušší
Mírná změna rozhraní ve speciálních případech Uživatel by měl být o specializaci informován Příklad: vector< bool> nedovoluje vytvořit ukazatel na jeden prvek
Modifikace chování jiné šablony - traits Specializovaná šablona je volána z jiného šablonovaného kódu Autor volající šablony předpokládá, že ke specializaci dojde
• Někdy dokonce ani nedefinuje základní obsah volané šablony Autor specializace tak upravuje chování volající šablony Příklad: šablona basic_string<T> volá šablonu char_traits<T>, ve
které je např. definována porovnávací funkceGenerické programování
Výpočty (s celými čísly a typovými konstrukcemi) při překladu
Parciální specializace - traitsModifikace chování jiné šablony - traits
Specializovaná šablona je volána z jiného šablonovaného kódu Autor volající šablony předpokládá, že ke specializaci dojde
• Někdy dokonce ani nedefinuje základní obsah volané šablony Autor specializace tak upravuje chování volající šablony Příklad: šablona basic_string<T> volá šablonu char_traits<T>, ve
které je např. definována porovnávací funkce
template< class T> struct char_traits;
template< class T> class basic_string { /* ... */ int compare( const basic_string< T> & b) const { /*...*/ char_traits< T>::compare( /* ... */) /*...*/ }};
template<> struct char_traits< char> { /* ... */ static int compare(const char* s1, const char* s2, size_t n) { return memcmp( s1, s2, n); }};
Parciální specializaceModifikace 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 objektyObsahují pouze:
Definice typů Konstanty Statické funkce
Určeny k doplnění informací o nějakém typu Příklad: char_traits<T> doplňuje informace o typu T, např.
porovnávací funkci
Traits & policiesTraits
Šablony, ze kterých nejsou vytvářeny objektyObsahují pouze:
Definice typů Statické funkce
Určeny k doplnění informací o nějakém typu Příklad: char_traits<T> doplňuje informace o typu T, např.
porovnávací funkci
Policy classesTřídy, ze kterých obvykle nejsou vytvářeny objektyPředávány jako parametr šablonám
Defaultní hodnotou parametru často bývá šablona traitsUrčeny k definování určitého chování
Příklad: Alokační strategie
Policy class vs. traits
template< typename P, typename K>void for_each( K & data){ for( ... it = ... ) P::f( * it); }
struct policy_add { static void f(...) { ... }};
my_k data;for_each< policy_add>( data);
template< typename K>struct for_each_traits;
template< typename K>void for_each( K & data){ for( ... it = ... ) for_each_traits< K>::f( * it); }
template<>struct for_each_traits< my_k> { static void f(...) { ... }};
my_k data;for_each( data);
PolymorfismusKompilační a běhový
Polymorfismus Polymorfismus
Stejný zdrojový kód v různých situacích dělá různé věciVolá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/switchKód je přehlednější a obsahuje méně chyb
PolymorfismusKompilační polymorfismus
Šablony, zejména:Funktory apod.Policy classes, traits
Běhový polymorfismusC: ukazatele na funkceC++: dědičnost a virtuální funkce
Běhový polymorfismus zdržujePouží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
Polymorfismus běhový a kompilačníclass functor {public: virtual void f( ...) = 0;};
void for_each( functor & x){ for( ... it = ... ) x.f( * it); // run-time binding}
class functor_add : public functor { virtual void f(...) { ... }};
for_each( functor_add());
template< typename P>void for_each( P & x){ for( ... it = ... ) x.f( * it); // compile-time binding}
struct functor_add { void f(...) { ... }};
for_each( functor_add());
Kompilační polymorfismus s objektem a bez
template< typename P>void for_each( P & x){ for( ... it = ... ) x.f( it); // metoda}
struct functor_add { void f(...) { ... }};
for_each( functor_add());
template< typename P>void for_each(){ for( ... it = ... ) P::f( it); // statická funkce}
struct policy_add { static void f(...) { ... }};
for_each< policy_add>();
Generické programováníVýpočty při překladu
Teoretický pohled na šablonyPřekladač dokáže vyhodnotit celočíselnou aritmetiku
I s rekurzivními funkcemi
template< int N> struct Fib { static const int value = Fib< N-1>::value + Fib< N-2>::value;};
template<> struct Fib< 0> { static const int value = 1;};template<> struct Fib< 1> { static const int value = 1;};
Kontrolní otázka:Jak dlouho trvá výpočet (tj. kompilace) Fib< 1000>::value
Teoretický pohled na šablonyPřekladač dokáže vyhodnotit celočíselnou aritmetiku
I s rekurzivními funkcemi
template< int N> struct Fib { static const int value = Fib< N-1>::value + Fib< N-2>::value;};
template<> struct Fib< 0> { static const int value = 1;};template<> struct Fib< 1> { static const int value = 1;};
Kontrolní otázka:Jak dlouho trvá výpočet (tj. kompilace) Fib< 1000>::valueMS Visual C++ 7.1: Build Time 0:00Kompilátory ukládají již vytvořené instanciace a nepočítají je znovu
Triky s šablonamiPodmíněný výraz nad typy
template< bool C, typename A, typename B>struct conditional { typedef A type;};
template< typename A, typename B>struct conditional< false, A, B> { typedef B type;};
conditional< C, A, B>::type je typPoužití
template< bool wide>class File { typename conditional< wide, wchar_t, char>::type get(); /* ... */};
C++11: <type_traits>
Triky s šablonamiPorovnání typů s booleovským výstupem
template< class A, class B>struct is_same { static const bool value = false;};
template< class A>struct is_same< A, A> { static const bool value = true;};
is_same< X, Y>::value je konstantní výrazPoužití
template< class T1>class Test { static const bool very_long = is_same< long long, T1>::value; typedef conditional< is_long, unsigned long long, unsigned long> T; /* ... */};
C++11: <type_traits>
Triky s šablonamiKompilační ověření invariantu
template< bool x>struct static_assert { struct type {};};
template<>struct static_assert< false> {};
template< int x>struct Assert { typename static_assert< (x > 0)>::type ignore_me();};
template< int x>struct Assert { static_assert( x > 0);};
C++11: vestavěno v jazyce
Teoretický pohled na šablonyTriky s typovým konstrukcemi
Seznam typů
template< class H, class R> struct List { typedef H Head; typedef R Rest;};
struct EmptyList {};
Použití
typedef List< char *, List< const char *, List< std::string, EmptyList> > > StringTypes;
Teoretický pohled na šablonyTriky s typovým konstrukcemi
Seznam typů
template< class H, class R> struct List { typedef H Head; typedef R Rest;};
struct EmptyList {};
Jiné použití
struct Apple {}; struct Pear {}; struct Plum {};
typedef List< Apple, List< Pear, List< Plum, EmptyList> > > Fruits;
Teoretický pohled na šablonyTriky s typovým konstrukcemi
Seznam typů
template< class H, class R> struct List { typedef H Head; typedef R Rest;};
struct EmptyList {};
Funkce na seznamu typůtemplate< class L> struct First { typedef typename L::Head Result;};
Teoretický pohled na šablonyTriky s typovým konstrukcemi
Seznam typů
template< class H, class R> struct List { typedef H Head; typedef R Rest;};
struct EmptyList {};
Funkce na seznamu typůtemplate< class L> struct First { typedef typename L::Head Result;};
struct NullType {};template<> struct First< EmptyList> { typedef NullType Result;};
Teoretický pohled na šablonyTriky s typovým konstrukcemi
Seznam typů
template< class H, class R> struct List { typedef H Head; typedef R Rest;};
struct EmptyList {};
Funkce na seznamu typůtemplate< class L, int n> struct Nth { typedef typename Nth< typename L::Rest, n-1>::Result Result;};template< class L> struct Nth< L, 0> { typedef typename L::Head Result;};
Teoretický pohled na šablonyTriky s typovým konstrukcemi
Jiná implementace seznamu typů
template< class H, class R> struct List;
struct EmptyList;
Funkce na seznamu typůtemplate< class L, int n> struct Nth;
template< class H, class R, int n> struct Nth< List< H, R>, n> { typedef typename Nth< R, n-1>::Result Result;};template< class H, class R> struct Nth< List< H, R>, 0> { typedef H Result;};
Teoretický pohled na šablonyTriky s typovým konstrukcemi
Moderní implementace seznamu typů
template< typename ... L> struct tuple;
Funkce na seznamu typů
template< size_t n, typedef X> struct tuple_element;
template< size_t n, typename H, typename ... R> struct tuple_element< n, tuple< H, R ...> > { typedef typename tuple_element< n-1, tuple< R ...> >::type type;};
template< typename H, typename ... R> struct tuple_element< 0, tuple< H, R ...> > { typedef H type;};
C++11: <tuple>
Teoretický pohled na šablonyTriky s typovým konstrukcemi
Zlomková aritmetika
template< intmax_t n, intmax_t d = 1> struct ratio;
Příklad použití
typedef ratio_add<ratio<9>, ratio<3,4>> platform;
static_assert( ratio_equal< platform, ratio< 975, 100>>::value);
Vyhodnocuje překladač• Včetně výpočtu největšího společného dělitele!
C++11: <ratio>
Teoretický pohled na šablonyVý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ěhuData:
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ř
Lambda
Lambda výrazyMotivace
class ftor {public: ftor(int a, int b) : a_(a),b_(b) { } bool operator()(int x) const { return x*a_<b_; }private: int a_, b_; };
typedef std::vector<int> v_t; v_t v;
v_t::iterator vi=remove_if(v.begin(), v.end(), ftor(m, n));
Řešenístd::vector<int> v;
auto vi=remove_if(v.begin(), v.end(), [=](int x){ return x*m<n; });
C++11
Lambda výrazyLambda výraz
[ capture ]( params ) mutable -> rettype { body }
Deklaruje třídu ve tvaruclass 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 objektuftor( vlist ...)
C++11
Lambda výrazy – návratový typ a typ funkceNá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
Lambda výrazy – captureCapture
[ 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
Lambda výrazy – příkladint 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
Exception handlingMechanismus výjimek
Exception handlingMechanismus výjimek
Start: příkaz throwCíl: try-catch blok
Určen za běhuSkok může opustit proceduru
Proměnné korektně zaniknouvolá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 ) { /*...*/ }}
Exception handlingMechanismus výjimek
Start: příkaz throwCíl: try-catch blok
Určen za běhuSkok může opustit proceduru
Proměnné korektně zaniknouvolá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 &) { /*...*/ }}
Exception handlingMechanismus výjimek
Start: příkaz throwCíl: try-catch blok
Určen za běhuSkok může opustit proceduru
Proměnné korektně zaniknouvolá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 (...) { /*...*/ }}
Exception handlingFá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 throwProvedení 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-unwindingZpracová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
Exception handlingZhmotně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
Exception handlingZhmotně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<T> p;std::future<T> 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
Exception handlingPouž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á
Exception handlingStandardní výjimky
<stdexcept> Všechny standardní výjimky jsou potomky třídy exception
metoda what() vrací řetězec s chybovým hlášenímbad_alloc: vyvolává operátor new při nedostatku paměti
V režimu "bez výjimek" new vrací nulový ukazatelbad_cast, bad_typeid: Chybné použití RTTIOdvozené 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
Exception handlingStandardní výjimky
<stdexcept> Všechny standardní výjimky jsou potomky třídy exception
metoda what() vrací řetězec s chybovým hlášenímbad_alloc: vyvolává operátor new při nedostatku paměti
V režimu "bez výjimek" new vrací nulový ukazatelbad_cast, bad_typeid: Chybné použití RTTIOdvozené 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
Exception specificationsException 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
Exception specificationsException 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< typename T>void g( T & y) noexcept( std::is_nothrow_copy_constructible < T>::value){ T x = y;}
C++11
Exception-safe programmingBezpečné programování s výjimkami
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
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
Exception-safe programmingPravidla 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čí
Exception-safe programmingPravidla 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
Exception-safe programmingPravidla 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é)
Exception-safe programmingPravidla 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í
Exception-safe programmingPravidla 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
Exception-safe programmingPozná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é
Exception-safe programmingKompilá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
Exception-safe programmingKompilá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;
Exception-safe programmingDefinice
(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
Exception-safe programmingDefinice
(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"
Exception-safe programmingPozná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
Exception-safe programmingKonstruktory a operator=
Exception-safe programmingcopy-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(); }}
Exception-safe programmingcopy-constructor
Silně bezpečné řešení
Chytré ukazatele std::unique_ptr< T>
#include <memory>
class String { /*...*/ std::unique_ptr< char[]> str_;};
String( const String & b){ if ( b.str_ ) { str_.reset( new char[ strlen( b.str_) + 1]); strcpy( str_.get(), b.str_.get()); } else { throw InvalidString(); }}
C++11
Exception-safe programmingoperator=
Silně bezpečné řešení Pokud je copy-constructor silně bezpečný Copy-constructor naplní lokální proměnnou c kopií parametru b
• Zde může dojít k výjimce Metoda swap_with vyměňuje obsah this a proměnné c
• Knihovní funkce swap je rychlá a (na ukazatelích) nevyvolává výjimky Před návratem z operatoru se volá destruktor c
• Tím zaniká původní obsah this
#include <algorithm>
void String::swap_with( String & x){ swap( str_, x.str_);}
String & String::operator=( const String & b){ String c( b); swap_with( c); return * this;}
Exception-safe programmingoperator=
Silně bezpečné řešení
Metodu swap_with je vhodné publikovat ve formě globální funkce se standardním jménem swap
Některé algoritmy nad kontejnery obsahujícími String se tak zrychlí a stanou se bezpečnými vůči výjimkám
#include <algorithm>
void String::swap_with( String & x){ swap( str_, x.str_);}
String & String::operator=( const String & b){ String c( b); swap_with( c); return * this;}
void swap( String & x, String & y){ x.swap_with( y);}
před C++11
Exception-safe programmingMove metody
Obvykle negenerují výjimky
Některé algoritmy nad kontejnery obsahujícími String se tak zrychlí a stanou se bezpečnými vůči výjimkám
Vlastní implementace globální funkce swap není zapotřebí Knihovní implementace swap volá move metody
#include <algorithm>
String::String( String && b) : str_( std::move( b.str_)){}
String & String::operator=( String && b){ str_ = std::move( b.str_); return * this;}
C++11
Exception-safe programmingFunkce s vedlejšími efekty
Exception-safe programmingPří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;}
Exception-safe programmingPříklad: StringStack::pop
Zásobník prvků typu String Implementován seznamem
Slabě bezpečná implementaceNení 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;}
Exception-safe programmingPříklad: StringStack::pop
Zásobník prvků typu String Implementován seznamem
Silně bezpečná implementace Jak zrušíme proměnnou p, když k výjimce nedojde?
std::unique_ptr< T>
#include <memory>
String StringStack::pop(){ if ( ! top_ ) throw StackEmpty(); std::unique_ptr< Box> p = top_; top_ = p->next; try { return p->v; } catch ( ...) { top_ = std::move( p); // toto přiřazení nuluje p throw; }}// při návratu se automaticky zruší p// pokud je p nenulové
Exception-safe programmingPříklad: StringStack::pop
Zásobník prvků typu String Implementován seznamem
Silně bezpečná implementaceUž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 (...){ /* ??? */}
Exception-safe programmingPouč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é ++, --
Exception-safe programmingPříklad: StringStack::pop
Zásobník prvků typu String Implementován seznamem
Řešení AJako v STLRozdě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 */}
Exception-safe programmingPříklad: StringStack::pop
Zásobník prvků typu String Implementován seznamem
Řešení BNamí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 */}
Exception-safe programmingPříklad: StringStack::pop
Zásobník prvků typu String Implementován seznamem
Řešení BLze implementovat nad řešením A
#include <memory>
class StringStack {public: /* A */ String & top(); void pop();
/* B */ void pop( String & out) { String & t = top(); swap( out, t); try { pop(); } catch (...) { swap( out, t); throw; } }};
Kompilační a běhovýpolymorfismus
Příklad
Příklad: Vektorové operace
template< typename P>std::vector< typename P::result_type> for_vector(
P g, std::vector< typename P::first_argument_type> x, const std::vector< typename P::second_argument_type> & 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
Příklad: Vektorové operace
typedef std::vector< int> my_vector;my_vector x( 3, 7), y( 3, 2);
auto op = std::plus< int>();
my_vector z = for_vector( op, x, y);
std::plus< int> je binární funktor definovaný normou C++• obaluje operátor + do funktoru, doplňuje typové položky
C++11
Polymorfismus: Dynamicky volené operace
std::string s = ...;
typedef std::vector< int> my_vector;my_vector x( 3, 7), y( 3, 2);
auto op = s == ”+” ? std::plus< int>() : std::minus< int>();
my_vector z = for_vector( op, x, y);
Chyba: std::plus< int> a std::minus< int> jsou rozdílné typy• Operátor ? : je nedokáže převést na společný typ
C++11
Polymorfismus: Dynamicky volené operace
std::string s = ...;
typedef std::vector< int> my_vector;my_vector x( 3, 7), y( 3, 2);
typedef std::function< int( int, int)> my_function;auto op = s == "+" ?
my_function( std::plus< int>()) : my_function( std::minus< int>());
my_vector z = for_vector( op, x, y);
Chyba odstraněna:• std::function< int( int, int)> 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
Polymorfismus: Dynamicky volené operace
std::string s = ...;
typedef std::vector< int> my_vector;my_vector x( 3, 7), y( 3, 2);
auto opplus = vectorize( std::plus< int>());auto opminus = vectorize( std::minus< int>());
typedef std::function< my_vector( my_vector, const my_vector &)> 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
Polymorfismus: Dynamicky volené operacetemplate< typename P>struct vectorized{
vectorized( P g) : g_( g) {}typedef std::vector< typename P::first_argument_type> first_argument_type;typedef std::vector< typename P::second_argument_type> second_argument_type;typedef std::vector< typename P::result_type> 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< typename P>vectorized< P> vectorize( P g){ return vectorized< P>( g); }
vectorized< P> je vektorová verze funktoru P
Funkce vectorize umožňuje automatické odvození typu P z parametru g
C++11
Polymorfismus: Dynamicky volené operaceauto opplus = vectorize( std::plus< int>());auto opminus = vectorize( std::minus< int>());
auto op = s == "+" ? dynamize( opplus) : dynamize( opminus);
Funkce dynamize umožňuje automatické odvození typu polymorfní obálky
template< typename P>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
Polymorfismus: Dynamicky volené operace Srozumitelnější zápis
Trait dynamize_type umožňuje automatické odvození typu polymorfní obálky
template< typename P>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 P>typename dynamize_type< P>::type dynamize( P g){
return dynamize_type< P>::type( g);}
Přímé využití dynamize_type
typedef std::binary_function< my_vector, my_vector, my_vector> my_function;std::map< std::string, dynamize_type< my_function>::type> operator_map;
operator_map.emplace( “+”, vectorize( std::plus< int>()));
C++11
Koenig lookup
iostreamProblé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
iostreamProblé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
Koenig lookupprostor::Souradnice p;std::cout << p; // správný operator<< je v namespace prostor,
// který není přímo vidětstd::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
Koenig lookupKoenigovo 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
Koenig lookupKoenigovo 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 namespaceIdentifiká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)
-----------------------------------