1 / 353

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

Pokro čilé p rogramování v C++ (část B). David Bednárek ulita.ms.mff.cuni.cz. Klíčové vlastnosti C++. 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

victor-key
Télécharger la présentation

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

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

E N D

Presentation Transcript


  1. Pokročilé programování v C++(část B) David Bednárek 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. Kanonické tvary tříd Singletony

  14. 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"); Kanonické tvary tříd

  15. Kanonické tvary tříd Hodnotové typy

  16. 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 &); Kanonické tvary tříd

  17. 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 &); Kanonické tvary tříd

  18. 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 &); Kanonické tvary tříd

  19. 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 &); Kanonické tvary tříd

  20. 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; } Kanonické tvary tříd

  21. 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; } Kanonické tvary tříd

  22. 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 &); Kanonické tvary tříd

  23. 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 &); Kanonické tvary tříd

  24. Kanonické tvary tříd Funktory, visitory

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

  26. 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_; }; Kanonické tvary tříd

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

  28. 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 Kanonické tvary tříd

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

  30. 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; }; Kanonické tvary tříd

  31. 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; }; Kanonické tvary tříd

  32. 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 &); /* ... */ }; Kanonické tvary tříd

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

  34. 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&); }; Kanonické tvary tříd

  35. 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 */; }; Kanonické tvary tříd

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

  37. 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 structallocation_policy { static void * alloc( size_t); static void free( void *); }; template< typename T> struct type_traits; template<> struct type_traits< char> { typedef char param_type; enum { min = 0, max = 255 }; static bool less( char, char); }; Kanonické tvary tříd

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

  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. class functor { public: virtual void f( ...) = 0; }; void for_each( functor & x) { for( ... it = ... ) x.f( it); // run-time binding } class functor_add { virtual void f(...) { ... } }; for_each( functor_add()); template< typename P> void for_each( P & x) { for( ... it = ... ) x.f( it); // compile-time binding } struct functor_add { void f(...) { ... } }; for_each( functor_add()); Polymorfismus běhový a kompilační

  43. template< typename P> void for_each( P & x) { for( ... it = ... ) x.f( it); // metoda } struct functor_add { void f(...) { ... } }; for_each( functor_add()); template< typename P> void for_each() { for( ... it = ... ) P::f( it); // statická funkce } struct policy_add { static void f(...) { ... } }; for_each< policy_add>(); Kompilační polymorfismus s objektem a bez

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

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

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

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

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

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

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

More Related