+ All Categories
Home > Documents > Pokro čilé p rogramování v C++ (část B)

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

Date post: 17-Mar-2016
Category:
Upload: zudora
View: 52 times
Download: 0 times
Share this document with a friend
Description:
Pokro čilé p rogramování v C++ (část B). David Bednárek www.ksi.mff.cuni.cz. C++ 11. C++11. Jazyk && (rvalue reference) Lambda auto Variadic templates Inicializ átory Podpora metaprogramov ání (constexpr, decltype, static _assert, ... ) ... a mnoho dal ších drobností Knihovny - PowerPoint PPT Presentation
169
Pokro Pokro čilé p čilé p rogramování v rogramování v C++ C++ (část B) (část B) David Bednárek www.ksi.mff.cuni.cz
Transcript
Page 1: Pokro čilé p rogramování v C++ (část B)

PokroPokročilé pčilé programování v C++rogramování v C++(část B)(část B)

David Bednárek

www.ksi.mff.cuni.cz

Page 2: Pokro čilé p rogramování v C++ (část B)

C++C++1111

Page 3: Pokro čilé p rogramování v C++ (část B)

C++11Jazyk

&& (rvalue reference) Lambda auto Variadic templates Inicializátory Podpora metaprogramování (constexpr, decltype, static_assert, ...) ... a mnoho dalších drobností

Knihovny Nové kontejnery (C++ 2003 TR1) Podpora paralelismu

• Atomické operace, fence• Vlákna, thread-local storage• Synchronizační primitiva (mutex, lock, condition_variable,...)

regexp, random, ...

Page 4: Pokro čilé p rogramování v C++ (část B)

PokroPokročilý pohled na čilý pohled na kontejnerykontejnery

Page 5: Pokro čilé p rogramování v C++ (část B)

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_multiset

Odlišné přidávání/odebírání Shodný způsob procházení Každý kontejner definuje

Typy iterator, const_iterator Metody begin(), end()

Iterátory jsou inspirovány ukazatelovou aritmetikou

Ukazatele do pole se chovají jako iterátory

Algoritmy mohou pracovat s iterátory libovolného

kontejneru s ukazateli do pole s čímkoliv, co má potřebné

operátory

void clr( int & x){ x = 0;}std::vector< int> v;for_each( v.begin(), v.end(), clr);std::array< int, N> a;for_each( a.begin(), a.end(), clr);int p[ N];for_each( p, p + N, clr);

C++11

C++11

Page 6: Pokro čilé p rogramování v C++ (část B)

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

Page 7: Pokro čilé p rogramování v C++ (část B)

Použití iterátorůPravidla použití iterátorů

Procházený rozsah bývá zadán intervalem [b,e)

Dereferencovat e se nesmí Přebíhat za e/před b se nesmí

template< iterator>void f( iterator b, iterator e){ ... = * b; // chyba !

for ( iterator i = e - 1; // chyba ! i >= b; -- i) // chyba ! { ... }}

Page 8: Pokro čilé p rogramování v C++ (část B)

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átory output category

back_inserter, front_inserter, inserter

std::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));

Page 9: Pokro čilé p rogramování v C++ (část B)

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átoru

Kaž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;};

Page 10: Pokro čilé p rogramování v C++ (část B)

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

Page 11: Pokro čilé p rogramování v C++ (část B)

std::iteratorPříklad

my_iterator b, e;std::distance( b, e)

distance potřebuje znát iterator_category a difference_type

std::iterator_traits< my_iterator>::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

Page 12: Pokro čilé p rogramování v C++ (část B)

Š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

Page 13: Pokro čilé p rogramování v C++ (část B)

Š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í funkce

