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

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

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

Podobné prezentace


Prezentace na téma: "Pokročilé programování v C++ (část B) David Bednárek ulita.ms.mff.cuni.cz."— 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 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 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.  Bjarne Stroustrup  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 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 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+( const Matrix &, const Matrix &);

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+( const Matrix &, const Matrix &);

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 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 { public: PrintVisitor( Printer * p) : p_( p) {} virtual void visitEllipse( Ellipse *); virtual void visitRectangle( Rectangle *); virtual void visitLine( Line *); private: Printer * p_; };

27 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 nodes_; multimap edges_; map 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 { public: 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 { public: 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; }; public: class NodeRef { public: /*... */ private: TreeNode * p; }; Tree() : root( 0) {} ~Tree() { /*... */ } NodeRef create_node( /*... */); void insert_node( NodeRef p); NodeRef find_node( /*... */ ); private: 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=( const AbstractObject&); };

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 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 struct type_traits; template<> struct type_traits { 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 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_tree;  Traits template – použití  Deklarace traits template struct type_traits;  Univerzální šablona template class Stack { /*... */ void push( typename type_traits::param_t x); };  Univerzální definice traits template struct type_traits { typedef const T & param_t; };  Explicitní specializace traits template<> struct type_traits { typedef char param_t; };

39 Polymorfismus Kompilační a běhový

40 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 Polymorfismus  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 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 void for_each( P & x) { for(... it =... ) x.f( it); // metoda } struct functor_add { void f(...) {... } }; for_each( functor_add()); template void for_each() { for(... it =... ) P::f( it); // statická funkce } struct policy_add { static void f(...) {... } }; for_each ();

44 Policy class vs. traits template void for_each( K & data) { for(... it =... ) P::f( it); } struct policy_add { static void f(...) {... } }; my_k data; for_each ( data); template struct for_each_traits; template void for_each( K & data) { for(... it =... ) for_each_traits ::f( it); } template<> struct for_each_traits { static void f(...) {... } }; my_k data; for_each( data);

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

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

47 Lambda (C++0x) template 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 T operator()( T x) const { return x; } }; extern functor_1 _1; template struct functor_const { functor_const( T c) : c_( c) {} T operator()( T x) const { return c_; } T c_; }; template functor_const constant( T c) { return functor_const ( c); } template struct functor_mul { functor_mul( F1 f1, F2 f2) : f1_( f1), f2_( f2) {} T operator()( T x) const { return c_; } F1 f1_; F2 f2_; }; template functor_mul operator+ ( F1 f1, F1 f2) { return functor_const ( 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 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

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 void f( iterator b, iterator e) {... = * b; // chyba ! for ( iterator i = e - 1; // chyba ! i >= b; -- i) // chyba ! {... }

55 Další standardní iterátory  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::copy( x.begin(), x.end(), std::ostream_iterator( o)); std::istream & i =...; std::copy( std::istream_iterator( i), std::istream_iterator(), std::back_inserter( a));

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 struct iterator_traits { typedef typename Iterator::difference_type difference_type; typedef typename Iterator::value_type value_type; typedef typename Iterator::pointer pointer; typedef typename Iterator::reference reference; typedef typename Iterator::iterator_category iterator_category; }; template struct iterator_traits { typedef ptrdiff_t difference_type; typedef T value_type; typedef T* pointer; typedef T& reference; typedef random_access_iterator_tag iterator_category; };

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  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 class X { X( const X &); }; template class X { X( const X &); }; Některé překladače připouštějí i tuto variantu template class X { X ( const X &); };

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 void X ::f( int a, int b) { /*... */ }  V kvalifikovaném jméně metody je nutné uvést patřičný seznam argumentů, tj. X ::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 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 ;

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 class X { typedef typename T::B U;// T::B je typ typename U::D p;// T::B::D je typ typename Y ::C q;// Y ::C je typ void f() { T::D(); }// T::D není typ } typename je nutné uvést před jmény typu ve tvaru A::B, kde A je závislé jméno Závislé jméno je jméno obsahující přímo či nepřímo parametr šablony

64 Š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 class X : public T { void f() { return this->a; } }

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

66 Šablony funkcí  Šablona funkce je generická funkce (globální nebo metoda) prefixovaná konstrukcí template  se stejnými druhy formálních parametrů šablony jako u šablon tříd template // parametry šablony int f( T * p, int q);// parametry funkce template // parametry šablony int g( T * p, vector q);// parametry funkce  Šablony funkcí lze volat dvěma způsoby  Explicitně f ( a, b)  Automaticky g( a, b) Překladač dopočte parametry šablony z typů parametrů funkce 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 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 T max( Array a) { /*... */ }  Příklad ze standardních knihoven: template 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 class Array { /* specializace pro pole typu bool */ };  Krajním případem parciální specializace je explicitní specializace  Explicitní specializace template<> class Array { /*... */ };  U šablon funkcí nahrazena obyčejnou funkcí  Explicitní instanciace  Překladač je možné donutit ke kompletní instanciaci šablony template class Array ;

69 Parciální specializace  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 C; // základní deklarace template class C { // specializace bool cmp( P *, Q *); }; template class C : public Z { // jin á specializace bool set( Z &); }; template class C { // základní definice X add( Y); };

70 Parciální specializace  Parciální specializace  Deklarovanou šablonu lze pro určité kombinace parametrů předefinovat jinak, než určuje její základní definice Parciální specializace může mít stejný, menší i větší počet formálních parametrů než základní definice, jejich hodnoty se odvozují ze skutečných parametrů šablony (kterých je vždy tolik, kolik určuje základní definice) template class C { /* specializace pro dvě pole stejné velikosti */ };

71 Parciální specializace  Parciální specializace  Deklarovanou šablonu lze pro určité kombinace parametrů předefinovat jinak, než určuje její základní definice Parciální specializace může mít stejný, menší i větší počet formálních parametrů než základní definice, jejich hodnoty se odvozují ze skutečných parametrů šablony (kterých je vždy tolik, kolik určuje základní definice) template class C { /* specializace pro dvě pole stejné velikosti */ };  Krajním případem parciální specializace je explicitní specializace  Explicitní specializace template<> class C { /*... */ };  Explicitní specializace šablony není šablona  Podléhá trochu jiným (jednodušším) pravidlům Překlad se neodkládá Těla metod se 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 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 může být jednodušší  Mírná změna rozhraní ve speciálních případech  Šablona je používána přímo z nešablonovaného kódu  Uživatel by měl být o specializaci informován  Příklad: vector 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 může být jednodušší  Mírná změna rozhraní ve speciálních případech  Šablona je používána přímo z nešablonovaného kódu  Uživatel by měl být o specializaci informován  Příklad: vector nedovoluje vytvořit ukazatel na jeden prvek  Modifikace chování jiné šablony  Specializovaná šablona je volána z jiného šablonovaného kódu  Autor volající šablony předpokládá, že ke specializaci dojde Někdy dokonce ani nedefinuje základní obsah volané šablony  Autor specializace tak upravuje chování volající šablony  Příklad: šablona basic_string volá šablonu char_traits, ve které je např. definována porovnávací funkce

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 volá šablonu char_traits, ve které je např. definována porovnávací funkce template struct char_traits; template class basic_string { /*... */ int compare( const basic_string & b) const { /*...*/ char_traits ::compare( /*... */) /*...*/ } }; template<> struct char_traits { /*... */ static int compare(const char* s1, const char* s2, size_t n) { return memcmp( s1, s2, n); } };

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 doplňuje informace o typu T, např. porovnávací funkci

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

78 Triky s šablonami  Porovnání typů s booleovským výstupem template struct Equal { enum { value = false }; }; template struct Equal { enum { value = true }; };  Equal ::value je konstantní výraz  Použití template class Test { enum { T1_is_int = Equal ::value}; enum { T1_is_long = Equal ::value}; /*... */ };

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

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

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

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

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

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

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

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

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

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 X { /*... */ void f(/*...*/); };  Nepublikovaný hlavičkový soubor XBody.h  Generická těla metod #include "X.h" template void X ::f(/*...*/) { /*...*/ }  Knihovní modul XBodyInt.cpp  Instanciace pro typ int #include "XBody.h" template X ;

89 Teoretický pohled na šablony  Šablona třídy je kompilátorem vyhodnocovaná funkce  f : T i  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 : T i  T  T je množina všech typů zkonstruovatelných v jazyce C  f : T i × K j  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 : T i  T  T je množina všech typů zkonstruovatelných v jazyce C  f : T i × K j  T  šablona s celočíselnými parametry  K je množina všech celočíselných konstant  f : T i × K j  T m × K n  výsledná třída může obsahovat typy a konstanty

92 Teoretický pohled na šablony  Šablona třídy je kompilátorem vyhodnocovaná funkce  f : T i × K j  T m × K n  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 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 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 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  Ve zpracování výjimky se poté pokračuje Uvnitř konstruktoru je možno výjimku zachytit speciálním try-blokem: X::X( /* formální parametry */) try : Y( /* parametry pro konstruktor součásti Y */) { /* vlastní tělo konstruktoru */ } catch ( /* parametr catch-bloku */ ) { /* ošetření výjimky v konstruktoru Y i ve vlastním těle */ }  Konstrukci objektu nelze dokončit Opuštění speciálního catch bloku znamená throw;

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 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 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  "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 String StringStack::pop() { if ( ! top_ ) throw StackEmpty(); std::auto_ptr 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 =...; f(...); data_ =...;  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(...); try { change_3(...); try { change_4(...); } catch(...) { rollback_change_3(...); } catch(...) { rollback_change_2(...); } catch(...) { 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 */ } try { stk.pop(); } catch (...) { /* chyba zkracování, proměnná a změněna, zásobník nedotčen */ }

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 class StringStack { public: /* A */ String & top(); void pop(); /* B */ void pop( String & out) { String & t = top(); swap( out, t); try { pop(); } catch (...) { swap( out, t); throw; } };

144 Exception specifications  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  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  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  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 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 ::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 ::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 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 ”  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 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 ::back(); const T & vector ::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 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 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á To nemusí jít kvůli ochraně přístupu  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 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 class XXX { 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 Virtuální funkce class Base { virtual void f() { /*... */ } }; class Derived : public Base { virtual void f() { /*... */ } };  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 { public: 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 Dědičnost  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  Nesprávné užití dědičnosti č. 1 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  Nesprávné užití dědičnosti č. 1 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  Nesprávné užití dědičnosti č. 2 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  Nesprávné užití dědičnosti č. 2 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  Nesprávné užití dědičnosti č. 2 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 template class Array { T p[ n]; T dummy; public: T & operator[]( int x) { return x d; a[ 3] = b[ 3]; a = b; // chyba !!! b = c; // OK, implicitní copy-constructor d[ 2][ 3] = 1;

171 Poučení – konverzní operátor  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é Complex::operator double() const; double operator+( double a, double b); // vestavěná operace double double::operator=( 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  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); // ale také Complex::operator double() const; double sin( double x);// Complex( double re, double im = 0.0); První varianta má přednost......ale když zapomenete #include...

173 C++

174 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 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  Nesprávné užití dědičnosti č. 1 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  Nesprávné užití dědičnosti č. 1 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  Nesprávné užití dědičnosti č. 1 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  Nesprávné užití dědičnosti č. 1 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  Nesprávné užití dědičnosti č. 2 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  Nesprávné užití dědičnosti č. 2 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  Nesprávné užití dědičnosti č. 2 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  Nesprávné užití dědičnosti č. 2 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 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 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; private: 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 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 objects_; };

210 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 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) { fill( b); } String::~String() { clean(); }

218 Poučení - konstruktory  Lepší řešení operátoru přiřazení: #include 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 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(); (_body = b._body)->inc(); } return * this; } String::~String() { _body->dec(); }

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 = _body->length() ) return 0; return _body->buffer()[ _pos]; }; /*... */ };

228 String č. 4 - operator [ ] - zápis class String { public: char & operator[]( int pos) { if ( 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 = _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(); return _body->buffer()[ _pos]; };

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(); return _body->buffer()[ _pos]; }; 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 { public: 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); }; namespace X { 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++ - ladicí funkce (makro assert) - klasifikace znaků (isalpha, isspace,...) - chybové kódy (ENOMEM,...), proměnná errno - vlastnosti a limity reálných typů (DBL_MAX,...) - limity celočíselných typů (INT_MAX,...) - přizpůsobení národnímu prostředí - matematické funkce (sin,...) - meziprocedurální skoky (setjmp, longjmp) - signály operačního systému - makra pro funkce s proměnným počtem argumentů - užitečné typy a konstanty (NULL) - standardní a souborový vstup a výstup - užitečné funkce (malloc,...) - manipulace s řetězci (strcpy,...) - konverze data a času - 16-bitové řetězce (wchar_t) - klasifikace 16-bitových znaků

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

239 STL – Ostatní  - užitečné algoritmy (for_each, sort, next_permutation,...)  - podpora funktorů  - podpora iterátorů  - alokátory pro kontejnery  - jednoduchá matematika na prvcích kontejnerů  - pomocné konstrukce (pair,...)

240 iostream vstupní a výstupní proudy

241 iostream #include 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 stringstream #include using namespace std; string f( int a) { ostringstream x; x << "a = " << a; return x.str(); }  - *stringstream – spolupracuje se std::string  - *strstream – spolupracuje s char *

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

244 iostream  Abstraktní rozhraní  basic_... 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 ios_base basic_ios basic_istream basic_ostream basic_iostream basic_streambuf nastavení formátovače stav média přímé a formátované čtení přímý a formátovaný zápis virtuální funkce čtení a zápisu dědičnost virtuální dědičnost ukazatel

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

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

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

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

249 iostream  Médium  Konkrétní médium je implementováno jako potomek třídy basic_streambuf  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) ios_base basic_ios basic_istream basic_ostream basic_iostream basic_...buf basic_streambuf stav formátovače stav média přímé a formátované čtení přímý a formátovaný zápis virtuální funkce čtení a zápisu médium, implementace čtení a zápisu

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

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

252 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 iostream  formátované čtení basic_istream & operator>>( basic_istream & 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 & operator & 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 iostream  formátované čtení/zápis basic_istream & operator>>( basic_istream & s, D & x) basic_ostream & operator & 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 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 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 & operator<<( basic_ostream & s, ios_base & (* f)( ios_base & s)) { f( s); return s; }

257 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 & operator<<( basic_ostream & s, const setw_manip & p) { s.width( p.x); return s; }

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

259 iostream  Iterátory nad proudy - příklady  naplnění kontejneru ze vstupu std::vector a; std::copy( std::istream_iterator ( std::cin),// aktuální pozice std::istream_iterator (),// "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 b; std::copy( b.begin(), b.end(), std::ostream_iterator ( std::cout));

260 RTTI Typová informace za běhu

261 RTTI  Operátor typeid 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 &   Lze porovnávat na rovnost  Má metodu name() vracející řetězec s nějakou formou jména typu

262 RTTI  Typické použití  Alternativa místo dynamic_cast #include 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 ( p); /*... */ } if ( typeid( * p) == typeid( Y) ) { Y * yp = static_cast ( p); /*... */ }

263 RTTI  Typické použití  Alternativa místo dynamic_cast  Pozor: rovnost typeid nereflektuje dědičnost Zatímco dynamic_cast ano #include 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 ( p); /*... */ } if ( typeid( * p) == typeid( Z) ) // platí { Z * zp = static_cast ( p); /*... */ }

264 RTTI  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  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 { public: void doitforall( Visitor &); /*... */ };

266 Visitor  Visitor - použití 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 { public: 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 { public: 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 { public: 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 { public: void doitforall( Visitor &); /*... */ };

270 Visitor  Visitor - implementace 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 { public: void doitforall( Visitor &); /*... */ private: typedef vector 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 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 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 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 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 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 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 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 struct List { typedef H Head; typedef R Rest; }; struct EmptyList {};  Funkce na seznamu typů template struct First { typedef typename L::Head Result; };

285 Teoretický pohled na šablony  Triky s typovým konstrukcemi  Seznam typů template struct List { typedef H Head; typedef R Rest; }; struct EmptyList {};  Funkce na seznamu typů template struct First { typedef typename L::Head Result; }; struct NullType {}; template<> struct First { typedef NullType Result; };

286 Teoretický pohled na šablony  Triky s typovým konstrukcemi  Seznam typů template struct List { typedef H Head; typedef R Rest; }; struct EmptyList {};  Funkce na seznamu typů template struct Nth { typedef typename Nth ::Result Result; }; template struct Nth { typedef typename L::Head Result; };

287 Teoretický pohled na šablony  Triky s typovým konstrukcemi  Jiná implementace seznamu typů template struct List; struct EmptyList;  Funkce na seznamu typů template struct Nth; template struct Nth, n> { typedef typename Nth ::Result Result; }; template struct Nth, 0> { typedef H Result; };

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 (...) { for ( int i = 0; i < n_-1; ++i) swap( p2[ i], p_[ i]); ++n_; swap( p_, p2); delete p2; throw; }

293 Oblasti platnosti identifikátorů  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 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 Code... push call... 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 /*...*/ int printf( const char *,...); /*...*/ hello.c #include int main( int argc, char * * argv) { printf( “Hello\n”); getch(); return 0; } conio.h int getch(); int putch(); /*...*/

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

297 Spustitelný program a spuštěný proces... push call... call... ret ‘H’,’e’,’l’,’l’, ’o’,10,0 hello... call... call entry point... syscall... Code Data 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( const void * key, 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) { /* test 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(); 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); return p; }

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( StringBody * a, StringBody * b) { _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 length() ? _a->read_at( i) : _b->read_at( i - _a->length()); } void StringBodyConcatenate::write_at( int i, char ch) { i length() ? _a->write_at( i, ch) : _b->write_at( i - _a->length(), ch); }

317 Problém  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 Řešení 1  Nahradit rekurzi pomocnou strukturou a cyklem typedef std::stack 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 typedef std::stack 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í  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  Problém: Implementace mnoha funkcí je rekurzivní void StringBodyConcatenate::copy( char * dst) const { _a->copy( dst); _b->copy( dst + _a->length()); }

323 Ř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 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 void F( T p) { H( p); while ( C( p) ) { p = G( p); H( 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 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í  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 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 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 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 vector {... */ protected: A allocator; };

334 STL – vector – naivní implementace – hlavička template class oriented_iterator; template class const_oriented_iterator; template 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 iterator; typedef const_oriented_iterator const_iterator; typedef oriented_iterator reverse_iterator; typedef const_oriented_iterator const_reverse_iterator; /*... */ private: size_type _n; T * _p; };

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

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

337 STL – vector – naivní implementace – push/pop template void vector ::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 void vector ::pop_back() { /*... */ }

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

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

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

341 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á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 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 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 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 Ukončení operačním systémem 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 Nedetekováno vůbec  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í Nesprávný static_cast - špatný odhad konkrétního typu 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  Vyplnění nepravděpodobnou hodnotou 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 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) David Bednárek ulita.ms.mff.cuni.cz."

Podobné prezentace


Reklamy Google