Prezentace se nahrává, počkejte prosím

Prezentace se nahrává, počkejte prosím

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

Podobné prezentace


Prezentace na téma: "Pokročilé programování v C++ (část B)"— Transkript prezentace:

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

2 Klíčové vlastnosti C++

3 Zpřístupníme téměř vše, co dokáže „hardware“
Koncepce jazyka C/C++ Zpřístupníme téměř vše, co dokáže „hardware“ Proto máme lokální/globální/dynamicky alokovaná data Ukazatelová aritmetika Nabízíme pouze to, co si programátor neudělá sám Proto nemáme referenční semantiku a garbage collector Nezdržujeme, pokud nemusíme Nevirtuální metody Minimální běhové kontroly Neinicializovaná data Nejsme mateřská školka Konstrukce jsou často nebezpečné

4 Real programmers can write assembly code in any language.
Koncepce jazyka C/C++ The committee shall make no rule that prevents C++ programmers from shooting themselves in the foot. First Amendment to the C++ Standard (urban legend) In C++ it's harder to shoot yourself in the foot, but when you do, you blow off your whole leg. Bjarne Stroustrup Within C++, there is a much smaller and cleaner language struggling to get out. Real programmers can write assembly code in any language. Larry Wall

5 Důsledky koncepce C/C++
Existuje mnoho způsobů, jak udělat totéž Špatně čitelné programy Existuje mnoho způsobů, jak udělat chybu Řešení: Dobrovolné omezení síly jazyka Některé možnosti se nevyužívají Chystají se pasti na úmyslná i neúmyslná vybočení Dodržování konvencí a vzorů Využití moudrosti věků Srozumitelnost rozhraní i kódu

6 Abstraktní pohled na typy

7 Číselné typy (char, int, double,...) jsou dobře zapouzdřené
Dobře zapouzdřený typ Dobře zapouzdřený typ Nemůže „onemocnět“ při nevhodném používání Zejména z hlediska inicializace, kopírování a úklidu Tím není vyloučena chyba jednotlivých operací dělení nulou, přístup mimo meze pole... Číselné typy (char, int, double,...) jsou dobře zapouzdřené Až na absenci inicializace Ukazatele v C/C++ nejsou dobře zapouzdřené Chybějící inicializací vznikne nedetekovatelný vadný obsah Chybějící úklid může způsobit nedosažitelnost alokované paměti Duplikovaná dealokace je katastrofální chyba Kopírování ukazatelů vede k neřešitelné odpovědnosti za úklid Ukazatele na lokální data mohou ztratit použitelnost ... a mnoho dalších potenciálních problémů

8 Dobře zapouzdřené typy
Číselné typy (char, int, double,...) jsou dobře zapouzdřené Ukazatele v C/C++ nejsou dobře zapouzdřené Reference v C++ nejsou dobře zapouzdřené Odpadá problém kopírování a dealokace Zůstává problém odkazu na zaniklá data Kontejnery jsou tak dobře zapouzdřené, jako jejich prvky

9 Dobře zapouzdřené typy
Třída obsahující pouze dobře zapouzdřené prvky Nemusí mít copy-constructor, operator= ani destruktor Sama bude dobře zapouzdřená Konstruktor může být nutný kvůli rozhraní a/nebo logice třídy Třída obsahující prvky, které nejsou dobře zapouzdřené Pokud má být dobře zapouzdřena, musí obsahovat: Konstruktor Copy-constructor operator= Destruktor Prvky musí být privátní

10 Třídy z pohledu chování
Hodnotové typy Nemají vlastní identitu (3+5 je totéž jako 2+6) Kopírování je bez problémů Jsou to programátorské proměnné, nikoliv matematické hodnoty Dědičnost nemá smysl „Živé“ objekty Mají vlastní identitu (dvě černé vrány nejsou jedna vrána) Mění stav v průběhu života, kopírování nemívá smysl Bývají polymorfní – dědičnost Rozhraní Vnější pohled na plnohodnotný objekt Odkazy Na rozhraní nebo plnohodnotné objekty Nemají vlastní identitu Mohou dovolovat kopírování – musí řešit odpovědnost za úklid Další (funktory, singletony,...)

11 Třídy z pohledu instanciace
Neinstanciované třídy Policy classes Traits templates Třídy s jednou instancí Singletony Třídy instanciované jako proměnné Hodnotové typy, chytré ukazatele, ... Jednorázové třídy - Funktory, visitory apod. Třídy reprezentující výjimky Třídy instanciované dynamicky Plain Old Data Velká a sdílená data Živé objekty - třídy s hierarchií dědičnosti Poznámka: Toto třídění nemůže být ani úplné ani jednoznačné

12 Kanonické tvary tříd

13 Kanonické tvary tříd Singletony

14 Kanonické tvary tříd Singletony
Veřejná statická funkce zpřístupňující instanci Lokální statická proměnná obsahuje jedinou instanci Privátní konstruktor a destruktor Znemožní vytváření a likvidaci mimo předepsaná místa Privátní hlavička copy-constructoru a operatoru= Tělo se neimplementuje Znemožní kopírování Většinou nevyužívají dědičnost ani virtuální funkce /* syslog.h */ class SysLog { public: static SysLog & instance() { static SysLog inst; return inst; } void write( const std::string & s) { os_ << s; } private: std::ofstream os_; SysLog() : os_("syslog") {} ~SysLog() {} SysLog( const SysLog &); void operator=( const SysLog &); }; /* pouziti */ Syslog::instance().write( "Hi");

15 Kanonické tvary tříd Hodnotové typy

16 Kanonické tvary tříd Hodnotové typy – jednoduché případy
Obsahují pouze dobře zapouzdřené prvky Číselné typy Jiné dobře zapouzdřené třídy Prvky mohou být veřejné Pokud nemusí dodržovat nějaký invariant class Complex { public: double re, im; Complex(); Complex( double r, double i = 0); Complex operator-() const; Complex & operator+=( const Complex &); }; Complex operator+( const Complex &, const Complex &);

17 Kanonické tvary tříd Hodnotové typy – jednoduché případy
Obsahují pouze dobře zapouzdřené prvky Číselné typy Jiné dobře zapouzdřené třídy Privátní prvky mívají zpřístupňující metody set/get konvence class Complex { public: Complex(); Complex( double r, double i = 0); double get_re() const; void set_re( double x); double get_im() const; void set_im( double x); Complex operator-() const; Complex & operator+=( const Complex &); private: double re_, im_; }; Complex operator+( const Complex &, const Complex &);

18 Kanonické tvary tříd Hodnotové typy – jednoduché případy
Obsahují pouze dobře zapouzdřené prvky Privátní prvky mívají zpřístupňující metody Konstruktory s parametry (někdy) Konverze, inicializace Mohou nahradit set-metody Konstruktor bez parametrů Pokud jsou konstruktory s parametry Pokud třída obsahuje atomické číselné typy class Complex { public: Complex(); Complex( double r, double i = 0); double get_re() const; void set_re( double x); double get_im() const; void set_im( double x); Complex operator-() const; Complex & operator+=( const Complex &); private: double re_, im_; }; Complex operator+( const Complex &, const Complex &);

19 Kanonické tvary tříd Hodnotové typy – jednoduché případy
Obsahují pouze dobře zapouzdřené prvky Konstruktory s parametry (někdy) Konstruktor bez parametrů Copy-constructor, operator=, destruktor nejsou zapotřebí Vyhovuje chování generované překladačem class Complex { public: Complex(); Complex( double r, double i = 0); double get_re() const; void set_re( double x); double get_im() const; void set_im( double x); Complex operator-() const; Complex & operator+=( const Complex &); private: double re_, im_; }; Complex operator+( const Complex &, const Complex &);

20 Kanonické tvary tříd Hodnotové typy
Unární a modifikující binární operátory jako metody unární operátory (včetně postfixového ++) vrací hodnotou modifikující operátory (vyjma postfixového ++) vrací odkazem * this Nemodifikující binární operátory jako globální funkce Vrací hodnotou Většinou lze implementovat pomocí modifikujících binárních operátorů class Complex { public: double re, im; Complex(); Complex operator-() const; Complex & operator+=( const Complex &); }; Complex operator+( const Complex & a, const Complex & b) { Complex tmp( a); tmp += b; return tmp; }

21 Kanonické tvary tříd Hodnotové typy Dědičnost nemá smysl
Virtuální funkce nemají smysl class Complex { public: double re, im; Complex(); Complex operator-() const; Complex & operator+=( const Complex &); }; Complex operator+( const Complex & a, const Complex & b) { Complex tmp( a); tmp += b; return tmp; }