template< typename T> class X: public T{ void f() { return m(); } // globální funkce m() void g() { return this->m(); } // metoda třídy T (nebo předka) void h() { return T::m(); } // metoda třídy T (nebo předka)}

problém lze také vyřešit pomocí usingtemplate< typename T> class X: public T{ using T::m; void h() { return m(); }}

Page 14: Pokro čilé p rogramování v C++ (část B)

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; //...};

Page 15: Pokro čilé p rogramování v C++ (část B)

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

Page 16: Pokro čilé p rogramování v C++ (část B)

Rvalue referenceRvalue reference

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

Page 17: Pokro čilé p rogramování v C++ (část B)

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ů

Page 18: Pokro čilé p rogramování v C++ (část B)

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?

Page 19: Pokro čilé p rogramování v C++ (část B)

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

Page 20: Pokro čilé p rogramování v C++ (část B)

Ř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

Page 21: Pokro čilé p rogramování v C++ (část B)

Ř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

Page 22: Pokro čilé p rogramování v C++ (část B)

Ř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

Page 23: Pokro čilé p rogramování v C++ (část B)

copy/movecopy/moveSpeciální metody tříd – C++11

Page 24: Pokro čilé p rogramování v C++ (část B)

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

Page 25: Pokro čilé p rogramování v C++ (část B)

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

Page 26: Pokro čilé p rogramování v C++ (část B)

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

Page 27: Pokro čilé p rogramování v C++ (část B)

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

Page 28: Pokro čilé p rogramování v C++ (část B)

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

Page 29: Pokro čilé p rogramování v C++ (část B)

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

Page 30: Pokro čilé p rogramování v C++ (část B)

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

Page 31: Pokro čilé p rogramování v C++ (část B)

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

Page 32: Pokro čilé p rogramování v C++ (část B)

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

Page 33: Pokro čilé p rogramování v C++ (část B)

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

Page 34: Pokro čilé p rogramování v C++ (část B)

lvalue/rvaluelvalue/rvaluePravidla

Page 35: Pokro čilé p rogramování v C++ (část B)

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í destruktoruf(), rrf(), std::move( x), std::move( rr)

• Číselné a znakové konstanty jsou rvalueToto je zjednodušená definice

Norma definuje lvalue, xvalue, prvalue, glvalue a rvalue

C++11

Page 36: Pokro čilé p rogramování v C++ (část B)

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řípady

Platí i pro inicializaci proměnných a příkaz returnVše ostatní je jen volání funkcí a operátorů

ZjednodušenoSkutečná pravidla jsou komplikována existencí dalších konverzí

(předek-potomek, číselné konverze, uživatelské konverze, pole, funkce, ...)

C++11

Page 37: Pokro čilé p rogramování v C++ (část B)

TTřídy obsahující velká datařídy obsahující velká dataOperátory podle C++11

Page 38: Pokro čilé p rogramování v C++ (část B)

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

Page 39: Pokro čilé p rogramování v C++ (část B)

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

Page 40: Pokro čilé p rogramování v C++ (část B)

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

Page 41: Pokro čilé p rogramování v C++ (část B)

lvalue/rvaluelvalue/rvalueNové rozhraní STL

Page 42: Pokro čilé p rogramování v C++ (část B)

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

Page 43: Pokro čilé p rogramování v C++ (část B)

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

Page 44: Pokro čilé p rogramování v C++ (část B)

Variadic templatesVariadic templates

Page 45: Pokro čilé p rogramování v C++ (část B)

Š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

Page 46: Pokro čilé p rogramování v C++ (část B)

Šablony s proměnlivým počtem parametrůtemplate< typename ... TList>

pojmenovaný parametr - seznam typůlze uvnitř šablony použít v těchto konstrukcích:

•vždy se suffixem ...typové argumenty v použití (jiné) šablony

X< TList ...> Y< int, TList ..., double>

seznam předků třídy class E : public TList ...

deklarace parametrů funkce/metody/konstruktoru void f( TList ... plist); double g( int a, TList ... b, double c);

•tím vzniká pojmenovaný parametr zastupující seznam hodnot

několik dalších okrajových případů

C++11

Page 47: Pokro čilé p rogramování v C++ (část B)

Š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

Page 48: Pokro čilé p rogramování v C++ (část B)

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

X< std::pair< int, TList *> ...> class E : public U< TList> ... void f( const TList & ... plist);

seznam výrazů ve volání funkce/metody/konstruktoru g( make_pair( 1, & plist) ...); h( static_cast< TList *>( plist) ...); i( sizeof( TList) ...);

seznam inicializátorů v konstruktorudalší okrajové případy

C++11

Page 49: Pokro čilé p rogramování v C++ (část B)

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 tuple_element< 1, my_tuple> alias_to_double;

my_tuple t1( 1, 2.3, 4);double v = get< 1>( t1);

C++11: <utility>

Page 50: Pokro čilé p rogramování v C++ (část B)

lvalue/rvaluelvalue/rvaluePerfect forwarding

Page 51: Pokro čilé p rogramování v C++ (část B)

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 ...);

