1 / 172

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

Pokro čilé p rogramování v C++ (část B). David Bednárek www.ksi.mff.cuni.cz. Pokro čilý pohled na kontejnery. Standardní knihovna definuje Mnoho druhů kontejnerů basic_string, vector, deque, list array, forward _list map, multimap, set, multiset

errol
Download Presentation

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

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Pokročilé programování v C++(část B) David Bednárek www.ksi.mff.cuni.cz

  2. Pokročilý pohled na kontejnery

  3. Standardní knihovna definuje Mnoho druhů kontejnerů basic_string, vector, deque, list array, forward_list map, multimap, set, multiset unordered_map, unordered_multimap, unordered_set, unordered_multiset Odlišné přidávání/odebírání Shodný způsob procházení Každý kontejner definuje Typyiterator, 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); Kontejnery a iterátory C++11 C++11

  4. 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); Ukazatele do pole se chovají jako iterátory Celé pole se ale nechová jako kontejner Nemá k.begin(), k.end() Řešení: begin(k), end(k) std::vector< 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); } Kontejnery a iterátory C++11

  5. Ukazatele do pole se chovají jako iterátory Celé pole se ale nechová jako kontejner Nemá k.begin(), k.end() Řešení: begin(k), end(k) void clr( int & x) { x = 0; } template< 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átory

  6. 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()); } 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(); }); } Kontejnery a iterátory C++11

  7. 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(); }); } 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(); }); } Kontejnery a iterátory C++11 C++11 C++11

  8. Standardní knihovna definuje Algoritmy mohou pracovat s čímkoliv, co má potřebné operátory Různé algoritmy mají různé nároky Norma C++ definuje 5 kategorií iterátorů random_access bidirectional forward output input Kategorie určuje, které syntaktické konstrukce musí iterátor umožňovat všechny kategorie iterator(I) /* copy constructor */ ++I, I++ output *I = x, *I++ = x random_access, bidirectional, forward, input I1 = I2 I1 == I2, I1 != I2 I->m /* pokud existuje (*I).m */ input *I /* pouze pro čtení */ random_access, bidirectional, forward iterator() *I /* čtení i zápis */ random_access, bidirectional --I, I-- random_access I += n, I + n, n + I I -= n, I - n, I1 - I2 I[ n] I1 < I2, I1 > I2, I1 <= I2, I1 >= I2 Kategorie iterátorů

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

  10. Složitější algoritmy Potřebují znát typ, na který iterátor ukazuje, apod. Potřebují určit kategorii iterátoru Standard definuje šablonu iterator_traits parametrizovanou typem iterátoru Každý iterator ji musí definovat Obvykle nepřímo definováním těchto typů uvnitř třídy iterátoru Pokud to nejde, explicitní specializací šablony template<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; }; iterator_traits

  11. std::iterator • Pomůcka pro vytváření vlastních iterátorů • šablona std::iterator použitelná jako předek třídy template<class Category, class T, class Distance = ptrdiff_t, class Pointer = T*, class Reference = T&> struct iterator { typedef T value_type; typedef Distance difference_type; typedef Pointer pointer; typedef Reference reference; typedef Category iterator_category; };

  12. std::iterator • Příklad my_iterator b, e; std::distance( b, e) • distance potřebuje znát iterator_category a difference_type std::iterator_traits< 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

  13. Šablony tříd – závislé typy • Šablony tříd (včetně těl metod) se při deklaraci kontrolují pouze částečně • Překladač může kontrolovat jen to, co nezávisí na parametru • Některé překladače nedělají ani to • Překladač potřebuje odlišit jména typů od ostatních jmen • U jmen ve tvaru A::B to překladač někdy nedokáže • Programátor musí pomoci klíčovým slovem typename template< 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

  14. Šablony tříd - this • Pokud je předkem třídy závislé jméno • překladač pak neví, které identifikátory jsou zděděny • nedokáže realizovat přednost děděných před vnějšími • uživatel musí pomoci konstrukcí this-> nebo kvalifikovaným jménem • Pozor: Kvalifikované jméno vypíná virtuálnost volání funkce template< 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í using template< typename T> class X : public T { using T::m; void h() { return m(); } }

  15. my_iterator • Iterator 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; //... };

  16. my_const_iterator • Kompletní 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í const template< 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

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

  18. Rvalue reference • Rvalue reference • Nová typová konstrukce T && • Lvalue reference • Nový název pro starou typovou konstrukci T & • Doplnění rvalue referencí do standardních knihoven • Cíl: Zrychlit běh existujících zdrojových kódů • Existující zdrojové kódy musí zůstat použitelné bez úprav • Přidány nové funkce umožňující další zrychlení • Rvalue reference mění styl programování v C++ • Konstrukce dříve neefektivní se stávají použitelnými • Použití nových knihovních funkcí • Přímé použití rvalue referencí • Move semantika - umožňuje nové druhy ukazatelů

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

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

  21. Řešení – operator= • Princip řešení - operator= • Dvě různé situace a = b; • zde se hodnota b okopírovat musí a = c + d; • výsledek operátoru + se stane novou hodnotou a • kopie principiálně není zapotřebí C++11

  22. Řešení – operator= • Princip řešení - operator= • Dvě různé situace • Potřebujeme dvěimplementace operatoru = a = b; • zde se hodnota b okopírovat musí T & T::operator=( const T & x); • copy-assignment • objekt b bude pouze čten a = c + d; • výsledek operátoru + se stane novou hodnotou a T & T::operator=(T && x); • move-assignment • hodnota vrácená operátorem + bude přesunuta do a • zdrojový pomocný objekt bude přesunem modifikován C++11

  23. Řešení – inicializace • Totéž platí pro inicializaci • Dvě různé situace • Potřebujeme dvěimplementace konstruktoru T a = b; • zde se hodnota b okopírovat musí T::T( const T & x); • copy-constructor • objekt b bude pouze čten T a = c + d; • výsledek operátoru + se stane hodnotou a T::T(T && x); • move-constructor • hodnota vrácená operátorem + bude přesunuta do a • zdrojový pomocný objekt bude přesunem modifikován C++11

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

  25. copy/move • Speciální metody tříd • Copy constructor T( const T & x); • Move constructor T(T && x); • Copy assignment operator T & operator=( const T & x); • Move assignment operator T & operator=( T && x); C++11

  26. copy/move • Překladačem definované chování (default) • Copy constructor T( const T & x)= default; • aplikuje copy constructor na složky • Move constructor T(T && x) = default; • aplikuje move constructor na složky • Copy assignment operator T & operator=( const T & x) = default; • aplikuje copy assignment operator na složky • Move assignment operator T & operator=( T && x)= default; • aplikuje move assignment operator na složky • default umožňuje vynutit defaultní chování C++11

  27. copy/move • Podmínky automatického defaultu • Copy constructor/assignment operator • pokud není explicitně deklarován move constructor ani assignment operator • budoucí normy pravděpodobně zakážou automatický default i v případě přítomnosti druhé copy metody nebo destruktoru • Move constructor/assignment operator • pokud není deklarována žádná ze4 copy/move metod ani destruktor C++11

  28. copy/move • Nejběžnější kombinace • Neškodná třída • Nedeklaruje žádnou copy/move metodu ani destruktor • Neobsahuje složky vyžadující zvláštní péči (ukazatele) • Složky vyžadující zvláštní péči • Překladačem generované chování (default) nevyhovuje • Bez podpory move (před C++11) T( const T & x); T & operator=( const T & x); ~T(); • Plná podpora copy/move T( const T & x); T( T && x); T & operator=( const T & x); T & operator=( T && x); ~T(); C++11

  29. copy/move • Další kombinace • Nekopírovatelná třída • Např. dynamicky alokované živé objekty v simulacích T( const T & x)= delete; T & operator=( const T & x) = delete; • delete zakazuje generování překladačem • Destruktor může ale nemusí být nutný • Přesouvatelná nekopírovatelná třída • Např. unikátní vlastník jiného objektu (viz std::unique_ptr< 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

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

  31. Matrix - Řešení s podporou move class Matrix { public: Matrix(); Matrix( const Matrix & x); Matrix(Matrix && x); Matrix & operator=( const Matrix & x); Matrix & operator=( Matrix && x); ~Matrix(); /* ... */ private: double * data_; std::size_t vsize_, hsize_; }; • Default nevyhovuje • Copy metody musejí alokovat nová data_ • Destruktor musí dealokovat data_ • Move metody musejí vynulovat položku data_ ve zdroji • rvalue reference neznamená, že zdroj nebude destruován • Assignment operátory musejí nějak uklidit starý obsah C++11

  32. Matrix – copy metody a destruktor Matrix::Matrix( const Matrix & x) : data_( new double[ x.vsize_ * x.hsize_]), vsize_( x.vsize_), hsize_( x.hsize_) { std::copy( x.data_, x.data_ + vsize_ * hsize_, data_); } Matrix & Matrix::operator=( const Matrix & x) { Matrix t( x); t.swap_with( * this); return * this; } • Trik s eliminací proměnné t v operátoru = nelze použít • Hodnotou předávaný parametr by zastínil move assignment Matrix::~Matrix() { delete data_; } • Operátor delete je odolný proti nulovému ukazateli C++11

  33. Matrix – move metody Matrix::Matrix( Matrix && x) : data_( x.data_), vsize_( x.vsize_), hsize_( x.hsize_) { x.data_ = nullptr; } • x.data_ je nutné vynulovat – jinak je destruktor x dealokuje • nullptr je nový preferovaný způsob zápisu nulového ukazatele Matrix & Matrix::operator=( Matrix && x) { x.swap_with( * this); return * this; } • Stará hodnota * this se dostane do x • Destruktor x ji časem zneškodní C++11

  34. Matrix – move metody - varianta Matrix::Matrix( Matrix && x) : data_( x.data_), vsize_( x.vsize_), hsize_( x.hsize_) { x.data_ = nullptr; } • x.data_ je nutné vynulovat – jinak je destruktor x dealokuje • nullptr je nový preferovaný způsob zápisu nulového ukazatele Matrix & Matrix::operator=( Matrix && x) { Matrix t( std::move( x)); t.swap_with( * this); return * this; } • std::move vynutí move constructor pro inicializaci t • Stará hodnota * this se dostane do t • Destruktor t ji včas zneškodní C++11

  35. lvalue/rvalue Pravidla

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

  37. lvalue/rvalue C++11

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

  39. Matrix - Řešení s podporou move class Matrix { public: Matrix(); Matrix( const Matrix & x); Matrix(Matrix && x); Matrix & operator=( const Matrix & x); Matrix & operator=( Matrix && x); ~Matrix(); Matrix & operator+=( const Matrix & b) const; /* ... */ private: double * data_; std::size_t vsize_, hsize_; }; Matrix operator+( const Matrix & a, const Matrix & b); Matrix && operator+(Matrix && a, const Matrix & b); Matrix && operator+( const Matrix & a, Matrix && b); Matrix && operator+(Matrix && a, Matrix && b); C++11

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

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

  42. lvalue/rvalue Nové rozhraní STL

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

  44. lvalue/rvalue - STL • Copy/move insertion iterator insert( const_iterator p, const T & x); iterator insert( const_iterator p, T && x); • Druhá verze umožňuje efektivní přesun hodnoty • Týká se i push_back/push_front • Emplace template< 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

  45. Variadic templates

  46. Šablony s proměnlivým počtem parametrů • Hlavička šablony • s proměnlivým počtem typových argumentů template< 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

  47. Š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... • deklaraceparametrů funkce/metody/konstruktoru void f( TList... plist); double g( int a, double c, TList... b); • tím vzniká pojmenovaný parametr zastupující seznam hodnot • ke každému seznamu hodnot musí být seznam typů • několik dalších okrajových případů • počet prvků seznamu sizeof...(TList) C++11

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

  49. Šablony s proměnlivým počtem parametrů template< 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 je • seznam typů v parametrech šablony nebo deklaraci funkce X< std::pair< int, TList*>...> class E : public U< TList> ... void f( constTList & ... plist); • seznam výrazůvevolá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 konstruktoru • další okrajové případy C++11

  50. Generická N-tice template <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í typedeftuple< int, double, int> my_tuple; typedeftypename tuple_element< 1, my_tuple>::type alias_to_double; my_tuple t1( 1, 2.3, 4); double v = get< 1>( t1); C++11: <utility>

More Related