22 Kanonické tvary tříd Hodnotové typy - složitější případy
Obsahují nedostatečně zapouzdřené prvky Ukazatele apod. Konstruktor bez parametrů Copy-constructor operator= Destruktor Data a pomocné funkce privátní class Matrix { public: Matrix(); Matrix( const Matrix &); Matrix & operator=( const Matrix &); ~Matrix(); Matrix operator-() const; Matrix & operator+=( const Matrix &); friend Matrix operator+( const Matrix &, const Matrix &); private: int m_, n_; double * d_; }; Matrix operator+(

23 Kanonické tvary tříd Hodnotové typy - složitější případy
Obsahují nedostatečně zapouzdřené prvky Ukazatele apod. Více než jeden dynamicky alokovaný blok Problémy s exception-safety Vyplatí se samostatně zapouzdřit ukazatele do pomocných tříd class Matrix { public: Matrix(); Matrix( const Matrix &); Matrix & operator=( const Matrix &); ~Matrix(); Matrix operator-() const; Matrix & operator+=( const Matrix &); friend Matrix operator+( const Matrix &, const Matrix &); private: int m_, n_; double * d_; }; Matrix operator+(

24 Kanonické tvary tříd Funktory, visitory

25 Kanonické tvary tříd Třídy instanciované jako proměnné Funktory
Třídy určené k předávání šablonovaným funkcím Obvykle používány pouze jednou Co nejjednodušší konstrukce Konstruktor S požadovanými parametry Data Metoda implementující požadovanou operaci Typicky operator() Dědičnost nemá smysl struct Printer { public: Printer( std::ostream & o) : out_( o) {} void operator()( int x) { o << x << std::endl; } private: std::ostream & out_; }; std::vector< int> v; for_each( v.begin(), v.end(), Printer( std::cout));

26 Kanonické tvary tříd Třídy instanciované jako proměnné
Abstraktní visitor Sada čistě virtuálních funkcí Virtuální destruktor Prázdné tělo Vše veřejné Konkrétní visitor Obvykle používán pouze jednou Co nejjednodušší konstrukce Kontrola přístupu není nutná Potomek abstraktní třídy Privátní nebo veřejná data Konstruktor (jsou-li data) Virtuální metody implementující požadované operace class Visitor { public: virtual void visitEllipse( Ellipse *)=0; virtual void visitRectangle( Rectangle *)=0; virtual void visitLine( Line *)=0; virtual ~Visitor() {} }; class PrintVisitor : public Visitor { PrintVisitor( Printer * p) : p_( p) {} virtual void visitEllipse( Ellipse *); virtual void visitRectangle( Rectangle *); virtual void visitLine( Line *); private: Printer * p_;

27 Dynamicky alokovaná data
Kanonické tvary tříd Dynamicky alokovaná data

28 Kanonické tvary tříd Třídy instanciované dynamicky Plain Old Data
Neobsahují dynamicky alokované součásti Datové položky obvykle veřejné Většinou bez konstruktoru Nedefinuje se copy-constructor, operator= ani destruktor Bez virtuálních funkcí a dědičnosti Někdy s obyčejnými metodami class Configuration { public: bool show_toolbar; bool show_status_bar; int max_windows; }; Dynamická alokace pouze kvůli požadované životnosti Pro srovnání Pojem "Plain Old Data" (POD) je definován jazykem C++ takto: Třída nemá žádný konstruktor ani destruktor Třída nemá virtuální funkce ani virtuální dědičnost Všichni předkové a datové položky jsou POD POD-třída má zjednodušená pravidla: Může být součástí unie Může být staticky inicializována

29 Kanonické tvary tříd Třídy instanciované dynamicky
Velká a sdílená data Často obsahuje definice typů Konstruktor nebo několik konstruktorů, často s parametry Destruktor Privátní datové položky Manipulace prostřednictvím metod udržujících konzistenci Obvykle privátní neimplementovaný copy-constructor a operator= Většinou bez virtuálních funkcí a dědičnosti Dynamická alokace kvůli velikosti a životnosti class ColoredGraph { public: typedef int node_id; typedef int edge_id; typedef int color; ColoredGraph(); ColoredGraph( istream &); ~ColoredGraph(); node_id add_node(); edge_id add_edge( node_id, node_id, color); /* ... */ private: vector< node_id> nodes_; multimap< node_id, edge_id> edges_; map< edge_id, color> colors_; ColoredGraph(const ColoredGraph &); void operator=(const ColoredGraph &); };

30 Kanonické tvary tříd Třídy instanciované dynamicky
Prvky datových struktur Obsahují ukazatele Konstruktor (inicializace ukazatelů) Další konstruktory, jsou-li využitelné Destruktor jen tehdy, pokud může mít dobře definované chování Datové položky někdy veřejné Nepolymorfní struktury: bez virtuálních funkcí a dědičnosti Konzistentní manipulace zajištěna zapouzdřením celé struktury do jiné třídy Dynamická alokace kvůli životnosti a složitosti struktury class TreeNode { public: TreeNode() : left( 0), right( 0) {} TreeNode( TreeNode * l, TreeNode * r) : left( l), right( r) {} ~TreeNode() { /* ??? */ } TreeNode * left, * right; }; class Tree { Tree() : root( 0) {} ~Tree() { /* ... */ } void insert_node( /* ??? */ ); private: TreeNode * root;

31 Kanonické tvary tříd Třídy instanciované dynamicky
Prvky datových struktur Datové položky někdy veřejné Konzistentní manipulace zajištěna zapouzdřením celé struktury do jiné třídy Problém: zpřístupnění prvků vnějšímu světu class Tree { public: Tree() : root( 0) {} ~Tree() { /* ... */ } void insert_node( /* ??? */ ); /* ??? */ find_node( /* ... */ ); private: class TreeNode { TreeNode() : left( 0), right( 0) {} ~TreeNode() { /* ??? */ } TreeNode * left, * right; }; TreeNode * root;

32 Kanonické tvary tříd Třídy instanciované dynamicky
Prvky datových struktur Datové položky někdy veřejné Konzistentní manipulace zajištěna zapouzdřením celé struktury do jiné třídy Problém: zpřístupnění prvků vnějšímu světu Lze řešit zapouzdřením ukazatelů do tříd – viz iterátory Shrnuto: NodeRef - technicky hodnotová třída s přiznanou referenční semantikou Tree – obvykle nekopírovatelná třída TreeNode - PlainOldData class Tree { private: class TreeNode { public: /* ... */ TreeNode * left, * right; }; class NodeRef { TreeNode * p; Tree() : root( 0) {} ~Tree() { /* ... */ } NodeRef create_node( /* ... */); void insert_node( NodeRef p); NodeRef find_node( /* ... */ ); TreeNode * root; Tree( const Tree &); /* ... */

33 Kanonické tvary tříd Třídy s dědičností

34 Kanonické tvary tříd Třídy instanciované dynamicky
Třídy s hierarchií dědičnosti "Objektové programování" Abstraktní třída Sada veřejných (čistě) virtuálních funkcí Veřejný virtuální destruktor Prázdné tělo Konstruktor obvykle protected Chrání proti instanciaci, pokud nejsou čistě virtuální funkce Pro jistotu: privátní neimplementovaný copy-constructor a operator= Kopírování metodou clone Žádné datové položky class AbstractObject { public: virtual void doit() {} virtual void showit( Where *) const = 0; virtual AbstractObject * clone() const = 0; virtual ~AbstractObject() {} protected: AbstractObject() {} private: AbstractObject( const AbstractObject&); void operator=( };

35 Kanonické tvary tříd Třídy instanciované dynamicky
Třídy s hierarchií dědičnosti "Objektové programování" Konkrétní třída Potomek abstraktní třídy Veřejný konstruktor Virtuální funkce obvykle protected Privátní data class ConcreteObject : public AbstractObject { public: ConcreteObject( /*...*/); protected: virtual void doit(); virtual void showit( Where *) const; virtual AbstractObject * clone() const; virtual ~ConcreteObject(); private: /* data */; };

36 Neinstanciované třídy
Kanonické tvary tříd Neinstanciované třídy

37 Kanonické tvary tříd Neinstanciované třídy Policy classes
Traits templates Obsahují pouze Definice typů (typedef, případně vnořené třídy) Definice výčtových konstant (a typů) Statické funkce Statická data Obvykle vše veřejné (struct) Nemají konstruktory ani destruktory Obvykle nevyužívají dědičnost struct allocation_policy { static void * alloc( size_t); static void free( void *); }; template< typename T> struct type_traits; template<> struct type_traits< char> { typedef char param_type; enum { min = 0, max = 255 }; static bool less( char, char);

38 Kanonické tvary tříd Policy class – použití Univerzální šablona
template< class policy> class BigTree { /* ... */ Node * new_node() { return policy::alloc(sizeof(Node)); } }; Specifická policy class struct my_policy { static void * alloc( size_t); static void free( void *); Použití BigTree< my_policy> my_tree; Traits template – použití Deklarace traits template< class T> struct type_traits; Univerzální šablona template< class T> class Stack { /* ... */ void push( typename type_traits::param_t x); }; Univerzální definice traits struct type_traits { typedef const T & param_t; Explicitní specializace traits template<> struct type_traits< char> { typedef char param_t;

39 Polymorfismus Kompilační a běhový

40 Stejný zdrojový kód v různých situací dělá různé věci
Polymorfismus Polymorfismus Stejný zdrojový kód v různých situací dělá různé věci Volání stejně pojmenované funkce (operátoru) volá různá těla Šetří práci programátora (zejména na údržbě) Kód není třeba kopírovat Nahrazuje konstrukce if/switch Kód je přehlednější a obsahuje méně chyb

41 Kompilační polymorfismus Šablony
Policy classes Traits Běhový polymorfismus C: ukazatele na funkce C++: dědičnost a virtuální funkce Běhový polymorfismus zdržuje Používat pouze v nutném případě Datové struktury obsahující objekty s různým chováním ("OOP") Komunikace s neznámými partnery (komponentové systémy) Vše ostatní lze řešit kompilačním polymorfismem

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

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

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

45 Traits a kontejnery template< typename K>
struct for_each_traits; void for_each( K & data) { typedef typename K::value_type T; for( ... it = ... ) for_each_traits< T>::f( it); } template< typename U> class vector { public: typedef U value_type; ... }; template<> struct for_each_traits< int> { static void f(...) { } typedef vector< int> my_k; my_k data; for_each( data);

46 Traits a iterátory template< typename K> struct for_each_traits;
template< typename IT> void for_each( IT b, IT e) { typedef typename iterator_traits< IT>::value_type T; for( ... it = ... ) for_each_traits< T>::f( it); } template<> struct for_each_traits< int> { static void f(...) { ... } }; typedef int my_k[ N]; my_k data; for_each( data, data + N); // IT = int*

47 Lambda (C++0x) template< typename P> void modify( const P & x) {
for( ... it = ... ) * it = x.operator()( * it); } struct functor_twice { int operator()( int x) const return 2 * x; }; modify( functor_twice()); for_each( []( int x){ return 2 * x });

48 Lambda (C++0x vs. boost) for_each( []( int x){ return 2 * x });
for_each( constant( 2) * _1);

49 Lambda (boost) struct functor_1 { template< typename T>
T operator()( T x) const { return x; } }; extern functor_1 _1; struct functor_const { functor_const( T c) : c_( c) {} return c_; T c_; functor_const< T> constant( T c) { return functor_const< T>( c); } template< typename F1, typename F2> struct functor_mul { functor_mul( F1 f1, F2 f2) : f1_( f1), f2_( f2) {} T operator()( T x) const { return c_; } F1 f1_; F2 f2_; }; functor_mul< F1, F2> operator+ ( F1 f1, F1 f2) return functor_const< F1, F2> ( f1, f2); for_each( constant( 2) * _1);

50 Lambda (boost) - problémy
for_each( std::cout << "x=" << _1); for_each( constant( 2) * _1);

51 Pokročilý pohled na kontejnery

52 Kontejnery a iterátory
Standardní knihovna definuje 8 druhů kontejnerů basic_string, vector, deque, list map, multimap, set, 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); int * p = new int[ N]; for_each( p, p + N, clr);

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

54 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 ! ... }

55 Další standardní iterátory
random_access iterátory k vector a deque bidirectional iterátory ostatních kontejnerů output back_inserter, front_inserter, inserter ostream_iterator input reverse_iterator šablona pro otočení smyslu bidirectional/random_access iterátoru kontejnery mají rbegin()/rend() pozor: k.rbegin() != reverse_iterator( k.end()) std::vector a; std::copy( x.begin(), x.end(), std::back_inserter( a)); std::ostream & o = ...; std::ostream_iterator( o)); std::istream & i = ...; std::copy( std::istream_iterator( i), std::istream_iterator(),

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

57 Šablony Templates

58 Š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á 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 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

59 Šablony tříd - instanciace
Instanciace šablony: Šablonu lze použít jako typ pouze s explicitním uvedením skutečných parametrů odpovídajících druhů: celé číslo: celočíselný konstantní výraz ukazatel: adresa globální nebo statické proměnné či funkce kompatibilního typu libovolný typ – jméno typu či typová konstrukce (včetně jiné instanciované šablony) Užití instanciované šablony: Instanciované šablony jsou stejného typu, pokud jsou stejného jména a jejich skutečné parametry obsahují stejné hodnoty konstantních výrazů, adresy stejných proměnných či funkcí a stejné typy

60 Šablony tříd – pravidla použití
Uvnitř těla šablony (nebo jako její předky) je možno užívat libovolné typy včetně: Instancí jiných šablon Téže šablony s jinými argumenty Téže šablony se stejnými argumenty V tomto případě se argumenty mohou, ale nemusí opisovat Ekvivalentní varianty šablony s copy-constructorem: template< typename T> class X { X( const X< T> &); }; X( const X &); Některé překladače připouštějí i tuto variantu X< T>( const X< T> &);

61 Šablony tříd – pravidla použití
Metody šablon mohou mít těla uvnitř třídy nebo vně Vně uvedená těla metod musejí být připojena k šabloně takto: template< typename T> void X< T>::f( int a, int b) { /* ... */ } V kvalifikovaném jméně metody je nutné uvést patřičný seznam argumentů, tj. X< T>::f a nikoliv X::f Těla metod musejí být viditelná z každého místa, kde jsou pro nějakou instanci šablony volána Musejí tedy typicky být v témže hlavičkovém souboru jako sama šablona. Uvedení těla metody vně třídy tedy u šablon typicky nic nepřináší, může být však vynuceno rekurzivními odkazy mezi šablonami apod.

62 Šablony tříd – pravidla použití
Šablona třídy se překládá až v okamžiku instanciace, tj. použití s konkrétními parametry Překladač instanciuje (tj. překládá) pouze ty metody, které jsou zapotřebí (tj. jsou volány nebo jsou virtuální) Některá těla metod tedy nemusí být pro některé případy parametrů přeložitelná template< typename Container> class Proxy { public: void pop_front() { c->pop_front(); } // jen pro list/deque /* ... */ private: Container * c; }; Explicitní instanciace Překladač je možné donutit ke kompletní instanciaci šablony template class Array< 128, char>;

63 Šablony tříd – závislé typy
Šablony tříd (včetně těl metod) se při deklaraci kontrolují pouze syntakticky 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

64 Pokud je mezi předkem třídy závislé jméno
Šablony tříd - this Pokud je mezi předkem třídy závislé jméno překladač pak neví, které identifikátory jsou zděděny uživatel musí pomoci konstrukcí this-> template< typename T> class X : public T { void f() { return this->a; } }

65 Dopředná deklarace šablony
Šablony tříd – triky Dopředná deklarace šablony template< typename T> class X; /* ... zde může být použito X<U> s jakýmikoliv argumenty U... ... pouze v kontextech, kde kompilátor nepotřebuje znát tělo šablony ... */ template< typename T> class X { /* ... */ };

66 Šablony funkcí lze volat dvěma způsoby
Šablona funkce je generická funkce (globální nebo metoda) prefixovaná konstrukcí template se stejnými druhy formálních parametrů šablony jako u šablon tříd template< typename T, int k> // parametry šablony int f( T * p, int q); // parametry funkce template< 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

67 Šablony funkcí Pod stejným identifikátorem může být deklarováno několik různých šablon funkce a navíc několik obyčejných funkcí. Obyčejné funkce mají přednost před generickými template< class T> T max( T a, T b) { return a < b ? b : a; }; char * max( char * a, char * b) { return strcmp( a, b) < 0 ? b : a; }; template< int n, class T> T max( Array< n, T> a) { /* ... */ } Příklad ze standardních knihoven: template< class T> void swap( T & a, T & b) { T tmp(a); a = b; b = tmp; }; K tomu řada chytřejších implementací swap pro některé třídy

68 Šablony – pokročilé konstrukce jazyka
Parciální specializace Deklarovanou šablonu lze pro určité kombinace parametrů předefinovat jinak, než určuje její základní definice template< int n> class Array< n, bool> { /* specializace pro pole typu bool */ }; Krajním případem parciální specializace je explicitní specializace Explicitní specializace template<> class Array< 32, bool> { /* ... */ }; U šablon funkcí nahrazena obyčejnou funkcí Explicitní instanciace Překladač je možné donutit ke kompletní instanciaci šablony template class Array< 128, char>;

69 Parciální specializace
Deklarovanou šablonu lze pro určité kombinace parametrů předefinovat jinak, než určuje její základní definice Parciálně specializovat lze šablony funkcí, celé šablony tříd i jednotlivě těla jejich metod Obsah specializace šablony třídy (teoreticky) nemusí nijak souviset se základní definicí - může mít zcela jiné položky, předky apod. Základní definice dokonce nemusí vůbec existovat (ale musí být deklarována) template< class X, class Y> class C; // základní deklarace template< class P, class Q> class C< P *, Q *> { // specializace bool cmp( P *, Q *); }; template< class Z> class C< Z, Z> : public Z { // jiná specializace bool set( Z &); template< class X, class Y> class C { // základní definice X add( Y);

70 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 */ };

71 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 */ }; Krajním případem parciální specializace je explicitní specializace 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 nepíší do hlavičkových souborů

72 Parciální specializace
Typická použití parciální a explicitní specializace Výhodnější implementace ve speciálních případech Šablona je používána přímo z nešablonovaného kódu Programátor - uživatel šablony o specializaci nemusí vědět Příklad: Implementace vector<char> může být jednodušší

73 Parciální specializace
Typická použití parciální a explicitní specializace Výhodnější implementace ve speciálních případech Šablona je používána přímo z nešablonovaného kódu 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

74 Parciální specializace
Typická použití parciální a explicitní specializace Výhodnější implementace ve speciálních případech Šablona je používána přímo z nešablonovaného kódu 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 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

75 Parciální specializace
Modifikace chování jiné šablony Specializovaná šablona je volána z jiného šablonovaného kódu Autor volající šablony předpokládá, že ke specializaci dojde Někdy dokonce ani nedefinuje základní obsah volané šablony Autor specializace tak upravuje chování volající šablony 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 & 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); }

76 Parciální specializace
Modifikace chování jiné šablony Specializovaná šablona je volána z jiného šablonovaného kódu Autor volající šablony předpokládá, že ke specializaci dojde Někdy dokonce ani nedefinuje základní obsah volané šablony Autor specializace tak upravuje chování volající šablony Traits Šablony, ze kterých nejsou vytvářeny objekty Obsahují pouze: Definice typů 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

77 Šablony, ze kterých nejsou vytvářeny objekty Obsahují pouze:
Traits & policies Traits Šablony, ze kterých nejsou vytvářeny objekty Obsahují pouze: Definice typů Statické funkce Určeny k doplnění informací o nějakém typu Příklad: char_traits<T> doplňuje informace o typu T, např. porovnávací funkci Policy classes Třídy, ze kterých obvykle nejsou vytvářeny objekty Předávány jako parametr šablonám Defaultní hodnotou parametru často bývá šablona traits Určeny k definování určitého chování Příklad: Alokační strategie

78 Porovnání typů s booleovským výstupem
Triky s šablonami Porovnání typů s booleovským výstupem template< class A, class B> struct Equal { enum { value = false }; }; template< class A> struct Equal< A, A> { enum { value = true }; Equal< X, Y>::value je konstantní výraz Použití template< class T1> class Test { enum { T1_is_int = Equal< int, T1>::value}; enum { T1_is_long = Equal< long, T1>::value}; /* ... */

79 Porovnání typů s typovým výstupem
Triky s šablonami Porovnání typů s typovým výstupem template< class A, class B, class C, class D> struct IfEqual { typedef D Result; }; template< class A, class C, class D> struct Equal< A, A, C, D> { typedef C Result; IfEqual< X, Y, U, V>::Result je typ Význam: X == Y ? U : V Použití template< class T1> class Test { typedef Equal< T1, unsigned, unsigned long, long>::Result longT1; /* ... */

80 Kompilační ověření invariantu
Triky s šablonami Kompilační ověření invariantu template< int x> struct AssertNot; template<> struct AssertNot< 0> { enum { value = true }; }; struct Assert { enum { value = AssertNot< ! x>::value };

81 Kompilační ověření invariantu
Triky s šablonami Kompilační ověření invariantu template< int x> struct AssertNot; template<> struct AssertNot< 0> { enum { value = true }; }; struct Assert { enum { value = AssertNot< ! x>::value }; Použití template< int N> class Array { enum { check = Assert< (N > 0)>::value }; /* ... */ Array< -3> x; error C2027: use of undefined type 'AssertNot<x>' with [x=1]

82 Teoretický pohled na šablony
Příklad template< int N> struct Fact { enum { value = Fact< N-1>::value * N }; }; template<> struct Fact< 0> { enum { value = 1 };

83 Teoretický pohled na šablony
Příklad template< int N> struct Fact { enum { value = Fact< N-1>::value * N }; }; template<> struct Fact< 0> { enum { value = 1 }; Použití enum { N = 10 }; int permutations[ Fact< N>::value];

84 Teoretický pohled na šablony
Příklad template< int N> struct Fact { enum { value = Fact< N-1>::value * N }; }; template<> struct Fact< 0> { enum { value = 1 }; Kontrolní otázka: Kolik je Fact< -1>::value

85 Teoretický pohled na šablony
Příklad template< int N> struct Fact { enum { value = Fact< N-1>::value * N }; }; template<> struct Fact< 0> { enum { value = 1 }; Kontrolní otázka: Kolik je Fact< -1>::value MS Visual C++ 7.1: fatal error C1202: recursive type or function dependency context too complex Řetěz instanciací Fact< -1>, Fact< -2>, Fact< -3>, ... způsobí přetečení tabulek kompilátoru

86 Teoretický pohled na šablony
Jiný příklad template< int N> struct Fib { enum { value = Fib< N-1>::value + Fib< N-2>::value }; }; template<> struct Fib< 0> { enum { value = 1 }; template<> struct Fib< 1> { Kontrolní otázka: Jak dlouho trvá výpočet (tj. kompilace) Fib< 1000>::value

87 Teoretický pohled na šablony
Jiný příklad template< int N> struct Fib { enum { value = Fib< N-1>::value + Fib< N-2>::value }; }; template<> struct Fib< 0> { enum { value = 1 }; template<> struct Fib< 1> { Kontrolní otázka: Jak dlouho trvá výpočet (tj. kompilace) Fib< 1000>::value MS Visual C++ 7.1: Build Time 0:00 Kompilátory ukládají již vytvořené instanciace a nepočítají je znovu

88 Šablony tříd – explicitní instanciace
Je-li předem známa množina typů (formálních parametrů), pro něž se bude šablona instanciovat, není nutno publikovat těla metod ve zdrojové formě a je možné je předkompilovat do knihovny Veřejný hlavičkový soubor X.h – hlavička třídy template< class T> class X { /* ... */ void f(/*...*/); }; Nepublikovaný hlavičkový soubor XBody.h Generická těla metod #include "X.h" template< class T> void X< T>::f(/*...*/) { /*...*/ } Knihovní modul XBodyInt.cpp Instanciace pro typ int #include "XBody.h" template X< int>;

89 Teoretický pohled na šablony
Šablona třídy je kompilátorem vyhodnocovaná funkce f : Ti  T T je množina všech typů zkonstruovatelných v jazyce C

90 Teoretický pohled na šablony
Šablona třídy je kompilátorem vyhodnocovaná funkce f : Ti  T T je množina všech typů zkonstruovatelných v jazyce C f : Ti × Kj  T šablona s celočíselnými parametry K je množina všech celočíselných konstant

91 Teoretický pohled na šablony
Šablona třídy je kompilátorem vyhodnocovaná funkce f : Ti  T T je množina všech typů zkonstruovatelných v jazyce C f : Ti × Kj  T šablona s celočíselnými parametry K je množina všech celočíselných konstant f : Ti × Kj  Tm × Kn výsledná třída může obsahovat typy a konstanty

92 Teoretický pohled na šablony
Šablona třídy je kompilátorem vyhodnocovaná funkce f : Ti × Kj  Tm × Kn Taková funkce může být definována Jedním předpisem Základní šablonou Po částech Parciálními specializacemi šablony V jednotlivých bodech Explicitními specializacemi šablony

93 Koenig lookup

94 iostream Problém: namespace namespace prostor {
class Souradnice { public: int x, y; }; std::ostream & operator<<( std::ostream & s, const Souradnice & a) { return s << '[' << a.x << ',' << a.y << ']'; } }; prostor::Souradnice p; std::cout << p; // správný operator<< je v namespace prostor, // který není přímo vidět

95 iostream Problém: namespace namespace prostor {
class Souradnice { public: int x, y; }; std::ostream & operator<<( std::ostream & s, const Souradnice & a) { return s << '[' << a.x << ',' << a.y << ']'; } }; prostor::Souradnice p; std::cout << p; // správný operator<< je v namespace prostor, // který není přímo vidět std::cout << std::endl; // tentýž problém je ale už tady: // tento operator<< je v namespace std

96 Oba případy jsou překládány správně
Koenig lookup prostor::Souradnice p; std::cout << p; // správný operator<< je v namespace prostor, // který není přímo vidět std::cout << std::endl; // tentýž problém je ale už tady: // tento operator<< je v namespace std Oba případy jsou překládány správně Je k tomu nutná složitá definice vyhledávání identifikátoru tzv. Koenigovo vyhledávání používá se, je-li význam identifikátoru závislý na parametrech volání funkce použití operátoru

97 Koenigovo vyhledávání (zjednodušeno)
Koenig lookup Koenigovo vyhledávání (zjednodušeno) Argument-dependent name lookup (ISO C++) Pro každý skutečný parametr se z jeho typu T určí množina asociovaných namespace Je-li T číselný, tyto množiny jsou prázdné Je-li T union nebo enum, jeho asociovaným namespace je ten, ve kterém je definován Je-li T ukazatel na U nebo pole U, přejímá asociované namespace od typu U Je-li T funkce nebo ukazatel na funkci, přejímá (sjednocením) asociované namespace všech parametrů a návratového typu Je-li T třída, asociovanými namespace jsou ty, v nichž jsou definovány tato třída a všichni její přímí i nepřímí předkové Je-li T instancí šablony, přejímá kromě asociovaných tříd a namespace definovaných pro třídu také asociované třídy a namespace všech typových argumentů šablony

98 Koenigovo vyhledávání (zjednodušeno)
Koenig lookup Koenigovo vyhledávání (zjednodušeno) Argument-dependent name lookup (ISO C++) Pro každý skutečný parametr se z jeho typu T určí množina asociovaných namespace Identifikátor funkce se pak vyhledává v těchto prostorech Globální prostor a aktuální namespace Všechny namespace přidané direktivami using Sjednocení asociovaných namespace všech parametrů funkce Všechny varianty funkce nalezené v těchto namespace jsou rovnocenné Mezi nimi se vybírá podle počtu a typu parametrů Pokud není jednoznačně určena nejlepší varianta, je to chyba Volání v kontextu třídy: Je-li identifikátor nalezen uvnitř této třídy nebo některého předka (jako metoda), má přednost před výše uvedenými variantami (globálními funkcemi)

99 Exception handling Mechanismus výjimek

100 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

101 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

102 Exception-safe programming
Pravidla vynucená jazykem Destruktor nesmí skončit vyvoláním výjimky Výjimka může být vyvolána uvnitř, ale musí být zachycena nejpozději uvnitř destruktoru Zdůvodnění: V rámci ošetření výjimek (ve fázi stack-unwinding) se volají destruktory lokálních proměnných Výjimku zde vyvolanou nelze z technických i logických důvodů ošetřit (ztratila by se původní výjimka) Nastane-li taková výjimka, volá se funkce terminate() a program končí

103 Exception-safe programming
Pravidla vynucená jazykem Destruktor nesmí skončit vyvoláním výjimky Výjimka může být vyvolána uvnitř, ale musí být zachycena nejpozději uvnitř destruktoru Toto pravidlo jazyka sice platí pouze pro destruktory lokálních proměnných A z jiných důvodů též pro globální proměnné Je však vhodné je dodržovat vždy Bezpečnostní zdůvodnění: Destruktory lokálních proměnných často volají jiné destruktory Logické zdůvodnění: Nesmrtelné objekty nechceme

104 Exception-safe programming
Pravidla vynucená jazykem Destruktor nesmí skončit vyvoláním výjimky Konstruktor globálního objektu nesmí skončit vyvoláním výjimky Zdůvodnění: Není místo, kde ji zachytit Stane-li se to, volá se terminate() a program končí Jiné konstruktory ale výjimky volat mohou (a bývá to vhodné)

105 Exception-safe programming
Pravidla vynucená jazykem Destruktor nesmí skončit vyvoláním výjimky Konstruktor globálního objektu nesmí skončit vyvoláním výjimky Copy-constructor typu v hlavičce catch-bloku nesmí skončit vyvoláním výjimky Zdůvodnění: Catch blok by nebylo možné vyvolat Stane-li se to, volá se terminate() a program končí Jiné copy-constructory ale výjimky volat mohou (a bývá to vhodné)

106 Exception-safe programming
Pravidla vynucená jazykem Destruktor nesmí skončit vyvoláním výjimky Konstruktor globálního objektu nesmí skončit vyvoláním výjimky Copy-constructor typu v hlavičce catch-bloku nesmí skončit vyvoláním výjimky

107 Exception-safe programming
Poznámka: Výjimky při zpracování výjimky Výjimka při výpočtu výrazu v throw příkaze Tento throw příkaz nebude vyvolán Výjimka v destruktoru při stack-unwinding Povolena, pokud neopustí destruktor Po zachycení a normálním ukončení destruktoru se pokračuje v původní výjimce Výjimka uvnitř catch-bloku Pokud je zachycena uvnitř, ošetření původní výjimky může dále pokračovat (přikazem throw bez výrazu) Pokud není zachycena, namísto původní výjimky se pokračuje ošetřováním nové

108 Exception-safe programming
Kompilátory samy ošetřují některé výjimky Dynamická alokace polí Dojde-li k výjimce v konstruktoru některého prvku, úspěšně zkonstruované prvky budou destruovány Ve zpracování výjimky se poté pokračuje

109 Exception-safe programming
Kompilátory samy ošetřují některé výjimky Dynamická alokace polí Dojde-li k výjimce v konstruktoru některého prvku, úspěšně zkonstruované prvky budou destruovány Ve zpracování výjimky se poté pokračuje Výjimka v konstruktoru součásti (prvku nebo předka) třídy Sousední, již zkonstruované součásti, budou destruovány 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;

110 Exception-safe programming
Bezpečné programování s výjimkami

111 Exception-safe programming
Definice (Weak) exception safety Funkce (operátor, konstruktor) je (slabě) bezpečná, pokud i v případě výjimky zanechá veškerá data v konzistentním stavu Konzistentní stav znamená zejména: Nedostupná data byla korektně destruována a odalokována Ukazatele nemíří na odalokovaná data Platí další invarianty dané logikou aplikace

112 Exception-safe programming
Definice (Weak) exception safety Funkce (operátor, konstruktor) je (slabě) bezpečná, pokud i v případě výjimky zanechá veškerá data v konzistentním stavu Konzistentní stav znamená zejména: Nedostupná data byla korektně destruována a odalokována Ukazatele nemíří na odalokovaná data Platí další invarianty dané logikou aplikace Strong exception safety Funkce je silně bezpečná, pokud v případě, že skončí vyvoláním výjimky, zanechá data ve stejném (pozorovatelném) stavu, ve kterém byla při jejím vyvolání Observable state - chování veřejných metod Nazýváno též "Commit-or-rollback semantics"

113 Exception-safe programming
Poznámky (Weak) exception safety Tohoto stupně bezpečnosti lze většinou dosáhnout Stačí vhodně definovat nějaký konzistentní stav, kterého lze vždy dosáhnout, a ošetřit pomocí něj všechny výjimky Konzistentním stavem může být třeba nulovost všech položek Je nutné upravit všechny funkce tak, aby je tento konzistentní stav nepřekvapil (mohou na něj ale reagovat výjimkou) Strong exception safety Silné bezpečnosti nemusí jít vůbec dosáhnout, pokud je rozhraní funkce navrženo špatně Obvykle jsou problémy s funkcemi s dvojím efektem Příklad: funkce pop vracející odebranou hodnotu

114 Exception-safe programming
Příklad: String č. 2 operator= Nebezpečná implementace: Pokud new char způsobí výjimku, operátor= zanechá v položce str_ původní ukazatel, který již míří na dealokovaný blok class String { public: // ...  private: char * str_; }; String & String::operator=( const String & b) { if ( this != & b ) delete[] str_; str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } return * this;

115 Exception-safe programming
Příklad: String č. 2 operator= Nebezpečná implementace: Pokud new char způsobí výjimku, operátor= zanechá v položce str_ původní ukazatel, který již míří na dealokovaný blok K jiné výjimce zde dojít nemůže: std::operator delete výjimky nikdy nevyvolává char je vestavěný typ a nemá tedy konstruktory které by mohly výjimku vyvolávat strlen a strcpy jsou C-funkce Parametry a návratová hodnota se předávají odkazem class String { public: // ...  private: char * str_; }; String & String::operator=( const String & b) { if ( this != & b ) delete[] str_; str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } return * this;

116 Exception-safe programming
Příklad: String č. 2 operator= Naivní pokus o opravu: Pokud new char způsobí výjimku, ošetří se Objekt se uvede do konzistentního stavu Výjimka se propaguje dál - ven z funkce Problém: V catch bloku teoreticky může vzniknout nová výjimka String & String::operator=( const String & b) { if ( this != & b ) delete[] str_; try { str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } catch ( ... ) str_ = new char[ 1]; * str_ = 0; throw; return * this;

117 Exception-safe programming
Příklad: String č. 2 operator= Lepší pokus o opravu: Pokud new char způsobí výjimku, ošetří se Je nutné pozměnit invariant třídy String: Položka str_ nyní smí obsahovat nulový ukazatel String & String::operator=( const String & b) { if ( this != & b ) delete[] str_; try { str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } catch ( ... ) str_ = 0; throw; return * this;

118 Exception-safe programming
Příklad: String č. 2 operator= Lepší pokus o opravu: Pokud new char způsobí výjimku, ošetří se Je nutné pozměnit invariant třídy String: Položka str_ nyní smí obsahovat nulový ukazatel Takový exemplář String je považován za konzistentní Konzistentnost nemusí znamenat, že to je z uživatelského pohledu platná hodnota Může být považována i za chybovou a každá operace s takovou hodnotou může vyvolávat výjimku String & String::operator=( const String & b) { if ( this != & b ) delete[] str_; try { str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } catch ( ... ) str_ = 0; throw; return * this;

119 Exception-safe programming
Příklad: String č. 2 operator= Ekvivalentní řešení: Nulovat str_ po delete Pokud new způsobí výjimku, v str_ zůstane nulový ukazatel String & String::operator=( const String & b) { if ( this != & b ) delete[] str_; str_ = 0; str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } return * this;

120 Exception-safe programming
Příklad: String č. 2 operator= Chyba: změnili jsme invariant str_ nyní může být nulové delete _str je v pořádku operator delete je vždy proti nulovému ukazateli ošetřen (nedělá nic) strlen a strcpy ale fungovat nebudou String & String::operator=( const String & b) { if ( this != & b ) delete[] str_; str_ = 0; str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } return * this;

121 Exception-safe programming
Příklad: String č. 2 operator= Opraveno String & String::operator=( const String & b) { if ( this != & b ) delete[] str_; str_ = 0; if ( b.str_ ) str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } return * this;

122 Exception-safe programming
Příklad: String č. 2 operator= Vylepšení: operator= může vyvolávat výjimku, pokud se přiřazuje neplatná hodnota Tato výjimka může být definována např. takto: #include <exception> class InvalidString : public std::exception { virtual const char * what() const { return "Invalid string"; } String & String::operator=( const String & b) { if ( this != & b ) delete[] str_; str_ = 0; if ( b.str_ ) str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } else throw InvalidString(); return * this;

123 Exception-safe programming
Příklad: String č. 2 operator= Toto řešení je slabě bezpečné Silně bezpečné ale není: Pokud dojde k výjimce, nezachovává se původní stav dat To bude pro uživatele nepříjemné: String x, y; /* ... */ try { x = y + x; } catch (...) { /* ... */ Uživatel nedokáže rozlišit mezi výjimkami v operátorech + a = Náš operator= ale v případě výjimky ztratí hodnotu x String & String::operator=( const String & b) { if ( this != & b ) delete[] str_; str_ = 0; if ( b.str_ ) str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } else throw InvalidString(); return * this;

124 Exception-safe programming
Příklad: String č. 2 operator= Silně bezpečné řešení Pokud dojde k výjimce v new, nestane se nic Ani před throw nenastane žádná změna String & String::operator=( const String & b) { if ( this != & b ) if ( b.str_ ) char * aux = new char[ strlen( b.str_) + 1]; strcpy( aux, b.str_); delete[] str_; str_ = aux; } else throw InvalidString(); return * this;

125 Exception-safe programming
Příklad: String č. 2 operator= Silně bezpečné řešení Pozorování: Toto řešení je "shodou okolností" imunní proti this == & b String & String::operator=( const String & b) { if ( this != & b ) if ( b.str_ ) char * aux = new char[ strlen( b.str_) + 1]; strcpy( aux, b.str_); delete[] str_; str_ = aux; } else throw InvalidString(); return * this;

126 Exception-safe programming
Příklad: String č. 2 operator= Silně bezpečné řešení Pozorování: Toto řešení je "shodou okolností" imunní proti this == & b Test je možno zrušit String & String::operator=( const String & b) { if ( b.str_ ) char * aux = new char[ strlen( b.str_) + 1]; strcpy( aux, b.str_); delete[] str_; str_ = aux; } else throw InvalidString(); return * this;

127 Exception-safe programming
Příklad: String č. 2 operator= Silně bezpečné řešení Pokud je copy-constructor silně bezpečný Standardní řešení: Copy-constructor naplní lokální proměnnou c kopií parametru b Zde může dojít k výjimce Metoda swap vyměňuje obsah this a proměnné c Metoda swap je rychlá a nevyvolává výjimky Před návratem z operatoru se volá destruktor c Tím zaniká původní obsah this void String::swap( String & x) { char * aux = str_; str_ = x.str_; x.str_ = aux; } String & String::operator=( const String & b) String c( b); swap( c); return * this;

128 Exception-safe programming
Příklad: String č. 2 operator= Silně bezpečné řešení Metodu swap je vhodné publikovat ve formě globální funkce Některé algoritmy nad kontejnery obsahujícími String se tak zrychlí a stanou se bezpečnými vůči výjimkám void String::swap( String & x) { char * aux = str_; str_ = x.str_; x.str_ = aux; } String & String::operator=( const String & b) String c( b); swap( c); return * this; void swap( String & x, String & y) x.swap( y);

129 Exception-safe programming
Příklad: String č. 2 operator= Silně bezpečné řešení Metodu swap je vhodné publikovat ve formě globální funkce Některé algoritmy nad kontejnery obsahujícími String se tak zrychlí a stanou se bezpečnými vůči výjimkám Sama metoda swap může využívat šablonu swap pro typ char * #include <algorithm> void String::swap( String & x) { swap( str_, x.str_); } String & String::operator=( const String & b) String c( b); swap( c); return * this; void swap( String & x, String & y) x.swap( y);

130 Exception-safe programming
Příklad: String č. 2 copy-constructor Silně bezpečné řešení Pokud tělo dorazí na konec, budou datové položky korektně vyplněny Tělo může vyvolávat výjimky V takovém případě není třeba datové položky vyplňovat Objekt nebude považován za platný a nebude používán ani destruován Obecně je však třeba ošetřit try-blokem situace, kdy je v objektu více dynamicky alokovaných ukazatelů String( const String & b) { if ( b.str_ ) str_ = new char[ strlen( b.str_) + 1]; strcpy( str_, b.str_); } else throw InvalidString();

131 Exception-safe programming
Příklad: StringStack::pop Zásobník prvků typu String Implementován seznamem Slabě bezpečná implementace: Při výjimce v konstruktoru proměnné s se nestane nic operator delete nezpůsobuje výjimky struct Box { String v; Box * next; }; class StringStack { public: // ...  private: Box * top_; }; String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); Box * p = top_; String s = p->v; top_ = p->next; delete p; return s; }

132 Exception-safe programming
Příklad: StringStack::pop Zásobník prvků typu String Implementován seznamem Slabě bezpečná implementace Není silně bezpečná: Funkce vrací hodnotou Pokud při vracení dojde k výjimce v copy-constructoru, zásobník již bude zkrácen struct Box { String v; Box * next; }; class StringStack { public: // ...  private: Box * top_; }; String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); Box * p = top_; String s = p->v; top_ = p->next; delete p; return s; }

133 Exception-safe programming
Příklad: StringStack::pop Zásobník prvků typu String Implementován seznamem Slabě bezpečná implementace Není silně bezpečná: Funkce vrací hodnotou Pokud při vracení dojde k výjimce v copy-constructoru, zásobník již bude zkrácen Tuto výjimku lze ošetřit try-blokem okolo příkazu return Uvést zásobník do původního stavu Ale: co když se uvedení do původního stavu nezdaří? String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); Box * p = top_; String s = p->v; top_ = p->next; delete p; try { return s; } catch ( ...) p = new Box; p->v = s; p->next = top_; top_ = p; throw;

134 Exception-safe programming
Příklad: StringStack::pop Zásobník prvků typu String Implementován seznamem Nefunkč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 Tuto výjimku lze ošetřit try-blokem okolo příkazu return Dokážeme udělat obnovení původního stavu bez nebezpečí výjimky Ale: jak zrušíme proměnnou p, když k výjimce nedojde? String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); Box * p = top_; String s = p->v; top_ = p->next; // tady bylo delete p; try { return s; // tady by delete p; nepomohlo } catch ( ...) top_ = p; throw;

135 Exception-safe programming
Příklad: StringStack::pop Zásobník prvků typu String Implementován seznamem Silně bezpečná implementace Jak zrušíme proměnnou p, když k výjimce nedojde? std::auto_ptr< T> "chytrý" ukazatel na T, který se chová jako "jediný vlastník objektu": po zkopírování se vynuluje při zániku volá delete Pozor: auto_ptr má nestandardní copy-constructor a operator= modifikují svůj parametr pro auto_ptr nefungují kontejnery apod. #include <memory> String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); std::auto_ptr< Box> p = top_; top_ = p->next; try { return p->v; } catch ( ...) top_ = p; // toto přiřazení nuluje p throw; // při návratu se automaticky zruší * p // pokud je p nenulové

136 Exception-safe programming
Příklad: StringStack::pop Zásobník prvků typu String Implementován seznamem Silně bezpečná implementace Uživatel ji nedokáže použít tak, aby to bylo silně bezpečné Vracenou hodnotu je nutné okopírovat Nedá se poznat, zda výjimku vyvolala metoda pop nebo operator= V prvním případě je zásobník nedotčen, ale ve druhém je již zkrácen StringStack stk; String a; /* ... */ try { a = stk.pop(); } catch (...) { /* ??? */

137 Exception-safe programming
Poučení Funkce, která má vedlejší efekty, smí vracet hodnotou pouze typy, jejichž kopírování nevyvolává výjimky a = stk.pop(); Pokud je třeba nějakou "nebezpečnou" hodnotu vracet, musí se předávat jako výstupní parametr stk.pop( a); Funkcí bez vedlejších efektů se problém netýká a = b + c; StringStack stk; String a; /* ... */ try { a = stk.pop(); } catch (...) { /* ??? */ Většiny operátorů se problém netýká: buď nemají vedlejší efekty, nebo vracejí trvale existující objekt odkazem Problematické jsou postfixové ++, --

138 Exception-safe programming
Použití Nebezpečí nastává všude, kde je změna pozorovatelného stavu následovaná voláním funkce (operátoru), které může vyvolat výjimku data_ = ...; f(...); Problém lze někdy odstranit obrácením pořadí Pomocná proměnná není součástí pozorovatelného stavu T tmp = ...; Pokud pořadí nelze vyměnit, je v případě výjimky nutný rollback T old = data_; data_ = ...; try { f(...); } catch(...) { data_ = old; Problém 1: Některé operace rollbackovat nelze Problém 2: Rollback nemusí být spolehlivý

139 Exception-safe programming
Typické použití Nebezpečný kód change_1(...); change_2(...); change_3(...); change_4(...); change_1(...); try { change_2(...); change_3(...); change_4(...); } catch(...) { rollback_change_3(...); rollback_change_2(...); rollback_change_1(...);

140 Exception-safe programming
Použití Operace, která rollbackovat nejde, musí být zařazena jako poslední Pokud to není možné, je třeba rollback simulovat dodatečnými daty f1_.get( tmp); } try { f2_.put( tmp); catch(...) { f1_.unget( tmp); // ??? if ( additional_data_valid_ ) { tmp = additional_data_; additional_data_valid_ = false; } else f1_.get( tmp); try { f2_.put( tmp); catch(...) { additional_data_ = tmp; additional_data_valid_ = true;

141 Exception-safe programming
Příklad: StringStack::pop Zásobník prvků typu String Implementován seznamem Řešení A Jako v STL Rozdělit pop na dvě funkce top vrací vrchol zásobníku může jej vracet odkazem nemodifikuje data pop pouze zkracuje je silně bezpečná StringStack stk; String a; /* ... */ try { a = stk.top(); } catch (...) { /* chyba kopírování nebo prázdný zásobník, proměnná a nezměněna, zásobník nedotčen */ stk.pop(); /* chyba zkracování, proměnná a změněna,

142 Exception-safe programming
Příklad: StringStack::pop Zásobník prvků typu String Implementován seznamem Řešení B Namísto vracení hodnoty funkce pop vyplňuje parametr předávaný odkazem tím se vyloučí nutnost kombinovat volání pop s dalším kopírováním Pro uživatele jednodušší, implementace pop je však těžší StringStack stk; String a; /* ... */ try { stk.pop( a); } catch (...) { /* chyba zkracování nebo kopírování, proměnná a nezměněna, zásobník nedotčen */

143 Exception-safe programming
Příklad: StringStack::pop Zásobník prvků typu String Implementován seznamem Řešení B Lze implementovat nad řešením A #include <memory> class StringStack { public: /* A */ String & top(); void pop(); /* B */ void pop( String & out) { String & t = top(); swap( out, t); try { pop(); } catch (...) { throw; };

144 Exception 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 */

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

146 Exception 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í */

147 Exception specifications
Kompilátor (a runtime) zajistí, že nepovolená výjimka neopustí funkci Kompilátor to může využít Speciálně při volání funkce s prázdným throw () se nemusí generovat ošetřující kód Program se zmenší a možná i zrychlí Užitek pro programátory: Komentář Ladicí prostředek

148 Reference vs. ukazatel

149 Pravidla pro začátečníky Kdy použít referenci: T &
Reference a ukazatelé Pravidla pro začátečníky Kdy použít referenci: T & Výstupní parametr Návratová hodnota funkce zpřístupňující objekt T & vector<T>::at(size_t i) Kdy použít konstantní referenci: const T & Obvykle pouze kvůli rychlosti Parametr typu struktura/třída Návratová hodnota funkce zpřístupňující objekt ke čtení const T & vector<T>::at(size_t i) const Kdy použít ukazatel (T *) Je-li objekt dynamicky alokován Je-li nutná schopnost přesměrování, null, nebo aritmetika Nelze-li referenci správně namířit v okamžiku inicializace Kdy použít konstantní ukazatel (const T *) Sekundární odkaz na objekt, schopný pouze čtení

150 Pravidla pro pokročilejší Vlastník dynamicky alokovaného objektu
Reference a ukazatelé Pravidla pro pokročilejší Vlastník dynamicky alokovaného objektu je zodpovědný za jeho zrušení - musí použít ukazatel “T *” nelze-li jednoznačně určit vlastníka, použijte “shared_ptr<T>” Uživatel objektu Pokud je životnost pozorovatele kratší než životnost objektu lze použít referenci – “T &” nebo “const T &” Pokud je životnost delší než životnost objektu nebo jinak komplikovaná je nutné použít ukazatel – “T *” nebo “const T *”

151 Pravidla pro vracení hodnot odkazem
Reference a ukazatelé Pravidla pro vracení hodnot odkazem Pokud hodnota, kterou funkce vrací, existuje v nějakém objektu i po návratu z funkce, lze vrátit odkaz na tento objekt (konstantní) referencí T & vector<T>::back(); const T & vector<T>::back() const; T & T::operator+=(const T & b); T & T::operator++();// prefixová verze ++ vrací novou hodnotu Pokud se hodnota, kterou funkce vrací, nově spočítala a není nikde uložena, funkce musí vracet hodnotou T operator+( const T & a, const T & b); T T::operator++(int);// postfixová verze ++ vrací starou hodnotu

152 Třída jako datový typ

153 Konstruktor bez parametrů (default constructor)
Speciální metody tříd Konstruktor bez parametrů (default constructor) XXX(); Používán u proměnných bez inicializace Kompilátor se jej pokusí vygenerovat, je-li to třeba a pokud třída nemá vůbec žádný konstruktor: Položky, které nejsou třídami, nejsou generovaným konstruktorem inicializovány Generovaný konstruktor volá konstruktor bez parametrů na všechny předky a položky To nemusí jít např. pro neexistenci takového konstruktoru Kopírovací konstruktor (copy constructor) XXX( const XXX &); Používán pro předávání parametrů a návratových hodnot Kompilátor se jej pokusí vygenerovat, je-li to třeba a pokud třída kopírovací konstruktor nemá: Položky, které nejsou třídami, jsou kopírovány Na předky a položky se volá kopírovací konstruktor To nemusí jít kvůli ochraně přístupu

154 Operátor přiřazení (assignment operator)
Speciální metody tříd Operátor přiřazení (assignment operator) const XXX & operator=( const XXX &); Implementace operátoru = pro typ XXX na levé straně Kompilátor se jej pokusí vygenerovat, je-li to třeba a třída jej nemá: Položky, které nejsou třídami, jsou kopírovány Na předky a položky se volá operátor přiřazení To nemusí jít kvůli ochraně přístupu Destruktor ~XXX(); Používán při zániku objektu Kompilátor se jej pokusí vygenerovat, je-li to třeba a třída jej nemá Pokud je objekt destruován operátorem delete aplikovaným na ukazatel na předka, musí být destruktor v tomto předku deklarován jako virtuální virtual ~XXX();

155 Konverzní konstruktory
Speciální metody tříd Konverzní konstruktory class XXX { XXX( YYY); }; Zobecnění kopírovacího konstruktoru Definuje uživatelskou konverzi typu YYY na XXX Je-li tento speciální efekt nežádoucí, lze jej zrušit: explicit XXX( YYY); Konverzní operátory operator YYY() const; Definuje uživatelskou konverzi typu XXX na YYY Vrací typ YYY hodnotou (tedy s použitím kopírovacího konstruktoru YYY, pokud je YYY třída) Kompilátor vždy použije nejvýše jednu uživatelskou konverzi

156 V jiné situaci není virtuálnost funkcí užitečná
Virtuální funkce class Base { virtual void f() { /* ... */ } }; class Derived : public Base { Mechanismus virtuálních funkcí se uplatní pouze v přítomnosti ukazatelů nebo referencí Base * p = new Derived; p->f(); // volá Derived::f V jiné situaci není virtuálnost funkcí užitečná Derived d; d.f(); // volá Derived::f i kdyby nebyla virtuální Base b = d; // slicing = kopie části objektu b.f(); // volá Base::f ikdyž je virtuální Slicing je specifikum jazyka C++

157 Názvosloví Abstraktní třída Definice v C++: Třída obsahující alespoň jednu čistě virtuální funkci Běžná definice: Třída, která sama nebude instanciována Představuje rozhraní, které mají z ní odvozené třídy (potomci) implementovat Konkrétní třída Třída, určená k samostatné instanciaci Implementuje rozhraní, předepsané abstraktní třídou, ze které je odvozena

158 Dědičnost a destruktor
class Base { public: virtual ~Base() {} }; class Derived : public Base { virtual ~Derived() { /* ... */ } Base * p = new Derived; delete p; Pokud je objekt destruován operátorem delete aplikovaným na ukazatel na předka, musí být destruktor v tomto předku deklarován jako virtuální Odvozené pravidlo: Každá abstraktní třída má mít virtuální destruktor Je to zadarmo Může se to hodit

159 Mechanismus dědičnosti v C++ je velmi silný
Bývá používán i pro nevhodné účely Ideální použití dědičnosti je pouze toto ISA hierarchie pro plnohodnotné objekty (first-class objects) Živočich-Obratlovec-Savec-Pes-Jezevčík Objekt-Viditelný-Editovatelný-Polygon-Čtverec Vztah interface-implementace Readable-InputFile Writable-OutputFile (Readable+Writable)-IOFile Jiná použití dědičnosti obvykle signalizují chybu v návrhu Výjimky samozřejmě existují (traits...)

160 Dědičnost ISA hierarchie pro plnohodnotné objekty (first-class objects) C++: Jednoduchá nevirtuální veřejná dědičnost class Derived : public Base Abstraktní třídy někdy obsahují datové položky Vztah interface-implementace C++: Násobná virtuální veřejná dědičnost class Derived : virtual public Base1, virtual public Base2 Abstraktní třídy obvykle neobsahují datové položky Abstraktní třídy nebývají využívány k destrukci objektu Oba přístupy se často kombinují class Derived : public Base, virtual public Interface1, virtual public Interface2

161 Nesprávné užití dědičnosti
class Real { public: double Re; }; class Complex : public Real { public: double Im; }; Porušuje pravidlo "každý potomek má všechny vlastnosti předka" např. pro vlastnost "má nulovou imaginární složku" Důsledek - slicing: double abs( const Real & p) { return p.Re > 0 ? p.Re : - p.Re; } Complex x; double a = abs( x); // tento kód LZE přeložit, a to je špatně Důvod: Referenci na potomka lze přiřadit do reference na předka Complex => Complex & => Real & => const Real &

162 Nesprávné užití dědičnosti
class Real { public: double Re; }; class Complex : public Real { public: double Im; }; Slicing nastává i u předávání hodnotou double abs( Real p) { return p.Re > 0 ? p.Re : - p.Re; } Complex x; double a = abs( x); // tento kód LZE přeložit, a to je špatně Důvod: Předání hodnoty x do parametru p je provedeno implicitně vytvořeným konstruktorem: Real::Real( const Real & y) { Re = y.Re; } Parametr x typu Complex do tohoto konstruktoru lze předat Complex => Complex & => Real & => const Real &

163 Nesprávné užití dědičnosti
class Complex { public: double Re, Im; }; class Real : public Complex { public: Real( double r); }; Vypadá jako korektní specializace: "každé reálné číslo má všechny vlastnosti komplexního čísla" Chyba: Objekty v C++ nejsou hodnoty v matematice Třída Complex má vlastnost "lze do mne přiřadit Complex" Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude

164 Nesprávné užití dědičnosti
class Complex { public: double Re, Im; }; class Real : public Complex { public: Real( double r); }; Vypadá jako korektní specializace: "každé reálné číslo má všechny vlastnosti komplexního čísla" Chyba: Objekty v C++ nejsou hodnoty v matematice Třída Complex má vlastnost "lze do mne přiřadit Complex" Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude void set_to_i( Complex & p) { p.Re = 0; p.Im = 1; } Real x; set_to_i( x); // tento kód LZE přeložit, a to je špatně Důvod: Referenci na potomka lze přiřadit do reference na předka Real => Real & => Complex &

165 Nesprávné užití dědičnosti
class Complex { public: double Re, Im; }; class Real : public Complex { public: Real( double r); }; Vypadá jako korektní specializace: "každé reálné číslo má všechny vlastnosti komplexního čísla" Chyba: Objekty v C++ nejsou hodnoty v matematice Třída Complex má vlastnost "lze do mne přiřadit Complex" Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude Poznámka: při přímem přiřazování tento problém nenastane Complex y; Real x; x = y; // tento kód NELZE přeložit Důvod: operátor = se nedědí Complex & Complex::operator=( const Complex &); // nezdědí se Real & Real::operator=( const Real &); // nesouhlasí typ argumentu

166 Ideální užití dědičnosti a virtuálních funkcí
Abstraktní třída Definuje rozhraní objektu jako množinu předepsaných virtuálních funkcí class GraphicObject { public: virtual ~GraphicObject(); // každá abstraktní třída má mít v.d. virtual void paint() = 0; // čistě virtuální funkce virtual void move( int dx, int dy) = 0; // čistě virtuální funkce };

167 Příklad: dědičnost a virtuální funkce
ISA hierarchie Osoba Student Zaměstnanec Učitel Matikář Fyzikář Chemikář Ředitel Ostatní Školník Kuchař Které abstraktní třídy potřebujeme? Ty, jejichž specifické rozhraní někdo potřebuje Třída „Ostatní“ není potřeba

168 Příklad: dědičnost a virtuální funkce
ISA hierarchie Osoba Student Zaměstnanec Ředitel Školník Kuchař Potřebujeme třídu Osoba? Máme nějaký seznam osob? Další rozhraní Učitel Matikář Fyzikář Chemikář Má být Matikář odvozen z Učitele? Mají Matikář a Fyzikář něco společného?

169 Nepoužité slajdy PRG032

170 Šablony tříd - příklad Definice Použití
template< int n, class T> class Array { T p[ n]; T dummy; public: T & operator[]( int x) { return x<n ? p[x] : dummy; } }; Použití Array< 5, int> a; Array< 7, int> b; Array< 5 + 2, int> c; Array< 3, Array< 7, int> > d; a[ 3] = b[ 3]; a = b; // chyba !!! b = c; // OK, implicitní copy-constructor d[ 2][ 3] = 1;

171 Poučení – konverzní operátor
operator double() const; Implementuje konverzi Complex => double Není zde vhodný Konverze je ztrátová Může vést k nejednoznačnostem Complex a, b; double c; a = b + c; může znamenat volání Complex::Complex( double re, double im = 0.0); Complex operator+( const Complex & a, const Complex & b); Complex::operator double() const; double double::operator=( double b); // vestavěná operace ale také double operator+( double a, double b); // vestavěná operace Kompilátor si (z definice) neumí vybrat – hlásí chybu Výběr funkce/operátoru je řízen pouze typy argumentů

172 Poučení – konverzní operátor
operator double() const; Implementuje konverzi Complex => double Není zde vhodný Konverze je ztrátová Může vést k nejednoznačnostem Může vést k chybám Complex a, b; a = sin( b); může znamenat volání Complex sin( const Complex & x); // <complexmath.h> ale také Complex::operator double() const; double sin( double x); // <math.h> Complex( double re, double im = 0.0); První varianta má přednost... ...ale když zapomenete #include <complexmath.h> ...

173 C++

174 Zobecnění pojmu struktura (struct)
Třída a objekt Třída (class) Zobecnění pojmu struktura (struct) Rozdíl mezi class a struct v C++ je nepatrný Užívání class místo struct je pouze konvence Deklarace třídy obsahuje Deklarace datových položek (stejně jako v C) Funkce (metody), virtuální funkce a statické funkce Definice výčtových konstant a typů (včetně vnořených tříd) Objekt (instance třídy) Běhová reprezentace jednoho exempláře třídy Reprezentace objektu v paměti obsahuje Datové položky Skryté pomocné položky umožňující funkci virtuálních metod, výjimek a RTTI virtuální dědičnosti

175 Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na něj Triviální důsledek: class T { /*...*/ }; T * p; // zde nevzniká objekt typu T

176 Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na něj Triviální důsledek: class T { /*...*/ }; T * p; // zde nevzniká objekt typu T Netriviální důsledek: Proměnná typu T je vždy objekt typu T a žádného jiného, přestože do ní lze přiřadit i objekt odvozeného typu class U : public T { /*...*/ }; U y; T x = y; // toto je kopie části objektu y do vznikajícího objektu x

177 Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na něj Triviální důsledek: class T { /*...*/ }; T * p; // zde nevzniká objekt typu T Netriviální důsledek: Proměnná typu T je vždy objekt typu T a žádného jiného, přestože do ní lze přiřadit i objekt odvozeného typu class U : public T { /*...*/ }; U y; T x = y; // toto je kopie části objektu y do vznikajícího objektu x Poznámka pro znalce pravidel: K tomuto přiřazení může dojít díky existenci automaticky vytvořeného copy-constructoru T::T( const T &); a díky možnosti konvertovat odkaz na potomka na odkaz na předka: U => U & => T & => const T &

178 Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na něj Triviální důsledek: class T { /*...*/ }; T * p; // zde nevzniká objekt typu T Netriviální důsledek: Proměnná typu T je vždy objekt typu T a žádného jiného, přestože do ní lze přiřadit i objekt odvozeného typu class U : public T { /*...*/ }; U y; T x = y; // toto je kopie části objektu y do vznikajícího objektu x Poznámka pro znalce implementace: Zde (ani nikde jinde) se nekopírují odkazy na tabulky virtuálních funkcí Proměnná typu T tedy zůstane typem T včetně přiřazení těl virtuálních funkcí Jiné chování by nemělo smysl

179 Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na něj Triviální důsledek: class T { /*...*/ }; T * p; // zde nevzniká objekt typu T Netriviální důsledek: Proměnná typu T je vždy objekt typu T a žádného jiného, přestože do ní lze přiřadit i objekt odvozeného typu class U : public T { /*...*/ }; U y; T x = y; // toto je kopie části objektu y do vznikajícího objektu x V tomto odlišování se C++ liší od většiny jazyků s objekty (Java, JavaScript, PHP, VisualBasic, ...)

180 Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na něj Nefunguje naivní implementace polymorfního typu: class Variant { enum { REAL, COMPLEX } t; }; class Real : public Variant { public: double Re; }; class Complex : public Variant { public: double Re, Im; }; Variant max( Variant a, Variant b); Real x, y, z = max( x, y); // nelze přeložit Complex u, v, w = max( u, v); // nelze přeložit

181 Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na něj Nefunguje naivní implementace polymorfního typu: class Variant { enum { REAL, COMPLEX } t; }; class Real : public Variant { public: double Re; }; class Complex : public Variant { public: double Re, Im; }; Variant max( Variant a, Variant b); Real x, y, z = max( x, y); // nelze přeložit Complex u, v, w = max( u, v); // nelze přeložit Parametry a, b nedokážou přenést atributy Re, Im Návratovou hodnotu nelze (ani explicitně) přetypovat na potomka Real x, y, z = (Real)max( x, y); // nelze přeložit Complex u, v, w = (Complex)max( u, v); // nelze přeložit I kdyby to šlo, typ Variant vracený hodnotou nedokáže přenést atributy Re, Im

182 Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na něj Nefunguje naivní implementace polymorfního typu: class Variant { enum { REAL, COMPLEX } t; }; class Real : public Variant { public: double Re; }; class Complex : public Variant { public: double Re, Im; }; V tomto případě lze tento problém řešit referencemi: Variant & max( Variant & a, Variant & b); vyžaduje ovšem explicitní přetypování, které je nebezpečné Real x, y, z = (Real &)max( x, y); // funguje Complex u, v, w = (Complex &)max( u, v); // funguje Vracení referencí ovšem funguje pouze pro funkce max a min

183 Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na něj Nefunguje naivní implementace polymorfního typu: class Variant { enum { REAL, COMPLEX } t; }; class Real : public Variant { public: double Re; }; class Complex : public Variant { public: double Re, Im; }; V tomto případě lze tento problém řešit referencemi: Variant & max( Variant & a, Variant & b); vyžaduje ovšem explicitní přetypování, které je nebezpečné Real x, y, z = (Real &)max( x, y); // funguje Complex u, v, w = (Complex &)max( u, v); // funguje Vracení referencí ovšem funguje pouze pro funkce max a min Tyto funkce mají speciální vlastnost: vrací jeden ze svých parametrů

184 Různé pohledy na třídy a různá pojmenování
Třídy v C++ Konstrukce class, dědičnost a virtuální funkce jsou silný mechanismus, užívaný k různým účelům Různé pohledy na třídy a různá pojmenování Abstraktní a konkrétní třídy Třídy jako datové typy Kontejnery (třídy logicky obsahující jiné objekty) Singletony (jednou instanciované třídy) Traits (neinstanciované třídy) Různé účely dědičnosti Rozšíření požadovaného rozhraní Implementace požadovaného rozhraní Rozšíření implementované funkčnosti Využití k implementaci

185 Nesprávné užití dědičnosti
class Real { public: double Re; }; class Complex : public Real { public: double Im; }; Vypadá jako reusabilita kódu

186 Nesprávné užití dědičnosti
class Real { public: double Re; }; class Complex : public Real { public: double Im; }; Vypadá jako reusabilita kódu Porušuje pravidlo "každý potomek má všechny vlastnosti předka" např. pro vlastnost "má nulovou imaginární složku"

187 Nesprávné užití dědičnosti
class Real { public: double Re; }; class Complex : public Real { public: double Im; }; Vypadá jako reusabilita kódu Porušuje pravidlo "každý potomek má všechny vlastnosti předka" např. pro vlastnost "má nulovou imaginární složku" Důsledek - slicing: double abs( const Real & p) { return p.Re > 0 ? p.Re : - p.Re; } Complex x; double a = abs( x); // tento kód LZE přeložit, a to je špatně Důvod: Referenci na potomka lze přiřadit do reference na předka Complex => Complex & => Real & => const Real &

188 Nesprávné užití dědičnosti
class Real { public: double Re; }; class Complex : public Real { public: double Im; }; Slicing nastává i u předávání hodnotou double abs( Real p) { return p.Re > 0 ? p.Re : - p.Re; } Complex x; double a = abs( x); // tento kód LZE přeložit, a to je špatně Důvod: Předání hodnoty x do parametru p je provedeno implicitně vytvořeným konstruktorem: Real::Real( const Real & y) { Re = y.Re; } Parametr x typu Complex do tohoto konstruktoru lze předat Complex => Complex & => Real & => const Real &

189 Nesprávné užití dědičnosti
class Complex { public: double Re, Im; }; class Real : public Complex { public: Real( double r); }; Vypadá jako korektní specializace: "každé reálné číslo má všechny vlastnosti komplexního čísla"

190 Nesprávné užití dědičnosti
class Complex { public: double Re, Im; }; class Real : public Complex { public: Real( double r); }; Vypadá jako korektní specializace: "každé reálné číslo má všechny vlastnosti komplexního čísla" Chyba: Objekty v C++ nejsou hodnoty v matematice Třída Complex má vlastnost "lze do mne přiřadit Complex" Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude

191 Nesprávné užití dědičnosti
class Complex { public: double Re, Im; }; class Real : public Complex { public: Real( double r); }; Vypadá jako korektní specializace: "každé reálné číslo má všechny vlastnosti komplexního čísla" Chyba: Objekty v C++ nejsou hodnoty v matematice Třída Complex má vlastnost "lze do mne přiřadit Complex" Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude void set_to_i( Complex & p) { p.Re = 0; p.Im = 1; } Real x; set_to_i( x); // tento kód LZE přeložit, a to je špatně Důvod: Referenci na potomka lze přiřadit do reference na předka Real => Real & => Complex &

192 Nesprávné užití dědičnosti
class Complex { public: double Re, Im; }; class Real : public Complex { public: Real( double r); }; Vypadá jako korektní specializace: "každé reálné číslo má všechny vlastnosti komplexního čísla" Chyba: Objekty v C++ nejsou hodnoty v matematice Třída Complex má vlastnost "lze do mne přiřadit Complex" Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude Poznámka: při přímem přiřazování tento problém nenastane Complex y; Real x; x = y; // tento kód NELZE přeložit Důvod: operátor = se nedědí Complex & Complex::operator=( const Complex &); // nezdědí se Real & Real::operator=( const Real &); // nesouhlasí typ argumentu

193 Třídy sloužící jako datové typy Proměnné typu T
Třídy v C++ Třídy sloužící jako datové typy Proměnné typu T Časté kopírování, vracení hodnotou Přiřazení bývá jediný způsob změny stavu objektu Dědičnost nemá smysl Bez virtuálních funkcí Třídy reprezentující „živé“ objekty Proměnné typu T *, případně T & Objekty alokovány dynamicky Kopírování nemívá smysl Metody měnící stav objektu Většinou s dědičností a virtuálními funkcemi

194 Ideální užití dědičnosti a virtuálních funkcí
Abstraktní třída Definuje rozhraní objektu jako množinu předepsaných virtuálních funkcí Abstraktní třídy se mohou dědit Dědičnost jako rozšiřování předepsaného rozhraní class ClickableObject : public GraphicObject { public: virtual void click( int x, int y) = 0; // čistě virtuální funkce };

195 Ideální užití dědičnosti a virtuálních funkcí
Abstraktní třída Definuje rozhraní objektu jako množinu předepsaných virtuálních funkcí Konkrétní třída Implementuje předepsané virtuální funkce Je potomkem abstraktní třídy Dědičnost jako vztah rozhraní-implementace

196 Ideální užití dědičnosti a virtuálních funkcí
Abstraktní třída Definuje rozhraní objektu jako množinu předepsaných virtuálních funkcí Konkrétní třída Implementuje předepsané virtuální funkce class Button : public ClickableObject { public: Button( int x, int y, const char * text); protected: virtual void paint(); virtual void move( int dx, int dy); virtual void click( int x, int y); private: int x_, y_; char * text_; };

197 Ideální užití dědičnosti a virtuálních funkcí
Abstraktní třída Definuje rozhraní objektu jako množinu předepsaných virtuálních funkcí Konkrétní třída Implementuje předepsané virtuální funkce Polotovar třídy Mezi abstraktní a konkrétní třídou - třída implementující část předepsaných virtuálních funkcí Jejím potomkem je konkrétní třída nebo jiný polotovar Dědičnost jako reusabilita kódu

198 Ideální užití dědičnosti a virtuálních funkcí
Polotovar třídy Mezi abstraktní a konkrétní třídou - třída implementující část předepsaných virtuálních funkcí class PositionedObject : public ClickableObject { public: PositionedObject( int x, int y); protected: int get_x() const { return x_; } int get_y() const { return y_; } virtual void move( int dx, int dy); private: int x_, y_; };

199 Ideální užití dědičnosti a virtuálních funkcí
Konkrétní třída Implementuje předepsané virtuální funkce Konkrétní třídy mohou mít potomky - jiné konkrétní třídy se změněnými vlastnostmi (redefinovanými virtuálními funkcemi) Dědičnost jako reusabilita kódu se změnou chování class IconButton : public Button { public: IconButton( int x, int y, const char * text, BitMap icon); protected: virtual void paint(); private: BitMap icon_; };

200 Ideální užití dědičnosti a virtuálních funkcí
Různé významy dědičnosti Rozšiřování předepsaného rozhraní GraphicObject => ClickableObject Vztah rozhraní-implementace ClickableObject => PositionedObject, Button Reusabilita kódu PositionedObject => Button Reusabilita se změnou chování (overriding) Button => IconButton A to není zdaleka všechno... C++ pro odlišné účely využívá tytéž mechanismy Některé jazyky tyto účely rozlišují (Java)

201 Ideální užití dědičnosti a virtuálních funkcí
Ideální abstraktní třída Pouze čistě virtuální funkce Žádná data, žádná těla funkcí Někdy (nesprávně) nazývána protokol Pojem Protokol většinou znamená seznam funkcí a pravidla pro pořadí jejich volání, což C++ nedovede vyjádřit

202 Ideální užití dědičnosti a virtuálních funkcí
Ideální abstraktní třída Pouze čistě virtuální funkce Žádná data, žádná těla funkcí Někdy (nesprávně) nazývána protokol Pojem Protokol většinou znamená množinu funkcí a pravidla pro pořadí jejich volání, což C++ nedovede vyjádřit Takové množiny funkcí má smysl kombinovat Významem je sjednocení množin schopností Příklad: Fyzikář+Matikář

203 Ideální užití dědičnosti a virtuálních funkcí
Ideální abstraktní třída Pouze čistě virtuální funkce Žádná data, žádná těla funkcí Někdy (nesprávně) nazývána protokol Pojem Protokol většinou znamená množinu funkcí a pravidla pro pořadí jejich volání, což C++ nedovede vyjádřit Takové množiny funkcí má smysl kombinovat Významem je sjednocení množin schopností Příklad: Fyzikář+Matikář V C++: násobná dědičnost Obvykle musí být virtuální, aby odpovídala sjednocení: Fyzikář = Pedagogika + Fyzika Matikář = Pedagogika + Matematika Fyzikář+Matikář nemá mít dvě rozhraní pro Pedagogiku

204 Ideální užití dědičnosti a virtuálních funkcí
Podmínka užitečnosti virtuálních funkcí Funkce musí být volána na objektu, jehož skutečný typ není v době kompilace znám Nesmí to být proměnná typu třída Musí to být ukazatel nebo reference na třídu

205 Ideální užití dědičnosti a virtuálních funkcí
Typické použití virtuálních funkcí Polymorfní datová struktura Datová struktura (pole, seznam, strom, ...) obsahující objekty různých typů Tyto objekty musí být vázány odkazem Typicky bývají samostatně dynamicky alokovány Na objektech se obvykle vykonávají hromadné abstraktní operace (např. vykreslení) Volání čistě virtuální funkce (paint) na každém objektu

206 Ideální užití dědičnosti a virtuálních funkcí
Typické použití virtuálních funkcí Polymorfní datová struktura class Scene { public: void insert( GraphicObject * go); void paintAll() const; private: enum { MAX_ = 30 }; GraphicObject * objects_[ MAX_]; int n_; }; primitivní řešení: pole ukazatelů pole objektů by nefungovalo (ani nešlo přeložit) void Scene::paintAll() { for ( int i = 0; i < n_; i++) objects_[ i]->paint(); }

207 Ideální užití dědičnosti a virtuálních funkcí
Typické použití virtuálních funkcí Polymorfní datová struktura struct Item { GraphicObject * go; Item * next; }; class Scene { public: void insert( GraphicObject * go); void paintAll() const; private: Item * first_; }; lepší řešení: spojový seznam ukazatelů Item nemůže přímo obsahovat GraphicObject void Scene::paintAll() { for ( Item * p = first_; p; p = p->next) p->go->paint(); }

208 Ideální užití dědičnosti a virtuálních funkcí
Typické použití virtuálních funkcí Polymorfní datová struktura jiné lepší řešení: intrusivní spojový seznam samotný GraphicObject slouží jako prvek spojového seznamu nevýhoda: abstraktní rozhraní zároveň obsahuje implementaci class GraphicObject { /* ... čistě virtuální funkce ... */ private: friend class Scene; Item * next_; }; class Scene { public: void insert( GraphicObject * go); void paintAll() const; GraphicObject * first_;

209 Ideální užití dědičnosti a virtuálních funkcí
Typické použití virtuálních funkcí Polymorfní datová struktura moderní řešení: STL kontejnery z STL vždy obsahují kopie vkládaných objektů std::list< GraphicObject> by nešlo ani přeložit kontejner je tedy třeba aplikovat na ukazatel, podobně jako pole class Scene { public: void insert( GraphicObject * go); void paintAll() const; private: std::list< GraphicObject *> objects_; };

210 Ukázková implementace č. 1 (nepoužitelná)
String Ukázková implementace č. 1 (nepoužitelná)

211 String č. 1 - Nepoužitelné řešení
class String { public: String() { _str[ 0] = 0; } String( const char * s) { strcpy( _str, s); } const char * c_str() const { return _str; } friend String operator+( const String & a, const String & b); private: enum { MAX = 256 }; char _str[ MAX]; }; String operator+( const String & a, const String & b) { String c; strcpy( c._str, a._str); strcat( c._str, b._str); return c; }

212 Poučení - Konverzní operátor
Metoda c_str by mohla být nahrazena konverzním operátorem operator const char *() const; V tomto případě to není vhodné: String x; if ( x > "A" ) Může být převedeno na String > String (pokud je definován takový operátor) ale také na const char * > const char * Porovnává adresy ! printf( "%s", x); Zde k žádné konverzi nedojde ! Program bude pravděpodobně ukončen pro pokus o nedovolenou operaci

213 Ukázková implementace č. 2 (naivní, neefektivní)
String Ukázková implementace č. 2 (naivní, neefektivní)

214 String č. 2 - Naivní řešení
class String { public: String(); String( const String &); const String & operator=( const String &); ~String(); String( const char *); const char * c_str() const; String cat( const String &) const; private: char * _str; }; inline String operator+( const String & a, const String & b) { return a.cat( b); }

215 Poučení - konstruktory
Ve třídě String jsou odkazy na data uložená jinde Chování kompilátorem vytvořených metod nevyhovuje: String(); Nedělá nic - je třeba naalokovat prázdný string String( const String &); Kopíruje ukazatel - je třeba kopírovat data, na která ukazuje String & operator=( const String &); Totéž, navíc je nutné před přiřazením uklidit ~String(); Nedělá nic - je třeba uklidit Tyto metody je tedy třeba napsat vlastní

216 Poučení - konstruktory
Operátor přiřazení by měl udělat toto: Zrušit starý obsah levé strany Totéž co destruktor Okopírovat pravou stranu Totéž co konstruktor Vrátit novou hodnotu levé strany To lze vrátit odkazem Pozor - v některých případech to fungovat nebude: String a = "ha"; a = a; Při kopírovaní pravá strana už nebude existovat

217 Poučení - konstruktory
Vzorový operátor přiřazení: String & String::operator=( const String & b) { if ( this != & b ) { clean(); fill( b); } return * this; Konstruktory nelze přímo volat, u destruktoru to není rozumné String::String( const String & b) String::~String() clean();

218 Poučení - konstruktory
Lepší řešení operátoru přiřazení: #include <algorithm> void String::swap( String & b) { std::swap( _str, b._str); } String & String::operator=( const String & b) String tmp( b); swap( tmp); return * this; Toto řešení je navíc exception-safe (později...) Metodu swap je vhodné publikovat takto void swap( String & a, String & b) { a.swap( b); } Kdyby někdo udělal třídu obsahující náš String...

219 Ukázková implementace č. 3 (counted-pointers)
String Ukázková implementace č. 3 (counted-pointers)

220 String č. 3 - Counted-pointers
class StringBody; class String { public: String(); String( const String &); const String & operator=( const String &); ~String(); String( const char *); operator const char *() const; String cat( const String &) const; private: String( StringBody *); StringBody * _body; };

221 String č. 3 - Counted-pointers
class StringBody { friend class String; private: StringBody(); StringBody( int); ~StringBody(); void inc() { _count++; }; void dec(); char * buffer() { return _str; }; char * _str; int _count; static StringBody empty; };

222 String č. 3 - Counted-pointers
Kopie a destrukce String::String( const String & b) { (_body = b._body)->inc(); } const String & String::operator=( const String & b) if ( _body != b._body ) _body->dec(); return * this; String::~String()

223 String č. 3 - Counted-pointers
Destrukce těla void StringBody::dec() { if ( ! --count ) delete this; }; StringBody::~StringBody() delete[] _str; }

224 String č. 3 - Counted-pointers
Vytvoření neprázdného těla StringBody::StringBody( int l) { _count = 0; _str = new char[ l]; } String::String( StringBody * b) (_body = b)->inc(); String::String( const char * s) _body = new StringBody( strlen( s) + 1); _body->inc(); strcpy( _body->_str, s);

225 String č. 3 - Counted-pointers
Speciální implementace prázdného řetězce StringBody::StringBody() { _str = ""; _count = 1; } StringBody StringBody::empty; String::String() (_body = &StringBody::empty)->inc();

226 String operator [ ]

227 String č. 4 - operator [ ] - čtení
class String { public: char operator[]( int pos) const { if ( pos < 0 || pos >= _body->length() ) return 0; return _body->buffer()[ _pos]; }; /* ... */

228 String č. 4 - operator [ ] - zápis
class String { public: char & operator[]( int pos) { if ( pos < 0 || pos >= _body->length() ) return _dummy; return _body->buffer()[ _pos]; }; private: static char _dummy; /* ... */ char String::_dummy = 0;

229 String č. 4 - operator [ ] - zápis
class String { public: char & operator[]( int pos) { if ( pos < 0 || pos >= _body->length() ) return _dummy; return _body->buffer()[ _pos]; // chyba: _body je sdíleno ! }; private: static char _dummy; /* ... */ char String::_dummy = 0;

230 String č. 4 - operator [ ] - zápis do privátní kopie
class String { public: char & operator[]( int pos) { /* ... */ make_private(); return _body->buffer()[ _pos]; }; private: void make_private() if ( _body->count > 1 ) StringBody * nb = new StringBody( * _body); // copy-constructor _body->dec(); _body = nb; _body->inc(); }

231 String č. 4 - operator [ ] - čtení a zápis
class String { public:   char operator[]( int pos) const { /* ... */ return _body->buffer()[ _pos]; }; char & operator[]( int pos) make_private();

232 String č. 4 - operator [ ] - čtení a zápis
class String { public:   char operator[]( int pos) const { /* ... */ return _body->buffer()[ _pos]; }; char & operator[]( int pos) make_private(); String a, b; char c; const String d; a[ 1] = c; // char & operator[]( int) c = d[ 1]; // char operator[]( int) const c = b[ 1]; // char & operator[]( int) - vytváří privátní kopii

233 String č. 4 - operator [ ] – rozlišení čtení a zápisu
class StringPos; class String { public: /* ... */ char read_pos( int pos) const { /* ... */ } void write_pos( int pos, char b) { /* ... */ }   char operator[]( int pos) const { return read_pos( pos); } StringPos operator[]( int pos) { return StringPos( this, pos); } }; class StringPos { StringPos( String * t, int p) : t_( t), p_( p) {} operator char() const { return t_->read_pos( p_); } const StringPos & operator =( char b) const { t_->write_pos( p_, b); return * this; } private: String * t_; int p_;

234 Standardní knihovny C++

235 namespace Konstrukce namespace umožňuje uzavřít několik deklarací typů, tříd, globálních proměnných a funkcí do zvláštního prostoru jmen Konstrukci namespace lze otevírat vícenásobně namespace X { typedef char * ptr; ptr f( ptr a, ptr b); }; ptr g(); Uzavřené identifikátory lze mimo tento prostor referencovat kvalifikovaným jménem X::ptr v = X::g(); Celý prostor jmen lze rozbalit do aktuálního bloku nebo modulu konstrukcí: using namespace X;

236 Standardní knihovny C++
V novějších implementacích má většina hlavičkových souborů dvě verze Stará konvence – v budoucnu nebude podporována soubor vector.h obsahuje šablonu vector Nová konvence soubor vector obsahuje šablonu vector uzavřenou do namespace std je tedy nutné používat identifikátor std::vector Standardní knihovny C++ mají tyto hlavní součásti Základní knihovny převzaté z C, podle nové konvence v přejmenovaných souborech Rozšířené C++ knihovny iostream: Systém znakového a formátovaného vstupu a výstupu STL: Standard Template Library

237 Základní knihovny C a C++
<assert.h> <cassert> - ladicí funkce (makro assert) <ctype.h> <cctype> - klasifikace znaků (isalpha, isspace, ...) <errno.h> <cerrno> - chybové kódy (ENOMEM, ...), proměnná errno <float.h> <cfloat> - vlastnosti a limity reálných typů (DBL_MAX, ...) <limits.h> <limits> <climits> - limity celočíselných typů (INT_MAX, ...) <locale.h> <locale> <clocale> - přizpůsobení národnímu prostředí <math.h> <cmath> - matematické funkce (sin, ...) <setjmp.h> <csetjmp> - meziprocedurální skoky (setjmp, longjmp) <signal.h> <csignal> - signály operačního systému <stdarg.h> <cstdarg> - makra pro funkce s proměnným počtem argumentů <stddef.h> <cstddef> - užitečné typy a konstanty (NULL) <stdio.h> <cstdio> - standardní a souborový vstup a výstup <stdlib.h> <cstdlib> - užitečné funkce (malloc, ...) <string.h> <cstring> - manipulace s řetězci (strcpy, ...) <time.h> <ctime> - konverze data a času <wchar.h> <cwchar> - 16-bitové řetězce (wchar_t) <wctype.h> <cwctype> - klasifikace 16-bitových znaků

238 Rozšířené knihovny pro C++
<bitset.h> <bitset> -- šablona pro bitové pole (bitset<N>) <complex.h> <complex> - komplexní čísla různé přesnosti (complex<double>,...) <exception.h> <exception> - nastavení zpracování výjimek (set_unexpected,...) <stdexcept.h> <stdexcept> - standardní výjimky (overflow_error,...) <valarray.h> <valarray> - šablony různě inteligentních polí (valarray,...), vektorové operace (podpora paralelních výpočtů matematických funkcí)

239 STL – Ostatní <algorithm.h> <algorithm> - užitečné algoritmy (for_each, sort, next_permutation, ...) <functional.h> <functional> - podpora funktorů <iterator.h> <iterator> - podpora iterátorů <memory.h> <memory> - alokátory pro kontejnery <numeric.h> <numeric> - jednoduchá matematika na prvcích kontejnerů <utility.h> <utility> - pomocné konstrukce (pair,...)

240 vstupní a výstupní proudy
iostream vstupní a výstupní proudy

241 iostream Manipulátory hex - šestnáctkový výpis, setw - počet míst
#include <iostream> #include <iomanip> using namespace std; f() { int X; double Y; cin >> X >> Y; cout << "X = " << hex << setw(4) << X << ", Y = " << Y << endl; } Manipulátory hex - šestnáctkový výpis, setw - počet míst platí pro daný stream (cout) trvale (do další změny) endl - vloží oddělovač řádek

242 <sstream> - *stringstream – spolupracuje se std::string
#include <string> #include <sstream> #include <iomanip> using namespace std; string f( int a) { ostringstream x; x << "a = " << a; return x.str(); } <sstream> - *stringstream – spolupracuje se std::string <strstream> - *strstream – spolupracuje s char *

243 iostream Hlavičkové soubory
<fstream.h> <fstream> - souborový vstup a výstup (ifstream, ofstream, fstream) <iomanip.h> <iomanip> - manipulátory pro nastavení parametrů formátovaného vstupu a výstupu (setw, setprecision, setfill, setbase, ...) <ios.h> <ios> - základní funkce abstraktního souboru, základní nastavení formátu (hex, left, ...) <iostream.h> <iostream> - standardní vstup a výstup (cin, cout, cerr) <istream.h> <istream> - abstraktní vstupní a kombinované médium (istream, iostream) <ostream.h> <ostream> - abstraktní výstupní médium (ostream) <sstream.h> <sstream> - vnitřní paměť jako médium (istringstream, ostringstream, stringstream)

244 basic_...<T> jsou šablony
iostream Abstraktní rozhraní basic_...<T> jsou šablony T = char - 8-bitové znakové sady typedef: ios, istream, ostream, iostream, streambuf T = wchar_t - 16-bitové znakové sady typedef: wios, wistream, wostream, wiostream, wstreambuf dědičnost virtuální dědičnost nastavení formátovače ios_base ukazatel stav média basic_ios<T> basic_streambuf<T> virtuální funkce čtení a zápisu basic_istream<T> basic_ostream<T> přímé a formátované čtení přímý a formátovaný zápis basic_iostream<T>

245 iostream Abstraktní rozhraní
funkce, které potřebují zapisovat, dostávají basic_ostream<T> & void zapis_neco( ostream & o) { o << neco; } dědičnost virtuální dědičnost nastavení formátovače ios_base stav média basic_ios<T> basic_ostream<T> přímý a formátovaný zápis

246 iostream Abstraktní rozhraní
funkce, které potřebují číst, dostávají basic_istream<T> & pozor: čtení modifikuje stream (posunuje ukazovátko) void cti_neco( istream & i) { i >> neco; } dědičnost virtuální dědičnost nastavení formátovače ios_base ukazatel stav média basic_ios<T> basic_istream<T> přímé a formátované čtení

247 iostream Abstraktní rozhraní
funkce, které potřebují číst i zapisovat totéž médium, dostávají basic_iostream<T> & ukazovátko čtení a zápisu NENÍ společné void zmen_neco( iostream & io) { io.read( neco, N); io.write( neco2, N); } dědičnost virtuální dědičnost nastavení formátovače ios_base ukazatel stav média basic_ios<T> basic_istream<T> basic_ostream<T> přímé a formátované čtení přímý a formátovaný zápis basic_iostream<T>

248 iostream Abstraktní rozhraní
basic_iostream< T> obsahuje funkce čtení i zápisu nastavení formátu i stav média jsou společné pro čtení i zápis basic_ios< T> zde musí být pouze v jedné instanci dědění basic_ios< T> musí být virtuální template< class T> class basic_istream : virtual public basic_ios< T> dědičnost virtuální dědičnost nastavení formátovače ios_base stav média basic_ios<T> basic_istream<T> basic_ostream<T> přímé a formátované čtení přímý a formátovaný zápis basic_iostream<T>

249 iostream Médium Konkrétní médium je implementováno jako potomek třídy basic_streambuf<T> Standardní knihovna C++ nabízí: soubor (to, co umí OS, tedy včetně rour apod.) uložení v paměti Lze implementovat vlastní (např. výstup do okna) stav formátovače ios_base stav média basic_ios<T> basic_streambuf<T> virtuální funkce čtení a zápisu basic_istream<T> basic_ostream<T> přímé a formátované čtení přímý a formátovaný zápis basic_...buf<T> basic_iostream<T> médium, implementace čtení a zápisu

250 Paměťové médium <sstream>
Obálkové třídy ...stringstream<T> zařídí vznik basic_stringbuf<T> Umožňují přístup k médiu jako basic_string<T> stav formátovače ios_base stav média basic_ios<T> basic_streambuf<T> virtuální funkce čtení a zápisu basic_istream<T> basic_ostream<T> přímé a formátované čtení přímý a formátovaný zápis basic_stringbuf<T> basic_iostream<T> médium, implementace čtení a zápisu basic_istringstream<T> basic_ostringstream<T> str: přístup k médiu str: přístup k médiu basic_stringstream<T> str: přístup k médiu

251 Souborové médium <fstream>, <iostream> (cin, cout, cerr)
Obálkové třídy ...fstream<T> zařídí vznik basic_filebuf<T> Soubor se otevírá(zavírá) metodou open(close) těchto tříd stav formátovače ios_base stav média basic_ios<T> basic_streambuf<T> virtuální funkce čtení a zápisu basic_istream<T> basic_ostream<T> přímé a formátované čtení přímý a formátovaný zápis basic_filebuf<T> basic_iostream<T> médium, implementace čtení a zápisu basic_ifstream<T> basic_ofstream<T> open, close open, close Operační systém basic_fstream<T> open, close

252 istream/ostream - neformátované čtení/zápis
iostream ios: stav média good, eof, fail, bad - metody indikující stavy istream/ostream - neformátované čtení/zápis read/write - čtení/zápis n znaků get/put - čtení/zápis jednotlivých znaků a čtení po řádkách seekg/seekp, tellg/tellp - posun ukazovátka, zjištění pozice funkce ...g manipulují s ukazovátkem pro čtení (get, istream) funkce ...p manipulují s ukazovátkem pro čtení (put, ostream) u některých médií nefunguje (roury)

253 Oba operátory vrací levý operand
iostream formátované čtení basic_istream<T> & operator>>( basic_istream<T> & s, D & x) operátor přečte několik znaků ve tvaru určeném typem D naplní výstupní parametr x formátovaný zápis basic_ostream<T> & operator<<( basic_ostream<T> & s, D x) operátor vypíše x jako několik znaků ve tvaru určeném typem D Oba operátory vrací levý operand Tím je umožněno zřetězené použití s << x << y << z; je ekvivalentní s << x; s << y; s << z;

254 formátované čtení/zápis
iostream formátované čtení/zápis basic_istream<T> & operator>>( basic_istream<T> & s, D & x) basic_ostream<T> & operator<<( basic_ostream<T> & s, D x) Knihovna istream/ostream implementuje operátory pro typy (unsigned) short, (unsigned) int, (unsigned) long - dec, hex, oct float, double, long double - desetinný a/nebo exponenciální tvar bool, void * - pro ladicí účely char/wchar_t - znak char * / wchar_t * - řetězec v C tvaru Další typy lze dodefinovat (jako globální operátory) Standardní knihovny C++ je definují pro string/wstring - řetězec complex - komplexní číslo

255 Definování vlastních formátovacích operátorů
iostream Definování vlastních formátovacích operátorů class Souradnice { public: int x, y; }; std::ostream & operator<<( std::ostream & s, const Souradnice & a) { return s << '[' << a.x << ',' << a.y << ']'; } Použití Souradnice p; std::cout << "p = " << p << std::endl;

256 Jak fungují manipulátory
iostream Jak fungují manipulátory cout << "X = " << hex << setw(4) << X << ", Y = " << Y << endl; Bez parametrů (hex, endl, ...) Definovány jako funkce manipulující s ios_base Proto ios_base musí být třída a nikoliv šablona jako basic_ios ios_base & hex( ios_base & s) { s.setf( ios_base::hex); return s; } Akceptovány jako ukazatel na funkci zvláštní verzí operátoru << basic_ostream<T> & operator<<( basic_ostream<T> & s, ios_base & (* f)( ios_base & s)) { f( s);

257 Jak fungují manipulátory
iostream Jak fungují manipulátory cout << "X = " << hex << setw(4) << X << ", Y = " << Y << endl; S parametry (setw, setprecision, setfill, ...) Definovány jako funkce vracející speciální třídu struct setw_manip { int x; explicit setw_manip( int p) : x( p) {} }; setw_manip setw( int p) { return setw_manip( p); } Akceptovány zvláštními verzemi operátoru << basic_ostream<T> & operator<<( basic_ostream<T> & s, const setw_manip & p) { s.width( p.x); return s;

258 Vztah proudů a kontejnerů
iostream Vztah proudů a kontejnerů stringstream umožňuje přístup k médiu jako string pro string jsou definovány operátory << a >> <iterator> definuje iterátory nad proudy: istream_iterator< U> vstupní iterátor, čte typ U pomocí operátoru >> parametrem konstruktoru je istream & ostream_iterator< U> výstupní iterátor, zapisuje typ U pomocí operátoru << parametrem konstruktoru je ostream &

259 Iterátory nad proudy - příklady naplnění kontejneru ze vstupu
iostream Iterátory nad proudy - příklady naplnění kontejneru ze vstupu std::vector< double> a; std::copy( std::istream_iterator< double>( std::cin), // aktuální pozice std::istream_iterator< double>(), // "konec souboru" std::back_inserter( a)); // vkládací iterátor kontejneru vysypání kontejneru na výstup nevýhoda: neodděluje elementy výstupu std::vector< std::string> b; b.begin(), b.end(), std::ostream_iterator< std::string>( std::cout));

260 Typová informace za běhu
RTTI Typová informace za běhu

261 RTTI Operátor typeid Identifikace typu Vrací identifikaci typu T
typeid(T) Vrací identifikaci typu T typeid(e) Pokud výraz e je typu reference na třídu s alespoň jednou virtuální funkcí Vrací identifikaci typu objektu určeného výrazem e Pokud je reference nulová, vyvolává výjimku std::bad_typeid Jinak Vrací identifikaci statického typu výrazu e Identifikace typu const std::type_info & <typeinfo> Lze porovnávat na rovnost Má metodu name() vracející řetězec s nějakou formou jména typu

262 Alternativa místo dynamic_cast
RTTI Typické použití Alternativa místo dynamic_cast #include <typeinfo> class Base { public: virtual ~Base(); /* alespoň jedna virtuální funkce */ }; class X : public Base { /* ... */ class Y : public Base { /* ... */ Base * p = /* ... */; if ( typeid( * p) == typeid( X) ) { X * xp = static_cast< X *>( p); /* ... */ } if ( typeid( * p) == typeid( Y) ) { Y * yp = static_cast< Y *>( p); /* ... */ }

263 Alternativa místo dynamic_cast
RTTI Typické použití Alternativa místo dynamic_cast Pozor: rovnost typeid nereflektuje dědičnost Zatímco dynamic_cast ano #include <typeinfo> class Base { public: virtual ~Base(); /* alespoň jedna virtuální funkce */ }; class X : public Base { /* ... */ class Z : public X { /* ... */ Base * p = new Z; if ( typeid( * p) == typeid( X) ) // neplatí ! { X * xp = static_cast< X *>( p); /* ... */ } if ( typeid( * p) == typeid( Z) ) // platí { Z * zp = static_cast< Z *>( p); /* ... */ }

264 RTTI obvykle něco stojí
Každá třída s virtuálními funkcemi někde musí mít své type_info a odkaz na něj ve své tabulce virtuálních funkcí To platí i pro instance šablon, jejichž jména bývají dlouhá RTTI se příliš nevyužívá Je slabší než dynamic_cast Je nové Mezitím se programátoři naučili dělat si RTTI vlastními prostředky Většina překladačů zapíná RTTI na vyžádání Někdy se takový přepínač vztahuje i na dynamic_cast U některých překladačů souvisí s RTTI i zpracování výjimek

265 Visitor Visitor Abstraktní třída Příklad
Určena k předání jiné funkci, která vybere a zavolá vhodnou virtuální metodu podle skutečného typu nějakého objektu Často používána ve spojení s procházením polymorfní datovou strukturou Příklad Datová struktura Scene obsahuje objekty tří druhů (Ellipse, Rectangle, Line) Metoda doitforall projde všechny prvky scény a aplikuje na každý z nich vhodnou virtuální metodu zadaného Visitoru class Ellipse; class Rectangle; class Line; class Visitor { public: virtual void visitEllipse( Ellipse *)=0; virtual void visitRectangle( Rectangle *)=0; virtual void visitLine( Line *)=0; virtual ~Visitor() {} }; class Scene { void doitforall( Visitor &); /* ... */

266 Visitor Visitor - použití class Ellipse; class Rectangle; class Line;
class PrintVisitor : public Visitor { public: PrintVisitor( Printer * p) : p_( p) {} virtual void visitEllipse( Ellipse *); virtual void visitRectangle( Rectangle *); virtual void visitLine( Line *); private: Printer * p_; }; Scene s; Printer * p; s.doitforall( PrintVisitor( p)); class Ellipse; class Rectangle; class Line; class Visitor { public: virtual void visitEllipse( Ellipse *)=0; virtual void visitRectangle( Rectangle *)=0; virtual void visitLine( Line *)=0; virtual ~Visitor() {} }; class Scene { void doitforall( Visitor &); /* ... */

267 Visitor Visitor Visitor umožňuje definovat tolik variant nějaké akce, kolik je druhů procházených objektů Pomocí visitoru je možné definovat další akce Příklad Vyskytne-li se potřeba výstupu na plotter, stačí definovat další potomek visitoru class Ellipse; class Rectangle; class Line; class Visitor { public: virtual void visitEllipse( Ellipse *)=0; virtual void visitRectangle( Rectangle *)=0; virtual void visitLine( Line *)=0; virtual ~Visitor() {} }; class Scene { void doitforall( Visitor &); /* ... */

268 Visitor Srovnání Virtuální funkce
Podobného efektu jako u visitoru lze dosáhnout virtuální funkcí deklarovanou ve společném předku objektů s odlišnými těly pro každý druh objektu Sada virtuálních funkcí ve společném předku není rozšiřitelná class AbstractObject { public: virtual void print( Printer *)=0; virtual void display( Display *)=0; virtual ~AbstractObject() {} }; class Ellipse : public AbstractObject { /* ... */ }; class Ellipse; class Rectangle; class Line; class Visitor { public: virtual void visitEllipse( Ellipse *)=0; virtual void visitRectangle( Rectangle *)=0; virtual void visitLine( Line *)=0; virtual ~Visitor() {} }; class Scene { void doitforall( Visitor &); /* ... */

269 Visitor Visitor Výhoda: Přidávání další akce nevyžaduje změnu společného rozhraní Nevýhoda: Přidání dalšího druhu objektu si vynutí změnu visitoru (přidání virtuální funkce) Poučení V případě stabilní množiny akcí na nestabilní množině druhů objektů použijte virtuální funkce V případě nestabilní množiny akcí na stabilní množině druhů objektů použijte visitor V případě nestabilní množiny akcí i druhů ... ? class Ellipse; class Rectangle; class Line; class Visitor { public: virtual void visitEllipse( Ellipse *)=0; virtual void visitRectangle( Rectangle *)=0; virtual void visitLine( Line *)=0; virtual ~Visitor() {} }; class Scene { void doitforall( Visitor &); /* ... */

270 Visitor Visitor - implementace class Ellipse; class Rectangle;
class AbstractObject { public: virtual void apply( Visitor &) = 0; virtual ~AbstractObject() {} }; class Ellipse { protected: virtual void apply( Visitor & v) { v.visitEllipse( v); } /* ... */ void Scene::doitforall( Visitor & v) { for ( my_vector_::iterator it = elements_.begin(); it != elements_.end(); ++it ) (*it)->apply( v); } class Ellipse; class Rectangle; class Line; class Visitor { public: virtual void visitEllipse( Ellipse *)=0; virtual void visitRectangle( Rectangle *)=0; virtual void visitLine( Line *)=0; virtual ~Visitor() {} }; class Scene { void doitforall( Visitor &); /* ... */ private: typedef vector< AbstractObject *> my_vector_; my_vector_ elements_;

271 Design Patterns Návrhové vzory

272 Design patterns Design patterns
Abstract factory Adapter Bridge Builder Chain of responsibility Command Composite Decorator Facade Factory method Flyweight Generation gap Interpreter Iterator Mediator Memento Multicast Observer Prototype Proxy Singleton State Strategy Template method Typed message Visitor a mnoho dalších... Gamma, Helm, Johnson, Vlissides: Design Patterns, 1995

273 Design patterns Design patterns - proč? Vše již bylo vynalezeno - nevynalézat kolo Moudrost věků - neopakovat chyby Výuka a poučení - jak to dělají jiní Zpřístupnění objektového programování "méně kreativním" programátorům Společná terminologie Ekvivalenty v různých jazycích (C#, Java, CORBA, ...)

274 Původním záměrem je jejich používání při návrhu
Design patterns Design patterns Původním záměrem je jejich používání při návrhu Je to návod, případně vzorová implementace Není to knihovna ani polotovar k úpravě Klasické návrhové vzory nevyužívají C++ šablony Ani "template function" není šablona Některé (ne všechny) vzory lze univerzálně implementovat jako šablony Zkušenost je obsažena především v pravidlech určujících, kdy je vhodné daný vzor použít V originále: "Intent" + "Applicability"

275 Design patterns Struktura Adapter Bridge Composite Decorator Facade
Objekt upravující rozhraní objektu Bridge Oddělení implementace objektu od jeho rozhraní Composite Rozhraní umožňující jednotný přístup k celkům i částem určité hierarchické struktury Decorator Třída podílející se na implementaci objektu Facade Objekt reprezentující část rozhraní objektu Flyweight Objekt snižující objem dat pomocí sdílení Proxy Objekt zpřístupňující jiný objekt se stejným rozhraním

276 Design patterns Vytváření objektů Abstract factory Builder
Vytváření rodin objektů bez určení jejich konkrétních tříd Builder Vytváření složených objektů Factory method Vytváření objektu bez určení konkrétní třídy Prototype Objekt se schopností vytvářet vlastní kopie Singleton Třída s jedinou instancí a globálním přístupem k ní

277 Design patterns Stav a synchronizace Iterator Mediator Memento
Objekt umožňující procházení datovou strukturou Mediator Objekt evidující vztahy jiných objektů Memento Záznam vnitřního stavu objektu určený k pozdějšímu obnovení Observer Synchronizace objektu a jeho pozorovatele State Oddělení funkcí a stavu objektu

278 Design patterns Zprávy a příkazy Command Interpreter Multicast
Zhmotněný pokyn k volání funkce/metody s parametry Interpreter Převodník zpráv Multicast Předávání zpráv dynamicky registrovaným příjemcům Typed message Typově bezpečné předávání zpráv různého druhu Visitor Rozšiřitelná náhrada virtuální funkce

279 Design patterns Implementace funkcí Chain of responsibility
Soustava funkcí podílejících se na implementaci jedné akce Generation gap Úpravy chování neupravitelné třídy Strategy Objekt určený ke specializaci univerzálního postupu Template method Univerzální algoritmus s modifikovatelnými částmi

280 Šablony Hlubší pohled

281 ***Nepoužité slajdy***

282 Teoretický pohled na šablony
Triky 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;

283 Teoretický pohled na šablony
Triky 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;

284 Teoretický pohled na šablony
Triky 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;

285 Teoretický pohled na šablony
Triky 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;

286 Teoretický pohled na šablony
Triky 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;

287 Teoretický pohled na šablony
Triky 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;

288 Teoretický pohled na šablony
Výpočty při kompilaci Data: celá čísla typy Funkcionální programování: Funkce bez vedlejších efektů Neexistuje přiřazovací příkaz "Proměnné" se nemění Rekurze Odlišného chování funkcí pro různé hodnoty parametrů se dociluje definováním několika těl funkcí (tj. šablon) Výpočty za běhu Data: celá i reálná čísla, struktury ukazatelé Procedurální programování: Procedury s vedlejšími efekty Destruktivní přiřazení Proměnné se mění Podmínky, cykly, rekurze Odlišného chování procedur pro různé hodnoty parametrů se dociluje podmínkami uvnitř

289 Exception-safe programming
Příklad: StringStack::pop Zásobník prvků typu String Implementován polem Slabě bezpečná implementace: Při výjimce v konstruktoru proměnné top se nestane nic Při výjimce v new korektně zanikne proměnná top a zůstane zachován původní stav Funkce swap ani operator delete nezpůsobují výjimky class StringStack { public: // ...  private: String * p_; int n_; }; String StringStack::pop() { if ( ! n_ ) return String(); String top = p_[ n_-1]; String * p2 = new String[ n_-1]; for ( int i = 0; i < n_-1; ++i) swap( p2[ i], p_[ i]); swap( p_, p2); --n_; delete p2; return top; }

290 Exception-safe programming
Příklad: StringStack::pop Zásobník prvků typu String Implementován polem 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 class StringStack { public: // ...  private: String * p_; int n_; }; String StringStack::pop() { if ( ! n_ ) return String(); String top = p_[ n_-1]; String * p2 = new String[ n_-1]; for ( int i = 0; i < n_-1; ++i) swap( p2[ i], p_[ i]); swap( p_, p2); --n_; delete p2; return top; }

291 Exception-safe programming
Příklad: StringStack::pop Zásobník prvků typu String Implementován polem 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 Tuto výjimku lze ošetřit try-blokem okolo příkazu return Uvést zásobník do původního stavu Ale: co když se uvedení do původního stavu nezdaří? String StringStack::pop() { if ( ! n_ ) return String(); String top = p_[ n_-1]; String * p2 = new String[ n_-1]; for ( int i = 0; i < n_-1; ++i) swap( p2[ i], p_[ i]); swap( p_, p2); --n_; delete p2; try { return top; } catch ( ...) push( top); throw;

292 Exception-safe programming
Příklad: StringStack::pop Zásobník prvků typu String Implementován polem Nefunkč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 Tuto výjimku lze ošetřit try-blokem okolo příkazu return Dokážeme udělat obnovení původního stavu bez nebezpečí výjimky Ale: jak zrušíme proměnnou p2, když k výjimce nedojde? String StringStack::pop() { if ( ! n_ ) return String(); String top = p_[ n_-1]; String * p2 = new String[ n_-1]; for ( int i = 0; i < n_-1; ++i) swap( p2[ i], p_[ i]); swap( p_, p2); --n_; // tady bylo delete p2; try { return top; } catch ( ...) ++n_; delete p2; throw;

293 Oblasti platnosti identifikátorů
Modul/namespace: Funkce, proměnné, typy a výčtové konstanty Třída: Metody, položky (příp. typy a výčtové konstanty) Funkce (blok): Parametry a lokální proměnné (příp. typy a výčtové konstanty) Pořadí vyhledávání identifikátoru Nejprve ve funkci: Od nejvnitřnějšího k hlavnímu bloku Poté v třídách (uvnitř metod): Od potomků k předkům Nakonec v modulu resp. aktuálním namespace S uplatněním direktiv using

294 Vztah vyhledávání identifikátoru a kontroly přístupu
Ochrana přístupu Vztah vyhledávání identifikátoru a kontroly přístupu V definovaném pořadí se najde první oblast platnosti, ve které se identifikátor vyskytuje Namespace slité direktivami using se považují za jednu oblast platnosti Jedná-li se o přetížený identifikátor funkce, vybere se v nalezené oblasti odpovídající varianta Neuplatní se tudíž jiné varianty v jiných oblastech, které by jinak byly viditelné Aplikuje se mechanismus ochrany přístupových práv. Pokud tedy nejlépe padnoucí varianta není přístupná, kompilátor ohlásí chybu bez ohledu na případnou existenci jiné aplikovatelné a přístupné varianty

295 Preprocesor a překlad stdio.h hello.o conio.h hello.i hello.c Code ...
push call ret Import _printf _getch Export _main Data ‘H’,’e’,’l’,’l’, ’o’,10,0 hello.o hello.i /*...*/ int printf( const char *, ...); int getch(); int putch(); int main( int argc, char * * argv) { printf( “Hello\n”); getch(); return 0; } stdio.h hello.c #include <stdio.h> #include <conio.h> conio.h

296 Spojování modulů cstart.o printer.o hello.o creader.o exit.o Code ...
push call ret Import _printf _getch Export _main Data ‘H’,’e’,’l’,’l’, ’o’,10,0 hello.o _exit entry point cstart.o printer.o creader.o syscall ... exit.o

297 Spustitelný program a spuštěný proces
... push call ret ‘H’,’e’,’l’,’l’, ’o’,10,0 hello entry point syscall ... Code Data Heap Stack Adresový prostor IP R0 R1 FFFFFFFF

298 ***** C++ ***** Identifikátory a kontexty Zapouzdření Dědičnost
Přetěžování funkcí Předefinování operátorů Objekty Konstruktory a destruktory Pozdní vazba (late binding) Virtuální funkce Neviditelné konverze Drobná vylepšení Striktnější typová kontrola oproti C new a delete Komentáře Šablony Zpracování výjimek Knihovny Streams

299 Virtuální funkce – Specializace datové struktury
class HashTable { public: void add( const void * key, int keylen, const void * data, int datalen); protected: virtual long hash( int keylen); private: SmartArray _tbl; }; class StringTable : public HashTable { public: void add( const char * key, const void * data, int datalen); protected: virtual long hash( const void * key, int keylen); };

300 Virtuální funkce – Užití datové struktury
class Catalog : private StringTable { public: void add( const char * isbn, const char * author, const char * title); private: virtual long hash( const void * key, int keylen); }; class Catalog { public: void add( const char * isbn, const char * author, const char * title); private: StringTable _t; };

301 Virtuální funkce - Řešení v C-stylu
Abstraktní třída struct File { int handle; int (* readf)( File * p); void (* writef)( File * p, int x); }; int read( File * p) { return p->readf( p); }    Specializace int bread( File * p) { /* ... */ } File * bopen() { File * p = new File; p->handle = /*...*/; p->readf = bread; p->writef = bwrite; return p; }   

302 Virtuální funkce - Řešení v C-stylu
Jiná specializace struct TFile { File f; int info; };    int tread( File * p) { TFile * tp = (TFile *)p; /* ... */ } File * topen() { TFile * tp = new TFile; tp->f.handle = /* ... */; tp->f.readf = tread; tp->f.writef = twrite; tp->info = /* ... */ return &tp->f;

303 Virtuální funkce - Řešení v C-stylu
Lepší implementace struct VTFile { int (* readf)( File * p); void (* writef)( File * p, int x); }; struct File { int handle; const VTFile * vt; const VTFile VTBFile = { bread, bwrite }; const VTFile VTTFile = { tread, twrite };

304 Virtuální funkce - Řešení v C++
Abstraktní třída class File { int handle; public: virtual int readf() = 0; virtual void writef( int x) = 0; }; int read( File * p) { return p->readf(); } Specializace class BFile : public File { virtual int readf(); virtual void writef( int x); }; int BFile::readf() { /* ... */ } File * bopen() { BFile * p = new BFile; p->handle = /* ... */; return p;

305 Virtuální funkce - Řešení v C++
Jiná specializace class TFile : public File { int info; virtual int readf(); virtual void writef( int x); }; int TFile::readf() { /* ... */ } File * topen() { TFile * p = new TFile; p->handle = /* ... */; p->info = /* ... */; return p; }

306 Statické a dynamické volání virtuálních metod
void x() { A a; /* A::A */ B b; /* B::B */ A & raa = a; A & rab = b; B & rbb = b; A * paa = &a; A * pab = &b; B * pbb = &b; a.f(); /* A::f (c) */ b.f(); /* B::f (c) */ raa.f(); /* A::f (r) */ rab.f(); /* B::f (r) */ rbb.f(); /* B::f (r) */ paa->f(); /* A::f (r) */ pab->f(); /* B::f (r) */ pbb->f(); /* B::f (r) */ b.A::f(); /* A::f (c) */ b.B::f(); /* B::f (c) */ paa->A::f(); /* A::f (c) */ pab->A::f(); /* A::f (c) */ pbb->A::f(); /* A::f (c) */ pbb->B::f(); /* B::f (c) */ raa.A::f(); /* A::f (c) */ rab.A::f(); /* A::f (c) */ rbb.A::f(); /* A::f (c) */ rbb.B::f(); /* B::f (c) */ }

307 String č. 5 - operator [ ] class String { friend class StringPos;
public: const char & operator[]( int pos) const { /* test pos */ return _body->buffer()[ _pos]; }; StringPos operator[]( int pos) return StringPos( this, pos); /* ... */

308 String č. 5 - operator [ ] class StringPos { public:
StringPos( String * ref, int pos) { _ref = ref; _pos = pos; }; operator char() const return _ref->_body->buffer()[ _pos]; char operator=( char x) const _ref->make_private(); _ref->_body->buffer()[ _pos] = x; return x; } private: String * _ref; int _pos;

309 String č. 6 - Držadlo class StringBody; class String { public:
String( const String &); const String & operator=( const String &); ~String(); String( const char *); const char * c_str() const; String cat( const String &) const; String cat( const char *) const; String catr( const char *) const; private: String( StringBody *); StringBody * _body; };

310 String č. 6 - Abstraktní tělo
class StringBody { public:  virtual ~StringBody() {} void inc() { _count++; }; void dec(); virtual bool private() { return _count == 1; } virtual StringBody * freeze() = 0; virtual StringBody * make_private() = 0; virtual int length() = 0; virtual void copy( char * dst) = 0; virtual const char * c_str() = 0; virtual char read_at( int i) = 0; virtual void write_at( int i, char ch) = 0; protected: StringBody() { _count = 0; } private: int _count; };

311 String č. 6 - Jednoduché tělo
class StringBodySimple : public StringBody { public: static StringBodySimple * create( const char * s); protected: StringBodySimple( int n); virtual ~StringBodySimple() { delete _str; } virtual StringBody * freeze() { return this; } virtual StringBody * make_private(); virtual int length() { return _len; } virtual void copy( char * dst) { memcpy( dst, _str, _len); } virtual const char * c_str() { return _str; } virtual char read_at( int i) { /* test! */ return _str[ i]; } virtual void write_at( int i, char ch) { /* test! */ _str[ i] = ch; } private: char * _str; int _len; };

312 String č. 6 - Jednoduché tělo
StringBodySimple * StringBodySimple::create( const char * s) { StringBodySimple * p = new StringBodySimple( strlen( s)); strcpy( p->_str, s); return p; } StringBodySimple::StringBodySimple( int n) { _str = new char[ n + 1]; _len = n; StringBody * StringBodySimple::make_private() { if ( private() ) return this; StringBodySimple * p = new StringBodySimple( _len); strcpy( p->_str, _str);

313 String č. 6 - Složené tělo class StringBodyConcatenate : public StringBody { public: static StringBodyConcatenate * create( StringBody * a, StringBody * b); protected: StringBodyConcatenate( StringBody * a, StringBody * b); virtual ~StringBodyConcatenate(); virtual StringBody * freeze(); virtual StringBody * make_private(); virtual int length() { return _a->length() + _b->length(); } virtual void copy( char * dst); virtual const char * c_str() { /* error */ return 0; } virtual char read_at( int i); virtual void write_at( int i, char ch); private: StringBody * _a, * _b; };

314 String č. 6 - Složené tělo StringBodyConcatenate * StringBodyConcatenate::create( StringBody * a, StringBody * b) { return new StringBodyConcatenate( a, b); } StringBodyConcatenate::StringBodyConcatenate( { _a = a; _a->inc(); _b = b; _b->inc(); StringBodyConcatenate::~StringBodyConcatenate() { _a->dec(); _b->dec();

315 String č. 6 - Složené tělo StringBody * StringBodyConcatenate::freeze() { StringBodySimple * p = new StringBodySimple( length()); copy( p->_str); return p; } StringBody * StringBodyConcatenate::make_private() { if ( private() ) return this; return new StringBodyConcatenate( _a->make_private(), _b->make_private());

316 String č. 6 - Složené tělo int StringBodyConcatenate::length()
{ return _a->length() + _b->length(); } void StringBodyConcatenate::copy( char * dst) { _a->copy( dst); _b->copy( dst + _a->length()); char StringBodyConcatenate::read_at( int i) { return i < _a->length() ? _a->read_at( i) : _b->read_at( i - _a->length()); void StringBodyConcatenate::write_at( int i, char ch) { i < _a->length() ? _a->write_at( i, ch) : _b->write_at( i - _a->length(), ch);

317 Problém: Vzájemné volání dec() a destruktoru je rekurzivní
void StringBody::dec() { if ( ! --count ) delete this; }; StringBodyConcatenate::~StringBodyConcatenate() { _a->dec(); _b->dec(); } Hloubka vnoření rekurze může být neúnosně velká { String x; for ( int i=0; i < ; i++ ) x = x + "a";

318 Nahradit rekurzi pomocnou strukturou a cyklem
Řešení 1 Nahradit rekurzi pomocnou strukturou a cyklem typedef std::stack< StringBody *> KillStack; inline void StringBody::dec() { if ( ! --count ) killMe(); } void StringBody::killMe() { KillStack toBeKilled; StringBody * b = this; for (;;) b->prepareToDie( toBeKilled); delete b; if ( toBeKilled.empty() ) break; b = toBeKilled.top(); toBeKilled.pop();

319 Řešení 1 StringBodyConcatenate::prepareToDie( KillStack & tbk)
{ _a->dec( tbk); _a = 0; _b->dec( tbk); _b = 0; } void StringBody::dec( KillStack & tbk) { if ( ! --count ) tbk.push( this); };

320 Řešení 1 Pomocná struktura Nevýhody:
typedef std::stack< StringBody *> KillStack; Nevýhody: Časová náročnost operací struktury není zanedbatelná K rozsáhlému mazání může dojít v rámci téměř každé operace Práce se strukturou vyžaduje alokaci Deadlock: Ke zrušení dat je třeba naalokovat další Exception-safety: Destruktor nemá vyvolávat výjimky

321 Řešení 2 Jiné řešení Ekvivalentní úloha
V okamžiku zrušení držadla String lze určit množinu uzlů typu StringBodyConcatenate, které jsou dosažitelné pouze z tohoto držadla Tato množina je binární strom Ekvivalentní úloha Zrušit binární strom bez rekurze a dalších datových struktur Pokud možno v lineárním čase Tato úloha je řešitelná postupným přeskupováním stromu a umazáváním uzlů s méně než dvěma syny Stále přetrvává problém s občasnými výskyty časově náročných operací Nevhodné pro real-time operace apod.

322 Problém: Implementace mnoha funkcí je rekurzivní
void StringBodyConcatenate::copy( char * dst) const { _a->copy( dst); _b->copy( dst + _a->length()); }

323 Pokus o náhradu rekurze opakováním
Řešení ? Pokus o náhradu rekurze opakováním Funkce copySomething okopíruje tolik, kolik dokáže class StringBody { /* ... */ virtual int copySomething( char * dst, int offset) const = 0; }; Funkce copy ji volá tak dlouho, dokud něco vrací void String::copy( char * dst) const { int offset, len; offset = 0; while ( (len = _body->copySomething( dst + offset, offset)) > 0 ) offset += len; }

324 Řešení ? Problém: Implementace funkce copySomething je stále rekurzivní int StringBodyConcatenate::copySomething( char * dst, int offset) const { if ( offset < _a->length() ) return _a->copySomething( dst, offset); else return _b->copySomething( dst + _a->length(), offset - _a->length()); }

325 Řešení ? Tail-recursion Rekurzivní volání na konci funkce
void F( T p) { H( p); if ( C( p) ) F( G( p)); } Obecně lze nahradit cyklem while ( C( p) ) p = G( p);

326 Řešení ? Tail-recursion Problém: Funkce copySomething je virtuální
Řešení: Pomocná funkce redirect Vrací odkaz na uzel, který je zodpovědný za danou pozici class StringBody { /* ... */ virtual const StringBody * redirect( int offset) const { return 0; // no redirection } }; const StringBody * StringBodyConcatenate::redirect( int offset) const if ( offset < _a->length() ) return _a; else return _b;

327 Řešení ? Tail-recursion Problém: Funkce copySomething je virtuální
Řešení: Pomocná funkce redirect void String::copy( char * dst) const { int offset, len; const String * p, * p2; offset = 0; do { p = _body; while ( !! (p2 = p->redirect( offset)) ) p = p2; len = p->copySomething( dst + offset, offset); offset += len; } while ( len > 0 ); }

328 Řešení ? Pyrrhovo vítězství: Rekurze je odstraněna
Algoritmus má nyní kvadratickou složitost

329 Závěr Srovnání možností Srovnání požadavků
Zrušení stromu lze provést bez rekurze a pomocných struktur v lineárním čase Průchod stromem, který nemá být zničen, takto udělat nelze Ledaže by ve stromě byly zpětné odkazy Naše struktura ovšem není strom, ale DAG Srovnání požadavků Rušení stromu musí být bezpečná, vždy proveditelná akce Protože se volá z destruktorů Běžné operace na stromě takto bezpečné být nemusejí Mohou tedy využívat pomocné struktury Je nutné definovat způsob indikace chyby

330 STL – vector template<class T, class A = allocator<T> > class vector { public: typedef A allocator_type; typedef A::size_type size_type; typedef A::difference_type difference_type; typedef A::reference reference; typedef A::const_reference const_reference; typedef A::value_type value_type; typedef /*...*/ iterator; typedef /*...*/ const_iterator; typedef /*...*/ reverse_iterator; typedef /*...*/ const_reverse_iterator; explicit vector(const A& al = A()); explicit vector(size_type n, const T& v = T(), const A& al = A()); vector(const vector& x); vector(const_iterator first, const_iterator last, const A& al = A()); /* ... */

331 STL – vector /* template<class T, class A = allocator<T> > class vector { ... */ iterator begin(); const_iterator begin() const; iterator end(); iterator end() const; reverse_iterator rbegin(); const_reverse_iterator rbegin() const; reverse_iterator rend(); const_reverse_iterator rend() const; size_type size() const; bool empty() const; reference at(size_type pos); const_reference at(size_type pos) const; reference operator[](size_type pos); const_reference operator[](size_type pos) const; /* ... */

332 STL – vector /* template<class T, class A = allocator<T> > class vector { ... */ reference front(); const_reference front() const; reference back(); const_reference back() const; void push_back(const T& x); void pop_back(); void assign(const_iterator first, const_iterator last); void assign(size_type n, const T& x = T()); iterator insert(iterator it, const T& x = T()); void insert(iterator it, size_type n, const T& x); void insert(iterator it, const_iterator first, const_iterator last); iterator erase(iterator it); iterator erase(iterator first, iterator last); void clear(); void swap(vector x); /* ... */

333 STL – vector /* template<class T, class A = allocator<T> > class vector { ... */ protected: A allocator; };

334 STL – vector – naivní implementace – hlavička
template<class T, int delta> class oriented_iterator; template<class T, int delta> class const_oriented_iterator; template<class T> class vector { public: typedef unsigned int size_type; typedef int difference_type; typedef T & reference; typedef const T & const_reference; typedef T value_type; typedef oriented_iterator< T, 1> iterator; typedef const_oriented_iterator< T, 1> const_iterator; typedef oriented_iterator< T, -1> reverse_iterator; typedef const_oriented_iterator< T, -1> const_reverse_iterator; /* ... */ private: size_type _n; T * _p; };

335 STL – vector – naivní implementace – přístup
template< class T> reference vector< T>::at(size_type pos) { return pos >= 0 && pos < n ? _p[ pos] : _dummy(); } template< class T> const_reference vector< T>::at(size_type pos) const template< class T> reference vector< T>::operator[](size_type pos) { return at( pos); } template< class T> const_reference vector< T>::operator[](size_type pos) const template< class T> reference vector< T>::dummy() const { return *(T*)0; }

336 STL – vector – naivní implementace - konstrukce
template< class T> vector< T>::vector() { _n = 0; _p = 0; } template< class T> vector< T>::vector( size_type n, const T & v) { _n = n; _p = new T[ _n]; /* nevolat konstruktory !!! */ for ( int i = 0; i < _n; i++) { /* zavolat konstruktory !!! */ /* _p[ i].T( v); */ } template< class T> vector< T>::vector( const vector & x) _n = x._n; /* _p[ i].T( x._p[ i]); */

337 STL – vector – naivní implementace – push/pop
template< class T> void vector< T>::push_back(const T& x) { T * p = _p; _n = _n + 1; _p = new T[ _n + 1]; /* nevolat konstruktory !!! */ for ( int i = 0; i < _n - 1; i++) { /* zavolat konstruktory !!! */ /* _p[ i].T( p[ i]); */ } template< class T> void vector< T>::pop_back() /* ... */

338 STL – vector – vylepšená implementace - konstrukce
void * operator new( size_t s, void * p) { return p; } template< class T> vector< T>::vector( size_type n, const T & v) { _n = n; _p = reinterpret_cast< T *>( new char[ _n * sizeof( T)]); for ( int i = 0; i < _n; i++) new( _p[ i]) T( v); } template< class T> vector< T>::~vector() _p[ i]->T::~T(); delete[] reinterpret_cast< char *>( _p);

339 STL – vector – naivní implementace – iterátory
template< class T, int delta> class oriented_iterator { public: T & operator *() const { return _i < _v->_n ? *( T *)0 : _v->_p[ _i]; } const oriented_iterator< T, delta> & operator ++() /* prefix */ { if ( _i < _v->_n ) _i++; return * this; oriented_iterator< T, delta> operator ++( int) /* postfix */ { oriented_iterator< T, delta> old = * this; operator ++(); return old; bool operator ==( const oriented_iterator< T, delta> & b) const { return _i == _b->_i; } private: friend class vector< T>; oriented_iterator( vector< T> * v, int i) { _v = v; _i = i; } vector< T> * _v; int _i; };

340 STL – vector – naivní implementace – iterátory
template< class T> iterator vector< T>::begin() { return iterator( this, 0); } template< class T> const_iterator vector< T>::begin() const { return const_iterator( this, 0); } template< class T> iterator vector< T>::end() { return iterator( this, _n); } template< class T> iterator vector< T>::end() const { return const_iterator( this, _n); }

341 mohou být definovány jako globální nebo uvnitř třídy
Operátory new a delete Operátory new a delete mohou být definovány jako globální nebo uvnitř třídy na rozdíl od ostatních operátorů jsou i uvnitř třídy považovány za statické funkce Operátory deklarované uvnitř třídy jsou použity při alokaci resp. dealokaci objektů této třídy (a jejích potomků) Globální operátory jsou používány pro třídy bez těchto operátorů a pro datové typy, které nejsou třídami, včetně polí tříd void * operator new( size_t s); void operator delete( void * p);

342 Operátor new může mít přídavné parametry a může tak být přetížen
Operátory new a delete Operátor new může mít přídavné parametry a může tak být přetížen Typické použití void * operator new( size_t s, void * p) { return p; } void call_constructor( X * p, int param) { new( p) X( param); Vztah operátoru new a konstruktoru X * p = new X(a,b,c); (nekorektní) ekvivalent X * p = (X*)X::operator new(sizeof(X)); if (p) p->X::X(a,b,c); // zakázáno Vztah operátoru delete a destruktoru delete p; ekvivalent if (p) p->X::~X(); // povoleno X::operator delete((void*)p);

343 Ladění programů v C++

344 Nežádoucí chování programu
Předčasné ukončení programu Nedobrovolné (zásah CPU+OS) Ochrana paměti Neplatná/nepovolená instrukce Hodnotová chyba (dělení nulou...) Přetečení zásobníku (rekurze) Dobrovolné Ochranné mechanismy standardních knihoven Neobsloužená C++ výjimka assert apod. Neukončení programu Nekonečná smyčka/deadlock Nesprávný výstup programu Korektní program počítá něco jiného Nekorektní program náhodou doběhl do konce

345 Nedefinované chování (undefined behavior)
Pohled normy C++ Nedefinované chování (undefined behavior) Vyvolání operace s parametry, pro něž norma nedefinuje chování definuje chování jako nedefinované Norma neříká, jak má být nedefinované chování ošetřeno Detekce při překladu Ukončení při běhu Dokumentované předvídatelné chování Ignorování problému s nepředvídatelnými důsledky

346 Nedefinované chování (undefined behavior)
Pohled normy C++ Nedefinované chování (undefined behavior) Normou nepovolená situace Unspecified behavior Normou povolená situace, ve které norma nedefinuje některé aspekty chování Implementation-defined behavior Situace, ve které norma vyžaduje, aby konkrétní implementace zvolila a dokumentovala chování Včetně možnosti zákazu Locale-specific behavior Chování závislé na místním prostředí (vč. nastavení OS)

347 Nejčastější nedefinované chování
Pohled normy C++ Nejčastější nedefinované chování Dereference nulového ukazatele Nekorektní přístup k paměťovému místu Paměťové místo neobsahuje žádný korektní objekt Paměťové místo obsahuje objekt jiného typu Paměťové místo obsahuje korektní objekt správného typu, ale pouze shodou okolností Nekorektní manipulace s iterátory Překročení intervalu [begin(), end()] Manipulace s invalidovaným iterátorem Nekorektní přetypování ukazatele Skutečný typ objektu je jiný než předpokládaný Nekorektní nepřímé volání Volání virtuální funkce na špatném objektu/ve špatný okamžik Neinicializovaný/nulový ukazatel na funkci

348 Typické důsledky nedefinovaného chování
Dereference nulového ukazatele Ukončení operačním systémem (ochrana paměti) Nekorektní přístup k paměťovému místu - čtení Ukončení operačním systémem Načtení nežádoucí hodnoty Nekorektní přístup k paměťovému místu - zápis Ohlášení ochrannými prostředky knihovny - později Poškození režijních dat (alokace, tabulky virtuálních funkcí,...) Poškození hodnoty cizího paměťového místa Nekorektní manipulace s iterátory Detekováno ochrannými prostředky knihovny Nedetekováno vůbec Nekorektní přetypování ukazatele Nekorektní nepřímé volání Ukončení OS (neplatná instrukce/ochrana paměti)

349 Nejčastější příčiny nedefinovaného chování
Dereference nulového ukazatele Zřejmé - toto je žádoucí chování Nekorektní přístup k paměťovému místu - malá odchylka Přetečení/podtečení mezí pole Nekorektní přístup k paměťovému místu - velká odchylka Neinicializovaný ukazatel/index/iterátor Ukazatel/index/iterátor poškozený nesprávným čtením/zápisem Nekorektní přístup k paměťovému místu - pozdě Ponechání ukazatele na odalokovaná data Vracení reference na lokální proměnnou Nekorektní přístup k paměťovému místu - chybný typ Nesprávný static_cast - špatný odhad konkrétního typu Příliš odvážný reinterpret_cast union Nekorektní nepřímé volání Neinicializovaný/nulový ukazatel na funkci

350 Principy detekce nekorektního chování
Ochrana paměti stránkováním - granularita ~4KB Ochrana kódu proti zápisu (čtení) Z hlediska ladění málo významné (Ochrana konstant proti zápisu) Nevýznamné, týká se pouze řetězcových a reálných konstant Ochrana proti přístupu mimo data/heap Účinné pouze proti přístupům na nesmyslné adresy Ochrana proti provádění instrukcí mimo kód (DEP) Z hlediska ladění chyb bezvýznamné Nárazníkové zóny dynamicky alokovaných dat Detekce mírného přetečení dynamicky alokovaných polí Naplněny nepravděpodobnými daty Zvyšuje pravděpodobnost rychlé detekce při čtení Testovány na poškození při dealokaci bloku Detekce zápisů

351 Principy detekce nekorektního chování
Ochrana paměti stránkováním - granularita ~4KB Nárazníkové zóny dynamicky alokovaných dat Inicializační hodnoty dynamicky alokovaných dat Vyplnění nepravděpodobnou hodnotou Včasná detekce čtení neinicializovaných objektů Čištění při dealokaci Včasná detekce čtení odalokovaných objektů Kontrola heapu při ukončení programu Neodalokované bloky (není nekorektní, ale obvykle indikuje chybu) Poškození alokačních záznamů (detekce nesprávných zápisů)

352 Naučte se chybu vyvolávat opakovaně
Postup při ladění chyb Naučte se chybu vyvolávat opakovaně Nahraďte interaktivitu vstupem ze souboru Prostudujte pečlivě chybové hlášení Zastavte v debuggeru program v okamžiku chyby Nedobrovolné ukončení - debuggery umí samy Dobrovolné ukončení: breakpoint na vhodné místo uvnitř systému Zjistěte, která data jsou špatně Prostudujte pečlivě špatné hodnoty Zjistěte, jak se tam špatná data dostala Znovuspouštění programu Datové breakpointy

353 Ladění výkonu programu
Za 99% výkonu odpovídá 1% kódu Určete který Profilery Instrumentace Kód se doplní o instrukce zaznamenávající průchod Ovlivňuje čas Sampling Běh se periodicky přerušuje a zaznamenává se pozice (call stack) Řízeno časem nebo performance-counters Sbírá i jiné informace než čas Rychlost bývá ovlivňována mnoha faktory Úspešnost vyrovnávacích pamětí je nejdůležitější


Stáhnout ppt "Pokročilé programování v C++ (část B)"

Podobné prezentace


Reklamy Google