/* ... */}

new( q) je starý trik: placement new využívá možnost napsat si vlastní alokátor s parametrem navíc

void * operator new( std::size, void * q) { return q; }

C++11

Page 52: Pokro čilé p rogramování v C++ (část B)

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? Proč neexistuje verze s const TList & Bylo by jich exponenciálně mnoho!

C++11

Page 53: Pokro čilé p rogramování v C++ (část B)

Perfect forwardingTrik: Skládání referencí

Jazyk definuje tato pravidla

Použijí se u šablon funkcí s parametrem typu T &&

template< typename T> void f( T && p);

X lv;f( lv);

Parametr typu T && lze navázat na lvalue typu X Dosadí se T = X &

f( std::move( lv)); Je-li skutečným parametrem rvalue typu X Dosadí se T = X

C++11

Page 54: Pokro čilé p rogramování v C++ (část B)

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 je však lvalue Do funkce g by měl být předán pomocí std::move

C++11

Page 55: Pokro čilé p rogramování v C++ (část B)

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

Page 56: Pokro čilé p rogramování v C++ (část B)

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

Page 57: Pokro čilé p rogramování v C++ (část B)

Policy class, traitsPolicy class, traits

Page 58: Pokro čilé p rogramování v C++ (část B)

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

Page 59: Pokro čilé p rogramování v C++ (část B)

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

Page 60: Pokro čilé p rogramování v C++ (část B)

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

Page 61: Pokro čilé p rogramování v C++ (část B)

Š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

Page 62: Pokro čilé p rogramování v C++ (část B)

Š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• Všechny formální argumenty šablony by měly být užity v typech

formálních parametrů funkce

Page 63: Pokro čilé p rogramování v C++ (část B)

Š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;

Page 64: Pokro čilé p rogramování v C++ (část B)

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

Page 65: Pokro čilé p rogramování v C++ (část B)

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

Page 66: Pokro čilé p rogramování v C++ (část B)

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>

Page 67: Pokro čilé p rogramování v C++ (část B)

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í

Page 68: Pokro čilé p rogramování v C++ (část B)

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

Page 69: Pokro čilé p rogramování v C++ (část B)

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í

Page 70: Pokro čilé p rogramování v C++ (část B)

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

Page 71: Pokro čilé p rogramování v C++ (část B)

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ů

Page 72: Pokro čilé p rogramování v C++ (část B)

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

Page 73: Pokro čilé p rogramování v C++ (část B)

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

Page 74: Pokro čilé p rogramování v C++ (část B)

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

Page 75: Pokro čilé p rogramování v C++ (část B)

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

Page 76: Pokro čilé p rogramování v C++ (část B)

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

Page 77: Pokro čilé p rogramování v C++ (část B)

PolymorfismusPolymorfismusKompilační a běhový

Page 78: Pokro čilé p rogramování v C++ (část B)

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

Page 79: Pokro čilé p rogramování v C++ (část B)

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

Page 80: Pokro čilé p rogramování v C++ (část B)

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

Page 81: Pokro čilé p rogramování v C++ (část B)

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

Page 82: Pokro čilé p rogramování v C++ (část B)

Smart pointersSmart pointers

Page 83: Pokro čilé p rogramování v C++ (část B)

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

Page 84: Pokro čilé p rogramování v C++ (část B)

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

Page 85: Pokro čilé p rogramování v C++ (část B)

Smart pointers – použití u velkých datových typůCíl: Zabalit velká data tak,

aby se chovala jako hodnota

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

Page 86: Pokro čilé p rogramování v C++ (část B)

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

Page 87: Pokro čilé p rogramování v C++ (část B)

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

Page 88: Pokro čilé p rogramování v C++ (část B)

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

Page 89: Pokro čilé p rogramování v C++ (část B)

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

Page 90: Pokro čilé p rogramování v C++ (část B)

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

Page 91: Pokro čilé p rogramování v C++ (část B)

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

Page 92: Pokro čilé p rogramování v C++ (část B)

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

Page 93: Pokro čilé p rogramování v C++ (část B)

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

Page 94: Pokro čilé p rogramování v C++ (část B)

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>

Page 95: Pokro čilé p rogramování v C++ (část B)

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>

Page 96: Pokro čilé p rogramování v C++ (část B)

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

Page 97: Pokro čilé p rogramování v C++ (část B)

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;

Page 98: Pokro čilé p rogramování v C++ (část B)

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;

Page 99: Pokro čilé p rogramování v C++ (část B)

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

Page 100: Pokro čilé p rogramování v C++ (část B)

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

Page 101: Pokro čilé p rogramování v C++ (část B)

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

Page 102: Pokro čilé p rogramování v C++ (část B)

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

Page 103: Pokro čilé p rogramování v C++ (část B)

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>

Page 104: Pokro čilé p rogramování v C++ (část B)

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>

Page 105: Pokro čilé p rogramování v C++ (část B)

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

Page 106: Pokro čilé p rogramování v C++ (část B)

LambdaLambda

Page 107: Pokro čilé p rogramování v C++ (část B)

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

Page 108: Pokro čilé p rogramování v C++ (část B)

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

Page 109: Pokro čilé p rogramování v C++ (část B)

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

Page 110: Pokro čilé p rogramování v C++ (část B)

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

Page 111: Pokro čilé p rogramování v C++ (část B)

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

Page 112: Pokro čilé p rogramování v C++ (část B)

Exception handlingException handlingMechanismus výjimek

Page 113: Pokro čilé p rogramování v C++ (část B)

Exception handlingMechanismus výjimek

Start: příkaz throw Cíl: try-catch blok

Určen za běhu Skok 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 ) { /*...*/ }}

Page 114: Pokro čilé p rogramování v C++ (část B)

Exception handlingMechanismus výjimek

Start: příkaz throw Cíl: try-catch blok

Určen za běhu Skok 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 &) { /*...*/ }}

Page 115: Pokro čilé p rogramování v C++ (část B)

Exception handlingMechanismus výjimek

Start: příkaz throw Cíl: try-catch blok

Určen za běhu Skok 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 (...) { /*...*/ }}

Page 116: Pokro čilé p rogramování v C++ (část B)

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

Page 117: Pokro čilé p rogramování v C++ (část B)

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

Page 118: Pokro čilé p rogramování v C++ (část B)

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

Většina kompilátorů umí překládat ve dvou režimech "s" a "bez"• Celý spojovaný program musí být přeložen stejně

Page 119: Pokro čilé p rogramování v C++ (část B)

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

Page 120: Pokro čilé p rogramování v C++ (část B)

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

Page 121: Pokro čilé p rogramování v C++ (část B)

Exception specificationsException specifications

U každé funkce (operátoru, metody) je možno určit seznam výjimek, kterými smí být ukončena

Na výjimky ošetřené uvnitř funkce se specifikace nevztahuje

Pokud není specifikace uvedena, povoleny jsou všechny výjimky

Specifikace respektuje dědičnost, to jest automaticky povoluje i všechny potomky uvedené třídy

void a(){ /* tahle smí všechno */}

void b() throw (){ /* tahle nesmí nic */}

void c() throw ( std::bad_alloc){ /* tahle smí std::bad_alloc */}

void d() throw ( std::exception, MyExc){ /* tahle smí potomky std::exception a MyExc */}

Page 122: Pokro čilé p rogramování v C++ (část B)

Exception specificationsException specifications

Kompilátor zajistí, že nepovolená výjimka neopustí funkci: Pokud by se tak mělo stát, volá se unexpected()

• unexpected() smí vyvolat "náhradní" výjimku Pokud ani náhradní výjimka není povolena, zkusí se vyvolat

std::bad_exception Pokud ani std::bad_exception není povoleno, volá se terminate() a

program končí

Page 123: Pokro čilé p rogramování v C++ (část B)

Exception specificationsException specifications

Kompilátor zajistí, že nepovolená výjimka neopustí funkci

Toto je běhová kontrola Kompilátor smí vydávat

nejvýše varování Funkce smí volat jinou, která

by mohla vyvolat nepovolenou výjimku (ale nemusí)

void f() throw ( std::exception){}

void g() throw (){ f(); /* tohle se smí */}

Page 124: Pokro čilé p rogramování v C++ (část B)

Exception specificationsException specifications

throw( T) specifikace se příliš nepoužívaly

C++11 definuje novou syntaxi noexcept noexcept( c) kde 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

Page 125: Pokro čilé p rogramování v C++ (část B)

Exception-safe programmingException-safe programmingBezpečné programování s výjimkami

Page 126: Pokro čilé p rogramování v C++ (část B)

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

Page 127: Pokro čilé p rogramování v C++ (část B)

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

Page 128: Pokro čilé p rogramování v C++ (část B)

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čí

Page 129: Pokro čilé p rogramování v C++ (část B)

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

Page 130: Pokro čilé p rogramování v C++ (část B)

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

Page 131: Pokro čilé p rogramování v C++ (část B)

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

Page 132: Pokro čilé p rogramování v C++ (část B)

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

Page 133: Pokro čilé p rogramování v C++ (část B)

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é

Page 134: Pokro čilé p rogramování v C++ (část B)

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

Page 135: Pokro čilé p rogramování v C++ (část B)

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;

Page 136: Pokro čilé p rogramování v C++ (část B)

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

Page 137: Pokro čilé p rogramování v C++ (část B)

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"

Page 138: Pokro čilé p rogramování v C++ (část B)

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

Page 139: Pokro čilé p rogramování v C++ (část B)

Exception-safe programmingException-safe programmingKonstruktory a operator=

Page 140: Pokro čilé p rogramování v C++ (část B)

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

Page 141: Pokro čilé p rogramování v C++ (část B)

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

Page 142: Pokro čilé p rogramování v C++ (část B)

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

Page 143: Pokro čilé p rogramování v C++ (část B)

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

Page 144: Pokro čilé p rogramování v C++ (část B)

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

Page 145: Pokro čilé p rogramování v C++ (část B)

Exception-safe programmingException-safe programmingFunkce s vedlejšími efekty

Page 146: Pokro čilé p rogramování v C++ (část B)

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

Page 147: Pokro čilé p rogramování v C++ (část B)

Exception-safe programmingPří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;}

Page 148: Pokro čilé p rogramování v C++ (část B)

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é

Page 149: Pokro čilé p rogramování v C++ (část B)

Exception-safe programmingPří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 (...){ /* ??? */}

Page 150: Pokro čilé p rogramování v C++ (část B)

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

Page 151: Pokro čilé p rogramování v C++ (část B)

Exception-safe programmingPří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 */}

Page 152: Pokro čilé p rogramování v C++ (část B)

Exception-safe programmingPří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 */}

Page 153: Pokro čilé p rogramování v C++ (část B)

Exception-safe programmingPříklad: StringStack::pop

Zásobník prvků typu String Implementován seznamem

Řešení B Lze implementovat nad

řešením A

#include <memory>

class StringStack {public: /* A */ String & top(); void pop();

/* B */ void pop( String & out) { String & t = top(); swap( out, t); try { pop(); } catch (...) { swap( out, t); throw; } }};

Page 154: Pokro čilé p rogramování v C++ (část B)

Kompilační a běhovýKompilační a běhovýpolymorfismuspolymorfismus

Příklad

Page 155: Pokro čilé p rogramování v C++ (část B)

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

Page 156: Pokro čilé p rogramování v C++ (část B)

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

Page 157: Pokro čilé p rogramování v C++ (část B)

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

Page 158: Pokro čilé p rogramování v C++ (část B)

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

Page 159: Pokro čilé p rogramování v C++ (část B)

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

Page 160: Pokro čilé p rogramování v C++ (část B)

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

Page 161: Pokro čilé p rogramování v C++ (část B)

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

Page 162: Pokro čilé p rogramování v C++ (část B)

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

Page 163: Pokro čilé p rogramování v C++ (část B)

Koenig lookupKoenig lookup

Page 164: Pokro čilé p rogramování v C++ (část B)

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

Page 165: Pokro čilé p rogramování v C++ (část B)

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

Page 166: Pokro čilé p rogramování v C++ (část B)

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

Page 167: Pokro čilé p rogramování v C++ (část B)

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

Page 168: Pokro čilé p rogramování v C++ (část B)

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)

Page 169: Pokro čilé p rogramování v C++ (část B)

----------------------------------------------------------------------


Recommended