Upload
larue
View
29
Download
3
Embed Size (px)
DESCRIPTION
Objektov ě-orientované p rogramování v C++. David Bednárek ulita.ms.mff.cuni.cz. Pravidla studia. PRG0 32 2/2 Z,Zk. Zápis na cvičení. Elektronický zápis do jednotlivých skupin www.mff.cuni.cz/vnitro/is/sis Grupíček Skupiny budou naplněny podle příslušnosti ke studijním skupinám - PowerPoint PPT Presentation
Citation preview
ObjektovObjektově-orientované ě-orientované pprogramování v C++rogramování v C++
David Bednárek
ulita.ms.mff.cuni.cz
Pravidla studiaPravidla studia
PRG032
2/2
Z,Zk
Zápis na cvičení
Elektronický zápis do jednotlivých skupin
www.mff.cuni.cz/vnitro/is/sis• Grupíček
Skupiny budou naplněny podle příslušnosti ke studijním skupinám• Asi 30% studentů žije mimo studijní skupiny a nebyli takto zapsáni
Zapsáni musejí být všichni Do 13.10.
• Kdo se neobjeví na prvním ani druhém cvičení, bude vyškrtnut
Kapacita laboratoře je omezena, skupiny nelze přeplňovat
Udělit zápočet může jen cvičící,ke kterému je student zapsán
Kdo nebude do 13.10. zapsán, zápočet v tomto šk. roce nedostane
Udělení zápočtu
Přesné podmínky udělení zápočtu určuje cvičící Cvičící může podmínky individuálně upravit,
pokud se s ním student na začátku semestru dohodne
Vzorové podmínky Přiměřená účast na cvičeních Úspěšné odevzdání domácího úkolu Úspěšné složení zápočtového testu
• 1. a 2. pokusy ve zkouškovém období ... 3. pokusy v dubnu
• 2-3 hodiny v laboratoři, společně pro všechny skupiny
Vypracování zápočtového programu• Dohoda o tématu - do konce října
• Předvedení cvičícímu do 15.3.2005
• Doladění do konce výuky v letním semestru
Zkouška
Zkouška bude provedena formou abc-testu Vlastnosti a pravidla jazyka C++ Základy používání knihoven C++ (STL, iostream) Typické konstrukce objektového programování Exception-safe programming
Termíny Ve zkouškovém období ZS Během výuky v LS
Pravidla pro budoucí neúspěšné
Zkouška Pokud letos složíte obě zkoušky z PRG029 a PRG032 se známkou
výborně nebo velmi dobře a nedostanete některý zápočet, budou vám příští rok obě automaticky uznány
• Tento mechanismus je implementován zkoušejícími, nikoliv studijním oddělěním
• Příští rok se bude měnit rozdělení syllabu mezi semestry, proto toto pravidlo neplatí pro jednotlivé předměty
Zápočet Pokud nedostanete zápočet, budete příští rok opakovat ty části,
které jste letos nesplnili• Podmínky splněné letos se automaticky uznávají
• V příštím roce se musíte na začátku semestru přihlásit na některé cvičení a dohodnout se s jeho cvičícím na konkrétních podmínkách
• Mohou být vypsána speciální cvičení pro repetenty
ObsahObsah
Obsah cvičení
V laboratoři SW2 / v učebně
Microsoft Visual Studio .NET 2003
Demonstrace konstruktorů, destruktorů a virtuálních funkcí Příklady dědičnosti a virtuálních funkcí Demonstrace operátorů a copy-constructoru Příklady datových typů (Complex, String, Vector, Matrix) Praktická implementace vlastního typu string Šablony, implementace vlastních kontejnerů STL: deque, vector, map fstream, open etc. iostream, manipulátory, implementace vlastního operatoru << (Complex) Experimenty s výjimkami Exception-safe programování Debugging Vlastní nástroje pro ladění
Obsah přednášky
2.10. 3.10. Příklady dědičnosti, významy dědičnosti
Abstraktní a konkrétní třídy
Přetěžování operátorů, příklady: Complex a primitivní String
Lepší implementace třídy String, counted-pointers
Šablony
Šablony; Standardní knihovny C++
STL
Algoritmy, iostream
iostream, Koenig lookup
Výjimky
exception safety
Throw specifikace, přetypování, RTTI
Šablony – parciální specializace, traits, policies
Kanonické tvary tříd, Návrhové vzory, Singleton, Visitor
LiteraturaLiteratura
Literatura
Pro začátečníkyBruce Eckel:
Thinking in C++ (2000)Myslíme v jazyku C++ (Grada 2000)
Miroslav Virius:Pasti a propasti jazyka C++Programování v C++ (ČVUT 2001)
Andrew Koenig, Barbara E. Moo:Accelerated C++ (2000)
Stanley B. Lippman:Essential C++ (2000)
Literatura
Pro středně pokročiléAndrei Alexandrescu, Herb Sutter:
C++ Coding Standards (2005)Scott Meyers:
Effective C++ (1998)More Effective C++ (1996)Effective STL (2001)
Herb Sutter:Exceptional C++ (2000)More Exceptional C++ (2002)Exceptional C++ Style (2004)
Nicolai M. Josuttis:Object-Oriented Programming in C++ (2002)The C++ Standard Library (1999)
Literatura
Až si budete myslet, že všechno umíteAndrei Alexandrescu:
Modern C++ Design (2001)Moderní programování v C++ (Computer Press 2004)
David Vandevoorde, Nicolai M. Josuttis:C++ Templates (2003)
Normy
C++ISO/IEC JTC1/SC22/WG21:
14882 - Programming languages - C++ (2003)
C++ 2003 TR1 (2005) Nové rozšíření knihoven (částečně podporováno GCC 4.0) Založeno na knihovně Boost
C++0x Návrhy lze sledovat na http://www.open-std.org/jtc1/sc22/wg21/
C:ISO/IEC 9899:
Programming languages - C (1999) C99 Významně není podmnožinou C++ Řada překladačů jej nezvládá (včetně MSVC 2005)
www
http://www.open-std.org/jtc1/sc22/wg21/
ISO
http://www.gotw.ca/
Herb Sutter: Guru of the Week
http://www.boost.org/
Knihovna Boost
http://gcc.gnu.org/
GCC
C++C++
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řídyReprezentace 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
Třída a objekt
Instanciace třídy = vznik objektu tohoto typuJako globální proměnná
V rámci startu programu (před main)
Jako lokální proměnná V okamžiku průchodu řízení deklarací
Jako parametr předávaný hodnotou Těsně před voláním funkce
Jako pomocná proměnná při výpočtu výrazu V okamžiku, kdy je vypočtena její hodnota
Dynamickou alokací V okamžiku volání operátoru new
Jako položka jiné třídy nebo součást pole V okamžiku vzniku celku
Jako předek jiné třídy V okamžiku vzniku instance potomka
Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na nějTriviální důsledek:
class T { /*...*/ };
T * p; // zde nevzniká objekt typu T
Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na nějTriviá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 typuclass U : public T { /*...*/ };
U y;
T x = y; // toto je kopie části objektu y do vznikajícího objektu x
Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na nějTriviá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 typuclass 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-constructoruT::T( const T &);
a díky možnosti konvertovat odkaz na potomka na odkaz na předka:U => U & => T & => const T &
Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na nějTriviá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 typuclass 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
Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na nějTriviá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 typuclass 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, ...)
Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na nějNefunguje 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
Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na nějNefunguje 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
Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na nějNefunguje 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
Objekt a ukazatel na objekt
C++ důsledně odlišuje objekt a ukazatel na nějNefunguje 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ů
Objekt a ukazatel na objekt
Funkce jako add nemůže vracet referenciadd vrací hodnotu různou od všech svých parametrůhodnotu parametrů nesmí měnitreference nemá na co ukazovat
Špatné řešení č. 1: Lokální proměnná
Complex & add( const Complex & a, const Complex & b)
{
Complex r( a.Re + b.Re, a.Im + b.Im);
return r;
}
BĚHOVÁ CHYBA: r zaniká při návratu z funkce
Objekt a ukazatel na objekt
Funkce jako add nemůže vracet referenciadd vrací hodnotu různou od všech svých parametrůhodnotu parametrů nesmí měnitreference nemá na co ukazovat
Špatné řešení č. 2: Dynamická alokace
Complex & add( const Complex & a, const Complex & b)
{
Complex * r = new Complex( a.Re + b.Re, a.Im + b.Im);
return * r;
}
PROBLÉM: kdo to odalokuje ?
Objekt a ukazatel na objekt
Funkce jako add nemůže vracet referenciadd vrací hodnotu různou od všech svých parametrůhodnotu parametrů nesmí měnitreference nemá na co ukazovat
Špatné řešení č. 3: Globální proměnná
Complex g;
Complex & add( const Complex & a, const Complex & b)
{
g = Complex( a.Re + b.Re, a.Im + b.Im);
return g;
}
CHYBA: globální proměnná je sdílená
Complex a, b, c, d, e = add( add( a, b), add( c, d));
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
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
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"
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 &
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 &
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"
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
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 &
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
Třídy v C++
Třídy sloužící jako datové typyPromě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é“ objektyProměnné typu T *, případně T &
Objekty alokovány dynamicky
Kopírování nemívá smyslMetody měnící stav objektuVětšinou s dědičností a virtuálními funkcemi
Třídy v C++
Abstraktní třídaDefinice v C++: Třída obsahující alespoň jednu čistě
virtuální funkciBěžná definice: Třída, která sama nebude instanciovánaPředstavuje rozhraní, které mají z ní odvozené třídy
(potomci) implementovat
Konkrétní třídaTřída, určená k samostatné instanciaciImplementuje rozhraní, předepsané abstraktní třídou, ze
které je odvozena
Dědičnost a destruktor
class UUU {
public:
virtual ~UUU();
};
class XXX : public UUU {
public:
virtual ~XXX();
};
XXX * px = new XXX;
// konverze potomek-předek
UUU * pu = px;
delete pu;
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
Ideální užití dědičnosti a virtuálních funkcí
Abstraktní třídaDefinuje 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
};
Ideální užití dědičnosti a virtuálních funkcí
Abstraktní třídaDefinuje 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
};
Ideální užití dědičnosti a virtuálních funkcí
Abstraktní třídaDefinuje rozhraní objektu jako množinu předepsaných
virtuálních funkcíKonkrétní třída
Implementuje předepsané virtuální funkceJe potomkem abstraktní třídy
Dědičnost jako vztah rozhraní-implementace
Ideální užití dědičnosti a virtuálních funkcí
Abstraktní třídaDefinuje 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_;
};
Ideální užití dědičnosti a virtuálních funkcí
Abstraktní třídaDefinuje rozhraní objektu jako množinu předepsaných
virtuálních funkcíKonkrétní třída
Implementuje předepsané virtuální funkcePolotovar 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
Ideální užití dědičnosti a virtuálních funkcí
Polotovar třídyMezi 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_;
};
Ideální užití dědičnosti a virtuálních funkcí
Konkrétní třídaImplementuje předepsané virtuální funkceKonkré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_;
};
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éž mechanismyNěkteré jazyky tyto účely rozlišují (Java)
Ideální užití dědičnosti a virtuálních funkcí
Ideální abstraktní třídaPouze č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
Ideální užití dědičnosti a virtuálních funkcí
Ideální abstraktní třídaPouze č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ář
Ideální užití dědičnosti a virtuálních funkcí
Ideální abstraktní třídaPouze č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čnostObvykle musí být virtuální, aby odpovídala sjednocení:
Fyzikář = Pedagogika + Fyzika Matikář = Pedagogika + Matematika Fyzikář+Matikář nemá mít dvě rozhraní pro Pedagogiku
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
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
Ideální užití dědičnosti a virtuálních funkcí
Typické použití virtuálních funkcíPolymorfní datová struktura
class Scene {
public:
void insert( GraphicObject * go);
void paintAll() const;
private:
enum { MAX_ = 30 };
GraphicObject * objects_[ MAX_];
int n_;
};
primitivní řešení: pole ukazatelů• pole objektů by nefungovalo (ani nešlo přeložit)
void Scene::paintAll()
{ for ( int i = 0; i < n_; i++) objects_[ i]->paint();
}
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();
}
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 implementaciclass GraphicObject {
/* ... čistě virtuální funkce ... */
private:
friend class Scene;
Item * next_;
};
class Scene {
public:
void insert( GraphicObject * go);
void paintAll() const;
private:
GraphicObject * first_;
};
Ideální užití dědičnosti a virtuálních funkcí
Typické použití virtuálních funkcíPolymorfní datová struktura
moderní řešení: STL • kontejnery z STL vždy obsahují kopie vkládaných objektů
• std::list< GraphicObject> by nešlo ani přeložit
• kontejner je tedy třeba aplikovat na ukazatel, podobně jako pole
class Scene {
public:
void insert( GraphicObject * go);
void paintAll() const;
private:
std::list< GraphicObject *> objects_;
};
Třída jako datový typTřída jako datový typ
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
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();
Speciální metody tříd
Konverzní konstruktoryclass 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átoryclass 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
Přetěžování operátorůPřetěžování operátorů
Operator overloading
Přetěžování operátorů
Většinu operátorů jazyka C++ lze definovat pro uživatelské datové typy.
Nelze předefinovat tyto operátory:. .* :: ? : sizeof
Alespoň jeden z operandů musí být třída nebo výčtový typ nebo reference na ně
Nelze tudíž předefinovat operace na číselných typech a ukazatelích
Předefinováním nelze měnit prioritu a asociativitu operátorů Pro předefinované operátory nemusí platit identity
definované pro základní typy, např.: ++a nemusí být ekvivalentní a=a+1 a[b] nemusí být ekvivalentní *(a+b) ani b[a]
Pro předefinované operátory && a || neplatí pravidla o zkráceném vyhodnocování
Přetěžování operátorů
Typy skutečných operandů předefinovaného operátoru nemusejí přesně odpovídat typům formálních parametrů operátoru.
Pro výběr správné varianty mezi předefinovanými operátory platí stejná pravidla, jako pro přetížené funkce.
Předefinování operátorů se provádí definováním metody se speciálním jménem operatorxxx ve třídě (prvního operandu), pro kterou má být operátor definován.
Některé operátory je možno definovat i jako globální funkce s týmž speciálním jménem.
Speciální jméno je možno používat i pro explicitní vyvolání této metody či funkce.
Operátory, které jsou metodami, jsou s výjimkou operátoru přiřazení dědičné a smějí být virtuální.
Přetěžování operátorů - Binární operátory
Binární operátor xxx z množiny+ - * / % << >> < > <= >= <<= >>= ^ & | && || == != += -= *= /= %= ^= &= |= ->*
lze pro operandy typu B a C předefinovat dvěma způsoby: Globální funkcí
A operator xxx( B, C)
A operator xxx( B &, C &)
A operator xxx( const B &, const C &)
MetodouA B::operator xxx( C)
A B::operator xxx( const C &)
A B::operator xxx( const C &) const
Binární operátor [ ] lze předefinovat pouze metodou
A B::operator []( C)
A B::operator []( C &)
A B::operator []( const C &) const
Přetěžování operátorů - Unární operátory
Unární operátor xxx z množiny+ - * & ~ !
a prefixové operátory++ --
lze pro operand typu B předefinovat dvěma způsoby: Globální funkcí
A operator xxx( B)
A operator xxx( B &)
A operator xxx( const B &)
MetodouA B::operator xxx()
A B::operator xxx() const
Přetěžování operátorů - Unární operátory
Postfixové operátory ++ a --lze pro operand typu B předefinovat dvěma způsoby:
Globální funkcíA operator xxx( B, int)
A operator xxx( B &, int)
A operator xxx( const B &, int)
MetodouA B::operator xxx( int)
A B::operator xxx( int) const
Přetěžování operátorů - Unární operátory
Operátor ->je považován za unární operátor a jeho návratovou
hodnotou musí být buďto ukazatel na třídu s uvedenou položkou, nebo objekt či referenci na objekt, pro který je znovu definován operátor ->
Přetěžování operátorů - Unární operátory
Operátor volání funkce ()smí být definován pouze jako metoda třídy a umožňuje
používat objekty této třídy jako funkce. Smí mít libovolný počet parametrů a pro výběr konkrétní
varianty operátoru se použije podobný mechanismus, jako pro přetížené funkce.
ComplexComplex
Komplexní číslo
Complex
class Complex {
public:
Complex() : re_( 0.0), im_( 0.0) {}
Complex( double re, double im = 0.0) : re_( re), im_( im) {}
double Re() const { return re_; }
double Im() const { return im_; }
Complex operator+( const Complex & b) const
{
return Complex( Re() + b.Re(), Im() + b.Im());
}
Complex operator+( double b) const
{
return Complex( Re() + b, Im());
}
private:
double re_, im_;
};
inline Complex operator+( double a, const Complex & b)
{
return Complex( a + b.Re(), b.Im());
}
Poučení - konstruktory
Ve třídě Complex nejsou odkazy na data uložená jindeVyhovuje chování těchto kompilátorem vytvořených metod:
Complex( const Complex &); Kopíruje datové položky
Complex & operator=( const Complex &); Kopíruje datové položky
~Complex(); Nedělá nic
Tyto metody není třeba psát vlastní
Poučení - konstruktory
Ve třídě Complex jsou datové položky atomických typů Ty nemají konstruktory a zůstávají neinicializované
Nevyhovalo by tedy chování kompilátorem vytvořeného konstruktoru bez parametrů:
Complex(); Nedělá nic
Navíc je zde jiný konstruktor, takže kompilátor má zakázáno konstruktor bez parametrů vytvořit
Nebylo by tedy možné deklarovat proměnnou typu Complex bez explicitní inicializace
Konstruktor bez parametrů musí být napsán ručně Měl by nastavit datové položky na vhodnou hodnotu
Poučení - konstruktory
Speciální konstruktorComplex( double re, double im = 0.0);
Lze zavolat s jedním parametrem
Slouží jako konverzní konstruktor Implementuje konverzi double => Complex
Důsledky: Bude fungovat přiřazení Complex=double Není nutné psát sčítání pro Complex+double:
Complex Complex::operator+( double b) const;
• Dělá se to kvůli rychlosti
Kompilátor umí použít konverzi (jednu) a najít náhradní metodu:Complex Complex::operator+( const Complex & b) const;
Nefunguje pro sčítání double+Complex• Protože Complex+Complex je implementováno metodou
• Při implementaci globální funkcí by to fungovaloComplex operator+( const Complex & a, const Complex & b)
Poučení – konverzní operátor
Konverzní operátoroperator 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á operacedouble 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ů
Poučení – konverzní operátor
Konverzní operátoroperator double() const;
Implementuje konverzi Complex => double
Není zde vhodný Konverze je ztrátová Může vést k nejednoznačnostem Může vést k chybám
Complex a, b;
a = sin( b);
• může znamenat voláníComplex sin( const Complex & x); // <complexmath.h>
• ale takéComplex::operator double() const;
double sin( double x); // <math.h>
Complex( double re, double im = 0.0);
• První varianta má přednost...
• ...ale když zapomenete #include <complexmath.h> ...
Poučení - vracení hodnotou
Once programmers grasp the efficiency implications of pass-by-value for objects, they become crusaders, determined to root out the evil of pass-by-value wherever it may hide. [Scott Meyers: Effective C++]
Poučení - vracení hodnotou
Once programmers grasp the efficiency implications of pass-by-value for objects, they become crusaders, determined to root out the evil of pass-by-value wherever it may hide. [Scott Meyers: Effective C++]
Poučení: operator+ vždy vrací hodnotouVrací novou hodnotu, která jinde neexistuje
return Complex( ...);
Poučení - vracení hodnotou
Once programmers grasp the efficiency implications of pass-by-value for objects, they become crusaders, determined to root out the evil of pass-by-value wherever it may hide. [Scott Meyers: Effective C++]
Poučení: operator+ vždy vrací hodnotouVrací novou hodnotu, která jinde neexistuje
return Complex( ...);
Ale: operator+= může vracet odkazemVrací hodnotu levého operandu
return * this;
Poučení – binární operátory +, +=
Kanonické řešeníclass Complex {
public:
Complex( double re, double im = 0.0); // konverzní konstruktor
// ...
Complex & operator+=( const Complex & b)
{
re_ += b.re_;
im_ += b.im_;
return * this;
}
// ...
};
Complex operator+( const Complex & a, const Complex & b)
{
Complex tmp( a);
tmp += b;
return tmp;
}
Poučení – unární operátory -, ++
Kanonické řešení Unární operátory jsou vždy metodami
• Není zapotřebí schopnost konverze operandůclass Complex {
public:
// ...
Complex operator-() const { return Complex( -re_, -im_); }
Complex & operator++() { _re += 1.0; return * this; }
Complex operator++( int)
{ Complex tmp( * this);
operator++();
return tmp;
}
};
Prefixové ++, -- vrací odkazem• Může a nemusí být const
Postfixové ++, -- vrací hodnotou
Poučení – unární operátory -, ++
Kanonické řešení Unární operátory jsou vždy metodami
• Není zapotřebí schopnost konverze operandůclass Complex {
public:
// ...
Complex operator-() const { return Complex( -re_, -im_); }
Complex & operator++() { _re += 1.0; return * this; }
Complex operator++( int)
{ Complex tmp( * this);
operator++();
return tmp;
}
};
Poučení pro uživatele operátorů ++, --Prefixová varianta je významně rychlejší
S výjimkou vestavěných typů
StringString
Ukázková implementace č. 1(nepoužitelná)
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;
}
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
StringString
Ukázková implementace č. 2
(naivní, neefektivní)
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);
}
Poučení - konstruktory
Ve třídě String jsou odkazy na data uložená jindeChová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í
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
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();
}
Poučení - konstruktory
Lepší řešení operátoru přiřazení:#include <algorithm>
void String::swap( String & b)
{
std::swap< _str, b._str>;
}
String & String::operator=( const String & b)
{
String tmp( b);
swap( tmp);
return * this;
}
Toto řešení je navíc exception-safe (později...) Metodu swap je vhodné publikovat takto
void swap( String & a, String & b)
{ a.swap( b); }
• Kdyby někdo udělal třídu obsahující náš String...
StringString
Ukázková implementace č. 3(counted-pointers)
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;
};
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;
};
String č. 3 - Counted-pointers
Kopie a destrukceString::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();
}
String č. 3 - Counted-pointers
Destrukce těla
void StringBody::dec()
{
if ( ! --count )
delete this;
};
StringBody::~StringBody()
{
delete[] _str;
}
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);
}
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();
}
StringString
operator [ ]
String č. 4 - operator [ ] - čtení
class String {
public:
char operator[]( int pos) const
{
if ( pos < 0 || pos >= _body->length() )
return 0;
return _body->buffer()[ _pos];
};
/* ... */
};
String č. 4 - operator [ ] - zápis
class String {
public:
char & operator[]( int pos)
{
if ( pos < 0 || pos >= _body->length() )
return _dummy;
return _body->buffer()[ _pos];
};
private:
static char _dummy;
/* ... */
};
char String::_dummy = 0;
String č. 4 - operator [ ] - zápis
class String {
public:
char & operator[]( int pos)
{
if ( pos < 0 || pos >= _body->length() )
return _dummy;
return _body->buffer()[ _pos]; // chyba: _body je sdíleno !
};
private:
static char _dummy;
/* ... */
};
char String::_dummy = 0;
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();
}
};
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 č. 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
String č. 4 - operator [ ] – rozlišení čtení a zápisuclass 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_;};
ŠablonyŠablony
Templates
Šablony tříd - příklad
Definicetemplate< int n, class T> class Array {
T p[ n]; T dummy;
public:
T & operator[]( int x) { return x<n ? p[x] : dummy; }
};
PoužitíArray< 5, int> a;
Array< 7, int> b;
Array< 5 + 2, int> c;
Array< 3, Array< 7, int> > d;
a[ 3] = b[ 3];
a = b; // chyba !!!
b = c; // OK, implicitní copy-constructor
d[ 2][ 3] = 1;
Š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 šablonytemplate< formální-parametry>
lze použít před několika formami deklarací; oblastí platnosti formálních parametrů je celá prefixovaná deklarace
Š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
Š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 T> class X {
X( const X< T> &);
};
template< class T> class X {
X( const X &);
};
• Některé překladače (nesprávně) připouštějí i tuto variantutemplate< class T> class X {
X< T>( const X< T> &);
};
Š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< class T> void X< T>::f( int a, int b)
{ /* ... */ }
V kvalifikovaném jméně metody je nutné uvést patřičný seznam argumentů, tj. X< T>::f a nikoliv X::f
Těla metod musejí být viditelná z každého místa, kde jsou pro nějakou instanci šablony volána
Musejí tedy typicky být v témže hlavičkovém souboru jako sama šablona.
Uvedení těla metody vně třídy tedy u šablon typicky nic nepřináší, může být však vynuceno rekurzivními odkazy mezi šablonami apod.
Šablony tříd – triky
Dopředná deklarace šablony
template< class T> class X;
/* ... zde může být použito X s jakýmikoliv argumenty...
... pouze v kontextech,
kde kompilátor nepotřebuje znát tělo šablony ...
*/
template< class T> class X
{ /* ... */
};
Šablony funkcí
Šablona funkce je generická globální funkce prefixovaná konstrukcí
template< /*...formální parametry...*/>
se stejnými druhy formálních parametrů šablony jako u šablon tříd
Všechny formální argumenty prefixu funkční šablony by měly být užity v typech formálních parametrů funkce
Pokud nejsou, nefunguje níže uvedené automatické odvozování
Skutečné argumenty šablony funkce se při volání neuvádějí Kompilátor je odvozuje z typů skutečných parametrů funkce Mohou se ovšem uvést explicitně za jménem funkce
Šablony funkcí
Pod stejným identifikátorem může být deklarováno několik různých šablon funkce a navíc několik obyčejných funkcí.
Obyčejné funkce mají přednost před generickými
template< class T> T max( T a, T b)
{ return a < b ? b : a; };
char * max( char * a, char * b)
{ return strcmp( a, b) < 0 ? b : a; };
template< int n, class T> T max( Array< n, T> a)
{ /* ... */ }
Příklad ze standardních knihoven:template< class T> void swap( T & a, T & b)
{ T tmp(a); a = b; b = tmp; };
• K tomu řada chytřejších implementací swap pro některé třídy
Šablony - triky
Parciální specializace Deklarovanou šablonu lze pro určité kombinace parametrů
předefinovat jinak, než určuje její základní definicetemplate< int n> class Array< n, bool> { /* specializace pro pole typu bool */ };
Krajním případem parciální specializace je explicitní specializaceExplicitní specializace
template<> class Array< 32, bool> { /* ... */ };
Explicitní instanciace Překladač je možné donutit ke kompletní instanciaci šablony
template class Array< 128, char>;
Triky a zvyklosti: traits, policy classes,...
Později...
Standardní knihovny C++Standardní knihovny C++
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;
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částiZákladní knihovny převzaté z C, podle nové konvence v
přejmenovaných souborechRozšířené C++ knihovnyiostream: Systém znakového a formátovaného vstupu a
výstupuSTL: Standard Template Library
Základní knihovny C a C++
• <assert.h> <cassert> - ladicí funkce (makro assert)
• <ctype.h> <cctype> - klasifikace znaků (isalpha, isspace, ...)
• <errno.h> <cerrno> - chybové kódy (ENOMEM, ...), proměnná errno
• <float.h> <cfloat> - vlastnosti a limity reálných typů (DBL_MAX, ...)
• <limits.h> <limits> <climits> - limity celočíselných typů (INT_MAX, ...)
• <locale.h> <locale> <clocale> - přizpůsobení národnímu prostředí
• <math.h> <cmath> - matematické funkce (sin, ...)
• <setjmp.h> <csetjmp> - meziprocedurální skoky (setjmp, longjmp)
• <signal.h> <csignal> - signály operačního systému
• <stdarg.h> <cstdarg> - makra pro funkce s proměnným počtem argumentů
• <stddef.h> <cstddef> - užitečné typy a konstanty (NULL)
• <stdio.h> <cstdio> - standardní a souborový vstup a výstup
• <stdlib.h> <cstdlib> - užitečné funkce (malloc, ...)
• <string.h> <cstring> - manipulace s řetězci (strcpy, ...)
• <time.h> <ctime> - konverze data a času
• <wchar.h> <cwchar> - 16-bitové řetězce (wchar_t)
• <wctype.h> <cwctype> - klasifikace 16-bitových znaků
Rozšířené knihovny pro C++
<bitset.h> <bitset> -- šablona pro bitové pole (bitset<N>) <complex.h> <complex> - komplexní čísla různé přesnosti
(complex<double>,...) <exception.h> <exception> - nastavení zpracování výjimek
(set_unexpected,...) <stdexcept.h> <stdexcept> - standardní výjimky (overflow_error,...) <valarray.h> <valarray> - šablony různě inteligentních polí
(valarray,...), vektorové operace (podpora paralelních výpočtů matematických funkcí)
STLSTL
Standard Template Library
STL
KontejneryPrefabrikáty základních datových strukturŠablony parametrizované typem ukládaného objektu
Všechny kontejnery pracují s kopiemi vkládaných hodnot Typ hodnot musí mít alespoň copy-constructor a destruktor Některé druhy kontejnerů či operací s nimi vyžadují i operator=
nebo konstruktor bez parametrů
Hodnoty se přidávají a odebírají metodami odpovídajícími druhu kontejneru
K hodnotám je možno přistupovat pomocí iterátoru, který reprezentuje inteligentní ukazatel dovnitř kontejneru
Prostřednictvím iterátoru je možno měnit uložené hodnoty
STL – Příklad
#include <deque>
typedef std::deque< int> my_deque;
my_deque the_deque;
the_deque.push_back( 1);
the_deque.push_back( 2);
the_deque.push_back( 3);
int x = the_deque.front(); // 1
the_deque.pop_front();
my_deque::iterator ib = the_deque.begin();
my_deque::iterator ie = the_deque.end();
for ( my_deque::iterator it = ib; it != ie; ++it)
{
*it = *it + 3;
}
int y = the_deque.back(); // 6
the_deque.pop_back()
int z = the_deque.back(); // 5
STL – Kontejnery
Sekvenční kontejnery<list.h> <list>
list - obousměrně vázaný seznam
<deque.h> <deque> deque - fronta s přidáváním a odebíráním z obou stran
<string.h> <string> basic_string - posloupnost ukončená terminátorem string = basic_string< char> - chytřejší implementace řetězce
<vector.h> <vector> vector - pole prvků s přidáváním zprava
Odvozené kontejnery <queue.h> <queue>
• queue - fronta
<stack.h> <stack>• stack - zásobník
STL – Kontejnery
Pomocné metody kontejneru
Test prázdnostibool empty() const
Počet prvkůsize_t size() const
nepoužívat pro testy prázdnosti
STL – Kontejnery
Metody kontejneru, vracející iterátoryOdkaz na začátek kontejneru - první platný prvek
iterator begin()
const_iterator begin() const
Odkaz za konec kontejneru - za poslední platný prvekiterator end()
const_iterator end() const
iterator a const_iterator jsou typy definované uvnitř kontejneru, zvané iterátory
přístupné konstrukcemi jako vector< int>::iterator vlastnosti iterátorů jsou mírně závislé na druhu kontejneru
Iterátor kontejneru obsahujícího typ T je třída s operátory definovanými tak, aby se chovala podobně jako ukazatel na typ T
Vytváří se tak iluze, že kontejner je pole
STL – Kontejnery
Operátory definované na iterátorechpřístup k prvku, na který iterátor ukazuje
T & iterator::operator *() const
const T & const_iterator::operator *() const
posouvání iterátoru směrem ke konci jednosměrný iterátor
iterator & iterator::operator++()
posouvání iterátoru směrem k začátku obousměrný iterátor
iterator & iterator::operator--()
libovolný posun iterátor s přímým přístupem
iterator operator+( iterator, int)
iterator operator-( iterator, int)
STL – Kontejnery
Metody kontejneru pro vkládání
iterator insert( iterator p, T x)
vsune (kopii) x před prvek, na který ukazuje iterátor p• vrací iterátor ukazující na vložený prvek
void insert( iterator p, int n, T x)
vsune n kopií x před prvek, na který ukazuje iterátor p
template< typename other_iterator>
void insert( iterator p, other_iterator b, other_iterator e)
před prvek, na který ukazuje iterátor p, vsune kopii posloupnosti prvků ohraničené iterátory b a e
• Tato posloupnost nesmí být uvnitř téhož kontejneru
• Tato posloupnost může být součástí jiného druhu kontejneru
je-li p == end(), vkládání připojuje na konec kontejneru všechny dříve existující iterátory odkazující na tento kontejner jsou
po použití insert neplatné, včetně p
STL – Kontejnery
Metody kontejneru pro odebírání
iterator erase( iterator p)
vyjme prvek, na který ukazuje iterátor p• p nesmí být rovno end()
• vrací iterátor ukazující na prvek za vyjmutým prvkem (nebo end())
iterator erase( iterator p, iterator e)
vyjme všechny prvky mezi p a e, včetně p a vyjma e• p nesmí být rovno end()
• vrací iterátor odkazující na prvek e (původní iterátor e nebude platný)
všechny iterátory odkazující na tento kontejner jsou po použití erase neplatné, včetně p a e
void clear()
{ erase( begin(), end()); }
STL – Kontejnery
Odvozené funkce kontejneru pro přístup k prvkůmPrvky na koncích
• list, deque, vector
• podmínka: ! empty()
T & front()
const T & front() const
{ return * begin(); }
T & back()
const T & back() const
{ return * --end(); }
STL – Kontejnery
Odvozené funkce kontejneru pro přístup k prvkůmPrvky uprostřed
• deque, vector, string
• podmínka: n < size()
T & at( int n)
T & operator[]( int n)
const T & at( int n) const
const T & operator[]( int n) const
{ return * ( begin() + n); }
STL – Kontejnery
Odvozené funkce manipulace s konci kontejneruPřidání jednotlivého prvku
void push_front( T x)
{ return insert( begin(), x); }
• list, deque
void push_back( T x)
{ return insert( end(), x); }
• list, deque, vector
Odebrání jednotlivého prvkuvoid pop_front()
{ return erase( begin()); }
• list, deque
void pop_back()
{ return erase( --end()); }
• list, deque, vector
STL - Kontejnerysložitost operace
na kontejneru s n prvky
list deque vector basic_string
přídání / odebrání jednoho prvku na začátku
push_front
pop_front
konstantní konstantní funkce neexistuje
funkce neexistuje
přídání / odebrání jednoho prvku na i-té pozici
insert
erase
konstantní min( i, n - i) n - i n - i
přídání / odebrání m prvků na i-té pozici
insert
erase
m
přesuny mezi seznamy (splice) jsou konstantní
m +min( i, n - i) m + n - i m + n - i
přídání / odebrání jednoho prvku na konci
push_back
pop_back
konstantní konstantní konstantní funkce neexistuje
nalezení i-tého prvku
begin() + i funkce neexistuje
konstantní konstantní konstantní
paměťová náročnost
kontejneru s prvky velikosti s
(s + K) * n
K řádově 16 B
q * s * n
q kolem 1.2
q * s * n
q kolem 1.2
q * s * n
q kolem 1.2
STL - Kontejnery
Asociativní kontejnery<set.h> <set>
set<T> - množina multiset<T> - množina s opakováním
<map.h> <map> map<K,T> - asociativní pole, tj. parciální zobrazení K -> T multimap<K,T> - relace s rychlým vyhledáváním podle klíče K
pair<A,B> - pomocná šablona uspořádané dvojice• s položkami first, second
STL - Kontejnery
Asociativní kontejnery vyžadují relaci uspořádání na klíčích Klíčem se rozumí první parametr šablony kontejneru Uspořádání se obvykle definuje operátorem < definovaným na typu
klíče• Pozor na konstrukce typu set< char *>
Uspořádání lze rovněž zadat přídavným parametrem šablony• Parametrem je funktor, tj. třída obsahující operator() se dvěma
parametry (porovnávanými objekty) typu klíče
Definované uspořádání nemusí být antisymetrická relace pokud platí
! (x < y) && ! (y < x)pak jsou prvky x a y považovány za ekvivalentní
STL - Kontejnery
Asociativní kontejnery - procházení
Asociativní kontejnery jsou uspořádány podle klíčůV pořadí podle tohoto uspořádání je lze procházet iterátory
Metody begin() a end() a operátory iterátorů mají stejný význam, jako u sekvenčních kontejnerů
Kontejnery map a multimap obsahují uspořádané dvojice pair< K, T>
Procházení celého asociativního kontejneru se užívá málokdy
Iterátory se častěji získávají vyhledáváním
STL - Kontejnery
Asociativní kontejnery - vyhledávání
iterator set::find( T x)
iterator multiset::find( T x)
iterator map::find( K x)
iterator multimap::find( K x)
pokud v kontejneru existuje prvek s klíčem ekvivalentním x:• vrací iterátor ukazující na první takový prvek
• multiset, multimap: další takové prvky jsou dostupné operací ++
jinak vrací end()
operace má logaritmickou složitost
STL - Kontejnery
Asociativní kontejnery - intervalové dotazy
iterator set::lower_bound( T x)
iterator multiset::lower_bound( T x)
iterator map::lower_bound( K x)
iterator multimap::lower_bound( K x)
vrací první prvek jehož klíč není menší než x, případně end()
iterator set::upper_bound( T x)
iterator multiset::upper_bound( T x)
iterator map::upper_bound( K x)
iterator multimap::upper_bound( K x)
vrací první prvek jehož klíč je větší než x, případně end()
dvojici takto získaných iterátorů lze využít v jiných funkcích téhož i jiného kontejneru
operace mají logaritmickou složitost
STL - Kontejnery
Asociativní kontejnery - vkládáníset a map
pair< iterator, bool> set::insert( T x)
pair< iterator, bool> map::insert( pair< K, T> x)
pokud prvek ekvivalentní x (resp. x.first) v kontejneru není: kopie x se vloží do kontejneru vrací pair< iterator, bool>( p, true) kde p je iterátor ukazující na
vložený prvek
pokud prvek ekvivalentní x (resp. x.first) v kontejneru již je: vrací pair< iterator, bool>( p, false) kde p je iterátor ukazující na
existující prvek ekvivalentní x
operace má logaritmickou složitostpo operaci budou dříve existující iterátory neplatné
STL - Kontejnery
Asociativní kontejnery - vkládánímultiset a multimap
iterator multiset::insert( T x)
iterator multimap::insert( pair< K, T> x)
kopie x se vloží do kontejneru vrací iterátor ukazující na vložený prvek
operace má logaritmickou složitostpo operaci budou dříve existující iterátory neplatné
STL - Kontejnery
Asociativní kontejnery - odebíránípodle klíče
size_type set::erase( T x)
size_type multiset::erase( T x)
size_type map::erase( K x)
size_type multimap::erase( K x)
odebere všechny prvky s klíčem ekvivalentním zadanému x vrací počet odebraných prvků
operace má logaritmickou složitost vzhledem k velikosti kontejneru plus lineární k počtu odebraných prvků
po operaci budou dříve existující iterátory neplatné
STL - Kontejnery
Asociativní kontejnery - odebíránípodle iterátoru - jeden prvek
void set::erase( iterator p)
void multiset::erase( iterator p)
void map::erase( iterator p)
void multimap::erase( iterator p)
odebere prvek na který odkazuje iterátor p• p nesmí být rovno end()
operace má konstantní složitost rozumí se amortizovaná složitost
po operaci budou dříve existující iterátory neplatné
STL - Kontejnery
Asociativní kontejnery - odebíránípodle iterátoru - interval
void set::erase( iterator p, iterator e)
void multiset::erase( iterator p, iterator e)
void map::erase( iterator p, iterator e)
void multimap::erase( iterator p, iterator e)
vyjme všechny prvky mezi p a e, včetně p a vyjma e• p nesmí být rovno end()
operace má složitost logaritmickou vůči velikosti kontejneru plus lineární vůči počtu odebraných prvků
po operaci budou dříve existující iterátory neplatné
STL - Kontejnery
map - operator [ ]
T & map::operator[]( K x)
{ return (*((insert(make_pair( x, T()))).first)).second; }
Vrátí referenci na hodnotovou (second) složku prvku s klíčem ekvivalentním x
Pokud takový prvek neexistuje, vytvoří jej• Jeho hodnotová složka bude T()
Tento operátor je možno používat pro vkládání, přepisování i čtení prvků kontejneru
Kontejner map se chová jako asociativní pole (perl, PHP,...) Pozor: To neplatí u sekvenčních kontejnerů
Po této operaci (i v případě čtení) mohou být všechny iterátory odkazující na tento kontejner neplatné
STL – Ostatní
<algorithm.h> <algorithm> - užitečné algoritmy (for_each, sort, next_permutation, ...)
<functional.h> <functional> - podpora funktorů <iterator.h> <iterator> - podpora iterátorů <memory.h> <memory> - alokátory pro kontejnery <numeric.h> <numeric> - jednoduchá matematika na prvcích
kontejnerů <utility.h> <utility> - pomocné konstrukce (pair,...)
STL – Algoritmy
Šablona funkce for_each <algorithm>
template<class InputIterator, class Function>
Function for_each(
InputIterator first,
InputIterator last,
Function f);
first a last jsou iterátory, určující procházený úsek nějakého kontejneru (všetně first, mimo last)
f je buďto globální funkce (ukazatel na funkci), nebo funktor, tj. třída obsahující operator()
Funkce f (případně metoda operator()) je zavolána na každý prvek v zadaném intervalu
prvek se předává jako * iterator, což může být odkazem funkce f tedy může modifikovat prvky seznamu
STL – Algoritmy
Šablona funkce for_each <algorithm>
template<class InputIterator, class Function>
Function for_each(
InputIterator first,
InputIterator last,
Function f)
{
for (; first != last; ++first)
f( * first);
return f;
}
Skutečná implementace může využívat specifických vlastností procházených kontejnerů
• Viz parciální specializace
• Bude tedy rychlejší, než ručně psaný for cyklus!
Takto napsanou šablonu lze zkompilovat pro jakékoliv f, na které lze aplikovat operátor (), tedy jak pro funkci, tak pro funktor
STL – Algoritmy
Použití funkce for_each
void my_function( double & x)
{
x += 1;
}
void increment( list< double> & c)
{
for_each( c.begin(), c.end(), my_function);
}
STL – Algoritmy
Použití funkce for_each
class my_functor {
public:
double v;
void operator()( double & x)
{ x += v; }
my_functor( double p) : v( p) {}
};
void add( list< double> & c, double value)
{
for_each( c.begin(), c.end(), my_functor( value));
}
STL – Algoritmy
Použití funkce for_each
class my_functor {
public:
double s;
void operator()( const double & x)
{ s += x; }
my_functor() : s( 0.0) {}
};
double sum( const list< double> & c)
{
my_functor f;
f = for_each( c.begin(), c.end(), f);
return f.s;
}
STL – Algoritmy
Použití funkce for_each
class my_functor {
public:
double s;
void operator()( const double & x)
{ s += x; }
my_functor() : s( 0.0) {}
};
double sum( const list< double> & c)
{
my_functor f;
for_each( c.begin(), c.end(), f);
return f.s;
}
Pozor: f se předává hodnotou - tato implementace vždy vrací 0.0
STL – Algoritmy
Použití funkce for_each
class my_functor {
public:
double s;
void operator()( const double & x)
{ s += x; }
my_functor() : s( 0.0) {}
};
double sum( const list< double> & c)
{
return for_each( c.begin(), c.end(), my_functor()).s;
}
STL – Algoritmy
Šablona funkce find <algorithm>
template<class InputIterator, class T>
InputIterator find(
InputIterator first,
InputIterator last,
const T & value)
{
for (; first != last; ++first)
if ( * first == value )
break;
return first;
}
STL – Algoritmy
Šablona funkce find_if <algorithm>
template<class InputIterator, class Predicate>
InputIterator find_if(
InputIterator first,
InputIterator last,
Predicate pred)
{
for (; first != last; ++first)
if ( pred( * first) )
break;
return first;
}
Predikát pred může být funkce nebo funktor
STL – Algoritmy
Přehled algoritmůPrůchod kontejnerem
for_each
Čtení kontejnerů find, find_if - první prvek s danou vlastností find_end - poslední výskyt druhé sekvence v první find_first_of - první výskyt některého prvku druhé sekvence v první adjacent_find - první prvek ekvivalentní sousednímu count, count_if - počet prvků s danou vlastností mismatch - první odlišnost dvou sekvencí equal - test shody dvou sekvencí search - první výskyt druhé sekvence v první search_n - první n-násobný výskyt dané hodnoty
STL – Algoritmy
Přehled algoritmůSwap
swap - výměna obsahu dvou objektů• Pomocí parciální/explicitní specializace bývá implementována rychleji,
než kopírování
Modifikace kontejnerů výměnou prvků swap_ranges - výměna obsahu dvou sekvencí (volá swap) iter_swap - výměna obsahu dvou jednoprvkových sekvencí
Modifikace kontejnerů permutací (voláním swap) partition, stable_partition - přemístění prvků s danou vlastností
dopředu random_shuffle - náhodná permutace dle zadaného náhodného
generátoru reverse - otočení posloupnosti rotate, rotate_copy - rotace prvků
STL – Algoritmy
Přehled algoritmůModifikace kontejnerů přiřazením
copy, copy_backward - kopie první sekvence do druhé transform - aplikace zadané unární/binární operace na každý prvek
první/první a druhé sekvence a zapsání výsledku do druhé/třetí sekvence
replace, replace_if - nahrazení prvků s danou vlastností jinou hodnotou
replace_copy, replace_copy_if - kopie s nahrazením fill, fill_n - naplnění sekvence danou hodnotou generate, generate_n - naplnění sekvence z daného generátoru
Modifikace kontejnerů odebráním remove, remove_if - smazání prvků s danou vlastností unique, unique_copy - smazání opakujících se sousedních prvků
• vhodné pro setříděné nebo asociativní kontejnery
Pozor: Tyto funkce neprodlužují ani nezkracují kontejner
STL – Algoritmy
Přehled algoritmůPozor: Algoritmy neprodlužují ani nezkracují kontejner
vector< int> a, b;
a.push_back( 1); a.push_back( 2); a.push_back( 3);
copy( a.begin(), a.end(), b.begin()); // ilegální použití
Pro tyto účely existují "vkládající iterátory" <iterator> obsahuje tyto funkce vracející iterátory
• back_inserter( K) - iterátor vkládající na konec kontejneru K
• front_inserter( K) - iterátor vkládající na začátek kontejneru K
• inserter( K, I) - iterátor vkládající před iterátor I do kontejneru K
tyto iterátory jsou pouze výstupní• lze je použít jako cíl ve funkcích typu copy
copy( a.begin(), a.end(), back_inserter( b));
STL – Algoritmy
Přehled algoritmů min, max - minimum a maximum ze dvou hodnot
Třídění a spol. sort, stable_sort - třídění partial_sort, partial_sort_copy, nth_element - polotovary třídění push_heap, pop_heap, make_heap, sort_heap - operace na haldě min_element, max_element lexicographical_compare next_permutation, prev_permutation
Operace na setříděných kontejnerech lower_bound, upper_bound, equal_range - hledání prvku binary_search - test na přítomnost prvku includes - test podmnožinovosti merge, inplace_merge - sjednocení s opakováním set_union, set_intersection - sjednocení, průnik set_difference, set_symmetric_difference - množinový rozdíl
iostreamiostream
vstupní a výstupní proudy
iostream
#include <iostream>
#include <iomanip>
using namespace std;
f()
{
int X;
double Y;
cin >> X >> Y;
cout << "X = " << hex << setw(4) << X << ", Y = " << Y << endl;
}
Manipulátory hex - šestnáctkový výpis, setw - počet míst
• platí pro daný stream (cout) trvale (do další změny)
endl - vloží oddělovač řádek
stringstream
#include <string>
#include <sstream>
#include <iomanip>
using namespace std;
string f( int a)
{
ostringstream x;
x << "a = " << a;
return x.str();
}
<sstream> - *stringstream – spolupracuje se std::string<strstream> - *strstream – spolupracuje s char *
iostream
Hlavičkové soubory <fstream.h> <fstream> - souborový vstup a výstup (ifstream,
ofstream, fstream) <iomanip.h> <iomanip> - manipulátory pro nastavení parametrů
formátovaného vstupu a výstupu (setw, setprecision, setfill, setbase, ...)
<ios.h> <ios> - základní funkce abstraktního souboru, základní nastavení formátu (hex, left, ...)
<iostream.h> <iostream> - standardní vstup a výstup (cin, cout, cerr)
<istream.h> <istream> - abstraktní vstupní a kombinované médium (istream, iostream)
<ostream.h> <ostream> - abstraktní výstupní médium (ostream) <sstream.h> <sstream> - vnitřní paměť jako médium (istringstream,
ostringstream, stringstream)
iostream
Abstraktní rozhraní
basic_...<T> jsou šablony T = char - 8-bitové znakové sady
• typedef: ios, istream, ostream, iostream, streambuf
T = wchar_t - 16-bitové znakové sady• typedef: wios, wistream, wostream, wiostream, wstreambuf
ios_base
basic_ios<T>
basic_istream<T> basic_ostream<T>
basic_iostream<T>
basic_streambuf<T>
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
iostream
Abstraktní rozhraní
funkce, které potřebují zapisovat, dostávají basic_ostream<T> &
void zapis_neco( ostream & o)
{
o << neco;
}
ios_base
basic_ios<T>
basic_ostream<T>
nastavení formátovače
stav média
přímýa formátovaný
zápis
dědičnost
virtuální dědičnost
iostream
Abstraktní rozhraní
funkce, které potřebují číst, dostávají basic_istream<T> &• pozor: čtení modifikuje stream (posunuje ukazovátko)
void cti_neco( istream & i)
{
i >> neco;
}
ios_base
basic_ios<T>
basic_istream<T>
nastavení formátovače
stav média
příméa formátovanéčtení
dědičnost
virtuální dědičnost
ukazatel
iostream
Abstraktní rozhraní
funkce, které potřebují číst i zapisovat totéž médium,dostávají basic_iostream<T> &
• ukazovátko čtení a zápisu NENÍ společnévoid zmen_neco( iostream & io)
{ io.read( neco, N);
io.write( neco2, N);
}
ios_base
basic_ios<T>
basic_istream<T> basic_ostream<T>
basic_iostream<T>
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
iostream
Abstraktní rozhraní
basic_iostream< T> obsahuje funkce čtení i zápisu nastavení formátu i stav média jsou společné pro čtení i zápis basic_ios< T> zde musí být pouze v jedné instanci dědění basic_ios< T> musí být virtuální
template< class T> class basic_istream : virtual public basic_ios< T>
ios_base
basic_ios<T>
basic_istream<T> basic_ostream<T>
basic_iostream<T>
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
iostream
Médium
Konkrétní médium je implementováno jakopotomek třídy basic_streambuf<T>
Standardní knihovna C++ nabízí:• soubor (to, co umí OS, tedy včetně rour apod.)
• uložení v paměti
Lze implementovat vlastní (např. výstup do okna)
ios_base
basic_ios<T>
basic_istream<T> basic_ostream<T>
basic_iostream<T> basic_...buf<T>
basic_streambuf<T>
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
sstream
Paměťové médium <sstream>
Obálkové třídy ...stringstream<T> zařídí vznik basic_stringbuf<T> Umožňují přístup k médiu jako basic_string<T>
ios_base
basic_ios<T>
basic_istream<T> basic_ostream<T>
basic_iostream<T> basic_stringbuf<T>
basic_istringstream<T> basic_ostringstream<T>
basic_stringstream<T>
basic_streambuf<T>
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 str: přístup k médiu
str: přístup k médiu
fstream
Souborové médium <fstream>, <iostream> (cin, cout, cerr)
Obálkové třídy ...fstream<T> zařídí vznik basic_filebuf<T> Soubor se otevírá(zavírá) metodou open(close) těchto tříd
ios_base
basic_ios<T>
basic_istream<T> basic_ostream<T>
basic_iostream<T> basic_filebuf<T>
basic_ifstream<T> basic_ofstream<T>
basic_fstream<T>
basic_streambuf<T>
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
open, close
open, closeOperační systém
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)
iostream
formátované čteníbasic_istream<T> & operator>>( basic_istream<T> & s, D & x)
operátor přečte několik znaků ve tvaru určeném typem D naplní výstupní parametr x
formátovaný zápisbasic_ostream<T> & operator<<( basic_ostream<T> & s, D x)
operátor vypíše x jako několik znaků ve tvaru určeném typem D
Oba operátory vrací levý operand Tím je umožněno zřetězené použití
s << x << y << z;
je ekvivalentnís << x; s << y; s << z;
iostream
formátované čtení/zápisbasic_istream<T> & operator>>( basic_istream<T> & s, D & x)
basic_ostream<T> & operator<<( basic_ostream<T> & s, D x)
Knihovna istream/ostream implementuje operátory pro typy (unsigned) short, (unsigned) int, (unsigned) long - dec, hex, oct float, double, long double - desetinný a/nebo exponenciální tvar bool, void * - pro ladicí účely char/wchar_t - znak char * / wchar_t * - řetězec v C tvaru
Další typy lze dodefinovat (jako globální operátory) Standardní knihovny C++ je definují pro
• string/wstring - řetězec
• complex - komplexní číslo
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;
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
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
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
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
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)
iostream
Jak fungují manipulátory
cout << "X = " << hex << setw(4) << X << ", Y = " << Y << endl;
Bez parametrů (hex, endl, ...) Definovány jako funkce manipulující s ios_base
• Proto ios_base musí být třída a nikoliv šablona jako basic_ios
ios_base & hex( ios_base & s)
{ s.setf( ios_base::hex);
return s;
}
Akceptovány jako ukazatel na funkci zvláštní verzí operátoru <<
basic_ostream<T> & operator<<(
basic_ostream<T> & s, ios_base & (* f)( ios_base & s))
{ f( s);
return s;
}
iostream
Jak fungují manipulátory
cout << "X = " << hex << setw(4) << X << ", Y = " << Y << endl;
S parametry (setw, setprecision, setfill, ...) Definovány jako funkce vracející speciální třídu
struct setw_manip {
int x;
explicit setw_manip( int p) : x( p) {}
};
setw_manip setw( int p)
{ return setw_manip( p);
}
Akceptovány zvláštními verzemi operátoru <<basic_ostream<T> & operator<<(
basic_ostream<T> & s, const setw_manip & p)
{ s.width( p.x); return s;
}
iostream
Vztah proudů a kontejnerů
stringstream umožňuje přístup k médiu jako string
pro string jsou definovány operátory << a >>
<iterator> definuje iterátory nad proudy:istream_iterator< U>
vstupní iterátor, čte typ U pomocí operátoru >> parametrem konstruktoru je istream &
ostream_iterator< U>
výstupní iterátor, zapisuje typ U pomocí operátoru << parametrem konstruktoru je ostream &
iostream
Iterátory nad proudy - příkladynaplnění kontejneru ze vstupu
std::vector< double> a;
std::copy(
std::istream_iterator< double>( std::cin), // aktuální pozice
std::istream_iterator< double>(), // "konec souboru"
std::back_inserter( a)); // vkládací iterátor kontejneru
vysypání kontejneru na výstup nevýhoda: neodděluje elementy výstupu
std::vector< std::string> b;
std::copy(
b.begin(),
b.end(),
std::ostream_iterator< std::string>( std::cout));
Exception handlingException handling
Mechanismus výjimek
Exception handling
Srovnání: goto Start: příkaz goto Cíl: návěští
Určen při kompilaci Skok může opustit blok
Proměnné korektně zaniknouvoláním destruktorů
Cíl musí být v téže proceduře
int f()
{
if ( something == wrong )
{
goto label;
}
else
{
MyClass my_variable;
if ( anything != good )
{
goto label;
}
/* ... */
}
return 0;
label:
std::cerr
<< "Error"
<< std::endl;
return -1;
}
Exception handling
Srovnání: goto Start: příkaz goto Cíl: návěští
Určen při kompilaci Skok může opustit blok
Proměnné korektně zaniknouvoláním destruktorů
Cíl musí být v téže proceduře
Srovnání 2: <csetjmp> Pro pokročilé Start: volání longjmp Cíl: volání setjmp Skok může opustit proceduru Neřeší lokální proměnné
Nelze použít v C++ Předává hodnotu typu int
int f()
{
if ( something == wrong )
{
goto label;
}
else
{
MyClass my_variable;
if ( anything != good )
{
goto label;
}
/* ... */
}
return 0;
label:
std::cerr
<< "Error"
<< std::endl;
return -1;
}
Exception handling
Mechanismus výjimek Start: příkaz throw Cíl: try-catch blok
Určen za běhu Skok může opustit proceduru
Proměnné korektně zaniknouvoláním destruktorů
Předává hodnotu libovolného typu
Typ hodnoty se podílí na určení cíle skoku
void f()
{
if ( something == wrong )
throw 729;
else
{
MyClass my_variable;
if ( anything != good )
throw 123;
/* ... */
}
}
void g()
{
try {
f();
}
catch ( int e ) {
std::cerr
<< "Exception in f(): "
<< e
<< std::endl;
}
}
Exception handling
Mechanismus výjimek Start: příkaz throw Cíl: try-catch blok
Určen za běhu Skok může opustit proceduru
Proměnné korektně zaniknouvoláním destruktorů
Předává hodnotu libovolného typu
Typ hodnoty se podílí na určení cíle skoku
Obvykle se používají pro tento účel zhotovené třídy
class WrongException { /*...*/ };
class BadException { /*...*/ };
void f()
{
if ( something == wrong )
throw WrongException( something);
if ( anything != good )
throw BadException( anything);
}
void g()
{
try {
f();
}
catch ( const WrongException & e1 ) {
/*...*/
}
catch ( const BadException & e2 ) {
/*...*/
}
}
Exception handling
Mechanismus výjimek Start: příkaz throw Cíl: try-catch blok
Určen za běhu Skok může opustit proceduru
Proměnné korektně zaniknouvoláním destruktorů
Předává hodnotu libovolného typu
Typ hodnoty se podílí na určení cíle skoku
Obvykle se používají pro tento účel zhotovené třídy
Mechanismus výjimek respektuje hierarchii dědičnosti
class AnyException { /*...*/ };
class WrongException
: public AnyException { /*...*/ };
class BadException
: public AnyException { /*...*/ };
void f()
{
if ( something == wrong )
throw WrongException( something);
if ( anything != good )
throw BadException( anything);
}
void g()
{
try {
f();
}
catch ( const AnyException & e1 ) {
/*...*/
}
}
Exception handling
Mechanismus výjimek Start: příkaz throw Cíl: try-catch blok
Určen za běhu Skok může opustit proceduru
Proměnné korektně zaniknouvoláním destruktorů
Předává hodnotu libovolného typu
Typ hodnoty se podílí na určení cíle skoku
Obvykle se používají pro tento účel zhotovené třídy
Mechanismus výjimek respektuje hierarchii dědičnosti
Hodnotu není třeba využívat
class AnyException { /*...*/ };
class WrongException
: public AnyException { /*...*/ };
class BadException
: public AnyException { /*...*/ };
void f()
{
if ( something == wrong )
throw WrongException();
if ( anything != good )
throw BadException();
}
void g()
{
try {
f();
}
catch ( const AnyException &) {
/*...*/
}
}
Exception handling
Mechanismus výjimek Start: příkaz throw Cíl: try-catch blok
Určen za běhu Skok může opustit proceduru
Proměnné korektně zaniknouvoláním destruktorů
Předává hodnotu libovolného typu
Typ hodnoty se podílí na určení cíle skoku
Obvykle se používají pro tento účel zhotovené třídy
Mechanismus výjimek respektuje hierarchii dědičnosti
Hodnotu není třeba využívat Existuje univerzální catch blok
class AnyException { /*...*/ };
class WrongException
: public AnyException { /*...*/ };
class BadException
: public AnyException { /*...*/ };
void f()
{
if ( something == wrong )
throw WrongException();
if ( anything != good )
throw BadException();
}
void g()
{
try {
f();
}
catch (...) {
/*...*/
}
}
Exception handling
Fáze zpracování výjimkyVyhodnocení výrazu v příkaze throw
Hodnota je uložena "stranou"
Stack-unwinding Postupně se opouštějí bloky a funkce, ve kterých bylo provádění
vnořeno Na zanikající lokální a pomocné proměnné jsou volány destruktory Stack-unwinding končí dosažením try-bloku, za kterým je catch-blok
odpovídající typu výrazu v příkaze throw
Provedení kódu v catch-bloku Původní hodnota throw je stále uložena pro případné pokračování:
• Příkaz throw bez výrazu pokračuje ve zpracování téže výjimky počínaje dalším catch-blokem - začíná znovu stack-unwinding
Zpracování definitivně končí opuštěním catch-bloku Běžným způsobem nebo příkazy return, break, continue, goto
• Nebo vyvoláním jiné výjimky
Exception handling
Použití mechanismu výjimek
Vyvolání a zpracování výjimky je relativně časově náročné Používat pouze pro chybové nebo řídké stavy
• Např. nedostatek paměti, ztráta spojení, chybný vstup, konec souboru
Připravenost na výjimky také něco (málo) stojí Za normálního běhu je třeba zařídit, aby výjimka dokázala najít cíl a
zrušit proměnné• Výjimky se týkají i procedur, ve kterých není ani throw, ani try-blok
Většina kompilátorů umí překládat ve dvou režimech "s" a "bez"• Celý spojovaný program musí být přeložen stejně
Exception handling
Standardní výjimky<stdexcept> Všechny standardní výjimky jsou potomky třídy exception
metoda what() vrací řetězec s chybovým hlášením
bad_alloc: vyvolává operátor new při nedostatku paměti V režimu "bez výjimek" new vrací nulový ukazatel
bad_cast, bad_typeid: Chybné použití RTTIOdvozené z třídy logic_error:
domain_error, invalid_argument, length_error, out_of_range vyvolávány např. funkcí vector::operator[]
Odvozené z třídy runtime_error: range_error, overflow_error, underflow_error
Exception handling
Standardní výjimky<stdexcept> Všechny standardní výjimky jsou potomky třídy exception
metoda what() vrací řetězec s chybovým hlášením
bad_alloc: vyvolává operátor new při nedostatku paměti V režimu "bez výjimek" new vrací nulový ukazatel
bad_cast, bad_typeid: Chybné použití RTTIOdvozené z třídy logic_error:
domain_error, invalid_argument, length_error, out_of_range vyvolávány např. funkcí vector::operator[]
Odvozené z třídy runtime_error: range_error, overflow_error, underflow_error
Aritmetické ani ukazatelové operátory na vestavěných typech NEHLÁSÍ běhové chyby prostřednictvím výjimek
např. dělení nulou nebo dereference nulového ukazatele
Exception handling
Typické použití Deklarace výjimek
class AnyException { /*...*/ };
class WrongException
: public AnyException { /*...*/ };
class BadException
: public AnyException { /*...*/ };
Vyvolání výjimekvoid f()
{
if ( something == wrong )
throw WrongException();
if ( anything != good )
throw BadException();
}
Částečné ošetřenívoid g()
{ try {
f();
}
catch (...) {
std::cout << "Exception in g()";
throw;
}
}
Podrobné ošetřeníint main( /*...*/)
{ try {
g();
h();
}
catch ( WrongException ) {
std::cout << "WrongException";
}
catch ( BadException ) {
std::cout << "BadException";
}
}
Exception handling
Použití se std::exception Deklarace výjimek
class WrongException
: public std::exception {
virtual const char * what() const
{ return "WrongException"; }
};
class BadException
: public std::exception {
virtual const char * what() const
{ return "BadException"; }
};
Vyvolání výjimekvoid f()
{
if ( something == wrong )
throw WrongException();
if ( anything != good )
throw BadException();
}
Částečné ošetřenívoid g()
{ try {
f();
}
catch (...) {
std::cout << "Exception in g()";
throw;
}
}
Podrobné ošetřeníint main( /*...*/)
{ try {
g();
h();
}
catch ( const std::exception & e ) {
std::cout << e.what();
}
}
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
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
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čí
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
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é)
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é)
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
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é
Exception-safe programming
Kompilátory samy ošetřují některé výjimkyDynamická 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
Exception-safe programming
Kompilátory samy ošetřují některé výjimkyDynamická 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;
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
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 stavu, ve kterém byla při jejím vyvolání
Nazýváno též "Commit-or-rollback semantics"
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
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
Exception-safe programming
Příklad: String č. 2 operator= Vylepšení:
operator= může vyvolávat výjimku, pokud se přiřazuje neplatná hodnota
Tato výjimka může být definována např. takto:
#include <exception>
class InvalidString
: public std::exception
{
virtual const char * what() const
{ return "Invalid string";
}
}
String & String::operator=(
const String & b)
{
if ( this != & b )
{
delete[] str_;
str_ = 0;
if ( b.str_ )
{
str_ = new char[
strlen( b.str_) + 1];
strcpy( str_, b.str_);
}
else
{
throw InvalidString();
}
}
return * this;
}
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;
}
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;
}
Exception-safe programming
Příklad: String č. 2 operator= Silně bezpečné řešení Pozorování:
Toto řešení je "shodou okolností" imunní protithis == & 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;
}
Exception-safe programming
Příklad: String č. 2 operator= Silně bezpečné řešení Pozorování:
Toto řešení je "shodou okolností" imunní protithis == & 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;
}
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;
}
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);
}
Exception-safe programming
Příklad: String č. 2 operator= Silně bezpečné řešení
Metodu swap je vhodné publikovat ve formě globální funkce
Některé algoritmy nad kontejnery obsahujícími String se tak zrychlí a stanou se bezpečnými vůči výjimkám
Sama metoda swap může využívat šablonu swap pro typ char *
#include <algorithm>
void String::swap( String & x)
{
swap( str_, x.str_);
}
String & String::operator=(
const String & b)
{
String c( b);
swap( c);
return * this;
}
void swap( String & x, String & y)
{
x.swap( y);
}
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();
}
}
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;
}
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;
}
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řittry-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;
}
}
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řittry-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;
}
}
Exception-safe programming
Příklad: StringStack::pop Zásobník prvků typu String Implementován seznamem
Silně bezpečná implementace Jak zrušíme proměnnou p,
když k výjimce nedojde? std::auto_ptr< T>
"chytrý" ukazatel na T, který se chová jako "jediný vlastník objektu":
• po zkopírování se vynuluje • při zániku volá delete
Pozor: auto_ptr má nestandardní copy-constructor a operator=
• modifikují svůj parametr• pro auto_ptr nefungují
kontejnery apod.
#include <memory>
String StringStack::pop()
{
if ( ! top_ )
throw StackEmpty();
std::auto_ptr< Box> p = top_;
top_ = p->next;
try {
return p->v;
}
catch ( ...)
{
top_ = p;
// toto přiřazení nuluje p
throw;
}
}
// při návratu se automaticky zruší * p
// pokud je p nenulové
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 (...)
{
/* ??? */
}
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 */
}
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 */
}
Exception-safe programming
Příklad: StringStack::pop Zásobník prvků typu String Implementován seznamem
Řešení B Lze implementovat nad
řešením A
#include <memory>
class StringStack {
public:
/* A */
String & top();
void pop();
/* B */
void pop( String & out)
{
String & t = top();
swap( out, t);
try {
pop();
}
catch (...) {
swap( out, t);
throw;
}
}
};
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 */
}
Exception specifications
Exception specificationsKompilá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čí
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í */
}
Exception specifications
Exception specificationsKompilátor (a runtime) zajistí, že nepovolená výjimka
neopustí funkci Microsoft Visual C++ 7.0 to ovšem neimplementuje
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
CCastast
Různé druhy přetypování
Přetypování
Různé varianty syntaxeC-style cast
(T)e
Převzato z C
Přetypování
Různé varianty syntaxeC-style cast
(T)e
Převzato z C
Function-style castT(e)
Ekvivalentní (T)e• T musí být syntakticky identifikátor nebo klíčové slovo označující typ
Přetypování
Různé varianty syntaxeC-style cast
(T)e
Převzato z C
Function-style castT(e)
Ekvivalentní (T)e• T musí být syntakticky identifikátor nebo klíčové slovo označující typ
Type conversion operators Pro odlišení účelu (síly a nebezpečnosti) přetypování:
const_cast<T>(e)
static_cast<T>(e)
reinterpret_cast<T>(e)
Přetypování
Různé varianty syntaxeC-style cast
(T)e
Převzato z C
Function-style castT(e)
Ekvivalentní (T)e• T musí být syntakticky identifikátor nebo klíčové slovo označující typ
Type conversion operators Pro odlišení účelu (síly a nebezpečnosti) přetypování:
const_cast<T>(e)
static_cast<T>(e)
reinterpret_cast<T>(e)
Novinka - přetypování s běhovou kontrolou:dynamic_cast<T>(e)
Přetypování
const_cast<T>(e)
Odstranění konstantnosti const U & => U & const U * => U *
Obvykle používáno pro měnění pomocných položek v logicky konstantních objektech
Příklad: Čítač odkazů na logicky konstantní objektclass Data {
public:
void register_pointer() const
{ const_cast< Data *>( this)->references++; }
private:
/* ... data ... */
int references;
};
Jiný příklad: datové struktury s amortizovaným vyhledáváním
Přetypování
const_cast<T>(e)
Odstranění konstantnosti const U & => U & const U * => U *
U moderních překladačů lze nahradit specifikátorem mutable
Příklad: Čítač odkazů na logicky konstantní objektclass Data {
public:
void register_pointer() const
{ references++; }
private:
/* ... data ... */
mutable int references;
};
Přetypování
static_cast<T>(e)
UmožňujeVšechny implicitní konverze
Bezztrátové i ztrátové aritmetické konverze (int <=> double apod.) Konverze přidávající modifikátory const a volatile Konverze ukazatele na void * Konverze odkazu na odvozenou třídu na odkaz na předka:
• Derived & => Base &
• Derived * => Base *
Aplikace copy-constructoru; v kombinaci s implicitní konverzí též:• Derived => Base (slicing: okopírování části objektu)
Aplikace libovolného konstruktoru T::T s jedním parametrem• Uživatelská konverze libovolného typu na třídu T
Aplikace konverzního operátoru: operator T()• Uživatelská konverze nějaké třídy na libovolný typ T
Přetypování
static_cast<T>(e)
UmožňujeVšechny implicitní konverze
Ekvivalentní použití pomocné proměnné tmp deklarované takto:T tmp(e);
Používá se tehdy, pokud se vynucením jedné z možných implicitních konverzí odstraní nejednoznačnost nebo vynutí volání jiné varianty funkce
class A { /* ... */ }; class B { /* ... */ };
void f( A *); void f( B*);
class C : public A, public B { /* ... */
void g() { f( static_cast< A>( this)); }
};
Přetypování
static_cast<T>(e)
UmožňujeVšechny implicitní konverzeKonverze na void - zahození hodnoty výrazu
Používá se v makrech a podmíněných výrazech
Konverze odkazu na předka na odkaz na odvozenou třídu• Base & => Derived &
• Base * => Derived *
Pokud objekt, na nějž konvertovaný odkaz ukazuje, není typu Derived či z něj odvozený, je výsledek nedefinovaný
• K chybě obvykle dojde později!
Konverze celého čísla na výčtový typ Pokud hodnota čísla neodpovídá žádné výčtové konstantě,
výsledek je nedefinovaný
Konverze void * na libovolný ukazatel
Přetypování
static_cast<T>(e)
Nejčastější použitíKonverze odkazu na předka na odkaz na odvozenou třídu
class Base { public: enum Type { T_X, T_Y };
virtual Type get_type() const = 0;
};
class X : public Base { /* ... */
virtual Type get_type() const { return T_X; }
};
class Y : public Base { /* ... */
virtual Type get_type() const { return T_Y; }
};
Base * p = /* ... */;
switch ( p->get_type() ) {
case T_X: { X * xp = static_cast< X *>( p); /* ... */ } break;
case T_Y: { Y * yp = static_cast< Y *>( p); /* ... */ } break;
}
Přetypování
reinterpret_cast<T>(e)
UmožňujeKonverze ukazatele na dostatečně velké celé čísloKonverze celého čísla na ukazatelKonverze mezi různými ukazateli na funkceKonverze odkazu na odkaz na libovolný jiný typ
• U * => V *
• U & => U &
Neuvažuje příbuzenské vztahy tříd, neopravuje hodnoty ukazatelů
Většina použití je závislá na platformě Příklad: Přístup k reálné proměnné po bajtech Typické použití: Čtení a zápis binárních souborů
void put_double( std::ostream & o, const double & d)
{ o.write( reinterpret_cast< char *>( & d), sizeof( double)); }
• Obsah souboru je nepřenositelný
Přetypování
dynamic_cast<T>(e)
UmožňujeKonverze odkazu na odvozenou třídu na odkaz na předka:
• Derived & => Base &
• Derived * => Base *
Implicitní konverze, chová se stejně jako static_cast
Konverze odkazu na předka na odkaz na odvozenou třídu• Base & => Derived &
• Base * => Derived *
Podmínka: Base musí obsahovat alespoň jednu virtuální funkci Pokud konvertovaný odkaz neodkazuje na objekt typu Derived nebo
z něj odvozený, je chování definováno takto:• Konverze ukazatelů vrací nulový ukazatel
• Konverze referencí vyvolává výjimku std::bad_cast
Umožňuje přetypování i v případě virtuální dědičnosti
Přetypování
dynamic_cast<T>(e)
Nejčastější použitíKonverze odkazu na předka na odkaz na odvozenou třídu
class Base { public:
virtual ~Base(); /* alespoň jedna virtuální funkce */
};
class X : public Base { /* ... */
};
class Y : public Base { /* ... */
};
Base * p = /* ... */;
X * xp = dynamic_cast< X *>( p);
if ( xp ) { /* ... */ }
Y * yp = dynamic_cast< Y *>( p);
if ( yp ) { /* ... */ }
RTTIRTTI
Typová informace za běhu
RTTI
Operátor typeidtypeid(T)
Vrací identifikaci typu Ttypeid(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 typuconst std::type_info &
<typeinfo> Lze porovnávat na rovnost Má metodu name() vracející řetězec s nějakou formou jména typu
RTTI
Typické použitíAlternativa místo dynamic_cast
#include <typeinfo>
class Base { public:
virtual ~Base(); /* alespoň jedna virtuální funkce */
};
class X : public Base { /* ... */
};
class Y : public Base { /* ... */
};
Base * p = /* ... */;
if ( typeid( * p) == typeid( X) )
{ X * xp = static_cast< X *>( p); /* ... */ }
if ( typeid( * p) == typeid( Y) )
{ Y * yp = static_cast< Y *>( p); /* ... */ }
RTTI
Typické použitíAlternativa místo dynamic_cast
Pozor: rovnost typeid nereflektuje dědičnost• Zatímco dynamic_cast ano
#include <typeinfo>
class Base { public:
virtual ~Base(); /* alespoň jedna virtuální funkce */
};
class X : public Base { /* ... */
};
class Z : public X { /* ... */
};
Base * p = new Z;
if ( typeid( * p) == typeid( X) ) // neplatí !
{ X * xp = static_cast< X *>( p); /* ... */ }
if ( typeid( * p) == typeid( Z) ) // platí
{ Z * zp = static_cast< Z *>( p); /* ... */ }
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
Kanonické tvary třídKanonické tvary tříd
Kanonické tvary tříd
Neinstanciované třídyPolicy classesTraits templates
Třídy s jednou instancíSingletony
Třídy instanciované jako proměnnéUživatelské datové typy, chytré ukazatele, ...Funktory a podobné parametry šablonVisitory a podobné objekty s virtuálními funkcemi
Třídy instanciované dynamickyPlain Old DataVelká a sdílená dataTřídy s hierarchií dědičnosti
Poznámka: Toto třídění nemůže být ani úplné ani jednoznačné
Kanonické tvary tříd
Neinstanciované třídy Policy classes Traits templates
Obsahují pouze Definice typů (typedef,
případně vnořené třídy) Definice výčtových konstant (a
typů) Statické funkce Statická data
Obvykle vše veřejné (struct)
Nemají konstruktory ani destruktory
Obvykle nevyužívají dědičnost
struct allocation_policy {
static void * alloc( size_t);
static void free( void *);
};
template< typename T>
struct type_traits;
template<>
struct type_traits< char> {
typedef char param_type;
enum { min = 0, max = 255 };
static bool less( char, char);
};
Kanonické tvary tříd
Policy class – použití Univerzální šablona
template< class policy> class BigTree {
/* ... */
Node * new_node()
{ return policy::alloc(sizeof(Node));
}
};
Specifická policy class
struct my_policy {
static void * alloc( size_t);
static void free( void *);
};
Použití
BigTree< my_policy> my_tree;
Traits template – použití Deklarace traits
template< class T>
struct type_traits;
Univerzální šablonatemplate< class T> class Stack {
/* ... */
void push(
typename type_traits::param_t x);
};
Univerzální definice traitstemplate< class T>
struct type_traits {
typedef const T & param_t;
};
Explicitní specializace traitstemplate<>
struct type_traits< char> {
typedef char param_t;
};
Kanonické tvary tříd
Třídy s jednou instancí Singletony
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í
Privátní statická proměnná odkazující na instanci
Veřejná statická funkce zpřístupňující instanci
Většinou nevyužívají dědičnost ani virtuální funkce
/* syslog.h */
class SysLog {
public:
static SysLog & instance()
{ if ( ! pinst_ )
pinst_ = new SysLog;
return * pinst_;
}
void write( const char * s)
{ ::write( s, handle_); }
private:
int handle_;
SysLog() { handle_=::open("syslog");}
~SysLog() { ::close(handle_); }
SysLog( const SysLog &);
void operator=( const SysLog &);
static std::auto_ptr<SysLog*> pinst_;
};
/* syslog.cpp */
std::auto_ptr<SysLog*> SysLog::pinst_;
Kanonické tvary tříd
Třídy instanciované jako proměnné
Jednoduché datové typy Neobsahují dynamicky
alokovaná data
Konstruktor bez parametrů Zajišťuje definovanou iniciální
hodnotu Copy-constructor, operator=,
destruktor se nedefinují Implementace kompilátorem
vyhovuje
Unární a modifikující binární operátory jako metody
Nemodifikující binární operátory jako globální funkce
class Complex {
public:
double re, im;
Complex();
Complex operator-() const;
Complex & operator+=(
const Complex &);
};
Complex operator+(
const Complex &, const Complex &);
Kanonické tvary tříd
Třídy instanciované jako proměnné
Složitější datové typy
Konstruktor bez parametrů Copy-constructor operator= Destruktor
Unární a modifikující binární operátory jako metody
Nemodifikující binární operátory jako globální funkce
Obvykle musejí být friend Data a pomocné funkce
privátní
Dědičnost nemá smysl
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
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));
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 &);
/* ... */
};
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 &);
/* ... */
};
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 &);
/* ... */
};
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 &);
/* ... */
};
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 &);
/* ... */
};
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< AbstractObject *>
my_vector_;
my_vector_ elements_;
};
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_;
};
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;
};
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
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
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
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
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 */;
};
Design PatternsDesign Patterns
Návrhové vzory
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
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, ...)
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"
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
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í
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
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
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
ŠablonyŠablony
Hlubší pohled
Šablony tříd – triky
Specializace metody pro konkrétní typ Překrývá generické tělo
template< class T> class X {
/* ... */
void f(/*...*/);
};
template< class T> void X< T>::f(/*...*/)
{ /*... generické tělo pro libovolný typ T ... */}
template<> void X< int>::f(/*...*/)
{ /*... speciální tělo pro typ int ... */}
Šablony tříd – triky
Specializace metody pro konkrétní typ Překrývá generické tělo
template<> void X< int>::f(/*...*/)
{ /*... speciální tělo pro typ int ... */}
Starší verze C++ zde nepoužívají prefix template<>
void X< int>::f(/*...*/)
{ /*... speciální tělo pro typ int ... */}
Šablony tříd – triky
Specializace metody pro konkrétní typ Překrývá generické tělo
template<> void X< int>::f(/*...*/)
{ /*... speciální tělo pro typ int ... */}
Tato konstrukce se nazývá Explicitní specializace šablony funkce
Šablony tříd – triky
Specializace metody pro konkrétní typ Překrývá generické tělo
template<> void X< int>::f(/*...*/)
{ /*... speciální tělo pro typ int ... */}
Tato konstrukce se nazývá Explicitní specializace šablony funkce
Existují též: Explicitní specializace celé třídy Parciální specializace funkce nebo celé třídy Explicitní instanciace
Později...
Šablony tříd – triky
Specializace metody pro konkrétní typ Překrývá generické tělo
template<> void X< int>::f(/*...*/)
{ /*... speciální tělo pro typ int ... */}
Tato konstrukce se nazývá Explicitní specializace šablony funkce
Existují též: Explicitní specializace celé třídy Parciální specializace celé třídy Explicitní instanciace
Později...
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 X, class Y> class C; // základní deklarace
template< class P, class Q> class C< P *, Q *> { // specializace
bool cmp( P *, Q *);
};
template< class Z> class C< Z, Z> : public Z { // jiná specializace
bool set( Z &);
};
template< class X, class Y> class C { // základní definice
X add( Y);
};
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 T, class U, int n> class C< T[n], U[n]>
{ /* specializace pro dvě pole stejné velikosti */ };
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 T, class U, int n> class C< T[n], U[n]>
{ /* specializace pro dvě pole stejné velikosti */ };
Krajním případem parciální specializace je explicitní specializace
Explicitní specializace template<> class C< char, int[ 8]> { /* ... */ };
Explicitní specializace šablony není šablona Podléhá trochu jiným (jednodušším) pravidlům
• Překlad se neodkládá
• Těla metod se nepíší do hlavičkových souborů
Parciální specializace
Typická použití parciální a explicitní specializaceVýhodnější implementace ve speciálních případech
Šablona je používána přímo z nešablonovaného kódu Programátor - uživatel šablony o specializaci nemusí vědět Příklad: Implementace vector<char> může být jednodušší
Parciální specializace
Typická použití parciální a explicitní specializaceVýhodnější implementace ve speciálních případech
Šablona je používána přímo z nešablonovaného kódu Programátor - uživatel šablony o specializaci nemusí vědět Příklad: Implementace vector<char> může být jednodušší
Mírná změna rozhraní ve speciálních případech Š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< bool> nedovoluje vytvořit ukazatel na jeden prvek
Parciální specializace
Typická použití parciální a explicitní specializaceVýhodnější implementace ve speciálních případech
Šablona je používána přímo z nešablonovaného kódu Programátor - uživatel šablony o specializaci nemusí vědět Příklad: Implementace vector<char> může být jednodušší
Mírná změna rozhraní ve speciálních případech Š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< bool> nedovoluje vytvořit ukazatel na jeden prvek
Modifikace chování jiné šablony Specializovaná šablona je volána z jiného šablonovaného kódu Autor volající šablony předpokládá, že ke specializaci dojde
• Někdy dokonce ani nedefinuje základní obsah volané šablony
Autor specializace tak upravuje chování volající šablony Příklad: šablona basic_string<T> volá šablonu char_traits<T>, ve
které je např. definována porovnávací funkce
Parciální specializace
Modifikace chování jiné šablony Specializovaná šablona je volána z jiného šablonovaného kódu Autor volající šablony předpokládá, že ke specializaci dojde
• Někdy dokonce ani nedefinuje základní obsah volané šablony
Autor specializace tak upravuje chování volající šablony Příklad: šablona basic_string<T> volá šablonu char_traits<T>, ve
které je např. definována porovnávací funkce
template< class T> struct char_traits;
template< class T> class basic_string { /* ... */
int compare( const basic_string & b) const
{ /*...*/ char_traits< T>::compare( /* ... */) /*...*/ }
};
template<> struct char_traits< char> { /* ... */
static int compare(const char* s1, const char* s2, size_t n)
{ return memcmp( s1, s2, n); }
};
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 objektyObsahují pouze:
Definice typů Statické funkce
Určeny k doplnění informací o nějakém typu Příklad: char_traits<T> doplňuje informace o typu T, např.
porovnávací funkci
Traits & policies
TraitsŠablony, ze kterých nejsou vytvářeny objektyObsahují pouze:
Definice typů Statické funkce
Určeny k doplnění informací o nějakém typu Příklad: char_traits<T> doplňuje informace o typu T, např.
porovnávací funkci
Policy classesTřídy, ze kterých obvykle nejsou vytvářeny objektyPředávány jako parametr šablonám
Defaultní hodnotou parametru často bývá šablona traits
Určeny k definování určitého chování Příklad: Alokační strategie
typename
Důsledky specializací Různé specializace téže šablony mohou mít naprosto odlišná těla Odkazuje-li se jedna šablona na jinou, nelze bez znalosti jejích
parametrů rozhodnout, které identifikátory jsou deklarovány a jak• Příklad: při kompilaci basic_string<T> není známo, co znamená
char_traits< T>::compare
Problém se týká tzv. závislých jmen, obsahujících jméno parametru šablony
• Příklady: T::x, C<T>, C<T>::x
K úspěšné syntaktické analýze je v C++ nutné odlišit jména typů od ostatních jmen:
bflm< T>::psvz( * x);
• volání statické funkce psvz nebo deklarace ukazatele na typ psvz?
V těle šablony je nutno všechna závislá kvalifikovaná jména označující typy opatřit klíčovým slovem typename
typename bflm< T>::psvz( * x); // deklarace
• Nesmí se používat v deklaraci předků šablony
• Nesmí se používat mimo šablony
Triky s šablonami
Porovnání typů s booleovským výstupemtemplate< class A, class B>
struct Equal {
enum { value = false };
};
template< class A>
struct Equal< A, A> {
enum { value = true };
};
Equal< X, Y>::value je konstantní výraz
Použitítemplate< class T1>
class Test {
enum { T1_is_int = Equal< int, T1>::value};
enum { T1_is_long = Equal< long, T1>::value};
/* ... */
};
Triky s šablonami
Porovnání typů s typovým výstupemtemplate< class A, class B, class C, class D>
struct IfEqual {
typedef D Result;
};
template< class A, class C, class D>
struct Equal< A, A, C, D> {
typedef C Result;
};
IfEqual< X, Y, U, V>::Result je typ• Význam: X == Y ? U : V
Použitítemplate< class T1>
class Test {
typedef Equal< T1, unsigned, unsigned long, long>::Result longT1;
/* ... */
};
Triky s šablonami
Kompilační ověření invariantutemplate< int x>
struct AssertNot;
template<>
struct AssertNot< 0> {
enum { value = true };
};
template< int x>
struct Assert {
enum { value = AssertNot< ! x>::value };
};
Triky s šablonami
Kompilační ověření invariantutemplate< int x>
struct AssertNot;
template<>
struct AssertNot< 0> {
enum { value = true };
};
template< int x>
struct Assert {
enum { value = AssertNot< ! x>::value };
};
Použitítemplate< int N> class Array {
enum { check = Assert< (N > 0)>::value };
/* ... */
};
Array< -3> x;
• error C2027: use of undefined type 'AssertNot<x>' with [x=1]
Teoretický pohled na šablony
Šablona třídy je kompilátorem vyhodnocovaná funkce f : Ti T
T je množina všech typů zkonstruovatelných v jazyce C
Teoretický pohled na šablony
Šablona třídy je kompilátorem vyhodnocovaná funkce f : Ti T
T je množina všech typů zkonstruovatelných v jazyce C
f : Ti × Kj T šablona s celočíselnými parametry K je množina všech celočíselných konstant
Teoretický pohled na šablony
Šablona třídy je kompilátorem vyhodnocovaná funkce f : Ti T
T je množina všech typů zkonstruovatelných v jazyce C
f : Ti × Kj T šablona s celočíselnými parametry K je množina všech celočíselných konstant
f : Ti × Kj Tm × Kn
výsledná třída může obsahovat typy a konstanty
Teoretický pohled na šablony
Šablona třídy je kompilátorem vyhodnocovaná funkce f : Ti × Kj Tm × Kn
Taková funkce může být definovánaJedním předpisem
Základní šablonou
Po částech Parciálními specializacemi šablony
V jednotlivých bodech Explicitními specializacemi šablony
Teoretický pohled na šablony
Příklad
template< int N>
struct Fact {
enum { value = Fact< N-1>::value * N };
};
template<>
struct Fact< 0> {
enum { value = 1 };
};
Teoretický pohled na šablony
Příklad
template< int N>
struct Fact {
enum { value = Fact< N-1>::value * N };
};
template<>
struct Fact< 0> {
enum { value = 1 };
};
Použití
enum { N = 10 };
int permutations[ Fact< N>::value];
Teoretický pohled na šablony
Příklad
template< int N>
struct Fact {
enum { value = Fact< N-1>::value * N };
};
template<>
struct Fact< 0> {
enum { value = 1 };
};
Kontrolní otázka: Kolik je Fact< -1>::value
Teoretický pohled na šablony
Příklad
template< int N>
struct Fact {
enum { value = Fact< N-1>::value * N };
};
template<>
struct Fact< 0> {
enum { value = 1 };
};
Kontrolní otázka: Kolik je Fact< -1>::value MS Visual C++ 7.1:
• fatal error C1202: recursive type or function dependency context too complex
Řetěz instanciací Fact< -1>, Fact< -2>, Fact< -3>, ... způsobí přetečení tabulek kompilátoru
Teoretický pohled na šablony
Jiný příklad
template< int N>
struct Fib {
enum { value = Fib< N-1>::value + Fib< N-2>::value };
};
template<> struct Fib< 0> {
enum { value = 1 };
};
template<> struct Fib< 1> {
enum { value = 1 };
};
Kontrolní otázka: Jak dlouho trvá výpočet (tj. kompilace) Fib< 1000>::value
Teoretický pohled na šablony
Jiný příklad
template< int N>
struct Fib {
enum { value = Fib< N-1>::value + Fib< N-2>::value };
};
template<> struct Fib< 0> {
enum { value = 1 };
};
template<> struct Fib< 1> {
enum { value = 1 };
};
Kontrolní otázka: Jak dlouho trvá výpočet (tj. kompilace) Fib< 1000>::value MS Visual C++ 7.1: Build Time 0:00 Kompilátory ukládají již vytvořené instanciace a nepočítají je znovu
******Nepoužité slajdyNepoužité slajdy******
Teoretický pohled na šablony
Triky s typovým konstrukcemiSeznam typů
template< class H, class R>
struct List {
typedef H Head; typedef R Rest;
};
struct EmptyList {};
Použití
typedef List< char *,
List< const char *,
List< std::string,
EmptyList> > > StringTypes;
Teoretický pohled na šablony
Triky s typovým konstrukcemiSeznam typů
template< class H, class R>
struct List {
typedef H Head; typedef R Rest;
};
struct EmptyList {};
Jiné použití
struct Apple {}; struct Pear {}; struct Plum {};
typedef List< Apple,
List< Pear,
List< Plum,
EmptyList> > > Fruits;
Teoretický pohled na šablony
Triky s typovým konstrukcemiSeznam typů
template< class H, class R>
struct List {
typedef H Head; typedef R Rest;
};
struct EmptyList {};
Funkce na seznamu typůtemplate< class L> struct First {
typedef typename L::Head Result;
};
Teoretický pohled na šablony
Triky s typovým konstrukcemiSeznam typů
template< class H, class R>
struct List {
typedef H Head; typedef R Rest;
};
struct EmptyList {};
Funkce na seznamu typůtemplate< class L> struct First {
typedef typename L::Head Result;
};
struct NullType {};
template<> struct First< EmptyList> {
typedef NullType Result;
};
Teoretický pohled na šablony
Triky s typovým konstrukcemiSeznam typů
template< class H, class R>
struct List {
typedef H Head; typedef R Rest;
};
struct EmptyList {};
Funkce na seznamu typůtemplate< class L, int n> struct Nth {
typedef typename Nth< typename L::Rest, n-1>::Result Result;
};
template< class L> struct Nth< L, 0> {
typedef typename L::Head Result;
};
Teoretický pohled na šablony
Triky s typovým konstrukcemiJiná implementace seznamu typů
template< class H, class R>
struct List;
struct EmptyList;
Funkce na seznamu typůtemplate< class L, int n> struct Nth;
template< class H, class R, int n> struct Nth< List< H, R>, n> {
typedef typename Nth< R, n-1>::Result Result;
};
template< class H, class R> struct Nth< List< H, R>, 0> {
typedef H Result;
};
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ř
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;
}
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;
}
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řittry-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;
}
}
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řittry-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;
}
}
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
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
Preprocesor a překlad
Code...pushcall ...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 <stdio.h>#include <conio.h>
int main( int argc, char * * argv){ printf( “Hello\n”); getch(); return 0;}
conio.hint getch();int putch();/*...*/
Spojování modulů
Code...pushcall ...call...ret
Import_printf_getch
Export_main
Data‘H’,’e’,’l’,’l’,’o’,10,0
hello.o
Code...call ...call
Import_main_exit
Exportentry point
Data
cstart.o
Code...
Import
Export_printf
Data
printer.o
Code...
Import
Export_getch
Data
creader.o
Codesyscall ...
Import
Export_exit
Data
exit.o
Spustitelný program a spuštěný proces
...pushcall ...call...ret
‘H’,’e’,’l’,’l’,’o’,10,0
hello
...call ...call
entry point
...
...
syscall ...
Code
Data
Code
Data
00000000
Heap
Stack
Adresový prostorIP
R0R1...
FFFFFFFF
***** 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é konverzeDrobná vylepšení
Striktnější typová kontrola oproti C
new a delete Komentáře
ŠablonyZpracování výjimekKnihovny
Streams
Virtuální funkce – Specializace datové strukturyclass 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);
};
Virtuální funkce – Užití datové strukturyclass 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;
};
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;
}
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;
}
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 };
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;
}
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;
}
Statické a dynamické volání virtuálních metodvoid 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) */
}
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);
};
/* ... */
};
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;
};
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;
};
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;
};
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;
};
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;
}
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;
};
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();
}
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());
}
String č. 6 - Složené tělo
int StringBodyConcatenate::length()
{ return _a->length() + _b->length();
}
void StringBodyConcatenate::copy( char * dst)
{ _a->copy( dst);
_b->copy( dst + _a->length());
}
char StringBodyConcatenate::read_at( int i)
{ return i < _a->length()
? _a->read_at( i)
: _b->read_at( i - _a->length());
}
void StringBodyConcatenate::write_at( int i, char ch)
{ i < _a->length()
? _a->write_at( i, ch)
: _b->write_at( i - _a->length(), ch);
}
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 < 1000000; i++ )
x = x + "a";
}
Řešení 1
Nahradit rekurzi pomocnou strukturou a cyklemtypedef std::stack< StringBody *> KillStack;
inline void StringBody::dec()
{
if ( ! --count )
killMe();
}
void StringBody::killMe()
{ KillStack toBeKilled;
StringBody * b = this;
for (;;)
{
b->prepareToDie( toBeKilled);
delete b;
if ( toBeKilled.empty() )
break;
b = toBeKilled.top();
toBeKilled.pop();
}
}
Ř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);
}
};
Řešení 1
Pomocná strukturatypedef std::stack< StringBody *> KillStack;
Nevýhody: Časová náročnost operací struktury není zanedbatelná
• K rozsáhlému mazání může dojít v rámci téměř každé operace
Práce se strukturou vyžaduje alokaci• Deadlock: Ke zrušení dat je třeba naalokovat další
• Exception-safety: Destruktor nemá vyvolávat výjimky
Ř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.
Problém
Problém: Implementace mnoha funkcí je rekurzivní
void StringBodyConcatenate::copy( char * dst) const
{ _a->copy( dst);
_b->copy( dst + _a->length());
}
Ř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;
}
Řešení ?
Problém: Implementace funkce copySomething je stále rekurzivní
int StringBodyConcatenate::copySomething(
char * dst, int offset) const
{
if ( offset < _a->length() )
return _a->copySomething( dst, offset);
else
return _b->copySomething(
dst + _a->length(), offset - _a->length());
}
Ř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);
}
}
Ř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 poziciclass StringBody {
/* ... */
virtual const StringBody * redirect( int offset) const
{
return 0; // no redirection
}
};
const StringBody * StringBodyConcatenate::redirect( int offset) const
{
if ( offset < _a->length() )
return _a;
else
return _b;
}
Ř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 );
}
Řešení ?
Pyrrhovo vítězství: Rekurze je odstraněna Algoritmus má nyní kvadratickou složitost
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
STL – vector
template<class T, class A = allocator<T> > class vector {
public:
typedef A allocator_type;
typedef A::size_type size_type;
typedef A::difference_type difference_type;
typedef A::reference reference;
typedef A::const_reference const_reference;
typedef A::value_type value_type;
typedef /*...*/ iterator;
typedef /*...*/ const_iterator;
typedef /*...*/ reverse_iterator;
typedef /*...*/ const_reverse_iterator;
explicit vector(const A& al = A());
explicit vector(size_type n, const T& v = T(), const A& al = A());
vector(const vector& x);
vector(const_iterator first, const_iterator last, const A& al = A());
/* ... */
STL – vector
/* template<class T, class A = allocator<T> > class vector { ... */
iterator begin();
const_iterator begin() const;
iterator end();
iterator end() const;
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
reverse_iterator rend();
const_reverse_iterator rend() const;
size_type size() const;
bool empty() const;
reference at(size_type pos);
const_reference at(size_type pos) const;
reference operator[](size_type pos);
const_reference operator[](size_type pos) const;
/* ... */
STL – vector
/* template<class T, class A = allocator<T> > class vector { ... */
reference front();
const_reference front() const;
reference back();
const_reference back() const;
void push_back(const T& x);
void pop_back();
void assign(const_iterator first, const_iterator last);
void assign(size_type n, const T& x = T());
iterator insert(iterator it, const T& x = T());
void insert(iterator it, size_type n, const T& x);
void insert(iterator it, const_iterator first, const_iterator last);
iterator erase(iterator it);
iterator erase(iterator first, iterator last);
void clear();
void swap(vector x);
/* ... */
STL – vector
/* template<class T, class A = allocator<T> > class vector { ... */
protected:
A allocator;
};
STL – vector – naivní implementace – hlavička
template<class T, int delta> class oriented_iterator;
template<class T, int delta> class const_oriented_iterator;
template<class T> class vector {
public:
typedef unsigned int size_type;
typedef int difference_type;
typedef T & reference;
typedef const T & const_reference;
typedef T value_type;
typedef oriented_iterator< T, 1> iterator;
typedef const_oriented_iterator< T, 1> const_iterator;
typedef oriented_iterator< T, -1> reverse_iterator;
typedef const_oriented_iterator< T, -1> const_reverse_iterator;
/* ... */
private:
size_type _n;
T * _p;
};
STL – vector – naivní implementace – přístup
template< class T> reference vector< T>::at(size_type pos)
{ return pos >= 0 && pos < n ? _p[ pos] : _dummy(); }
template< class T> const_reference vector< T>::at(size_type pos) const
{ return pos >= 0 && pos < n ? _p[ pos] : _dummy(); }
template< class T> reference vector< T>::operator[](size_type pos)
{ return at( pos); }
template< class T> const_reference vector< T>::operator[](size_type pos) const
{ return at( pos); }
template< class T> reference vector< T>::dummy() const
{ return *(T*)0; }
STL – vector – naivní implementace - konstrukce
template< class T> vector< T>::vector()
{ _n = 0; _p = 0; }
template< class T> vector< T>::vector( size_type n, const T & v)
{
_n = n;
_p = new T[ _n]; /* nevolat konstruktory !!! */
for ( int i = 0; i < _n; i++)
{ /* zavolat konstruktory !!! */
/* _p[ i].T( v); */
}
}
template< class T> vector< T>::vector( const vector & x)
{
_n = x._n;
_p = new T[ _n]; /* nevolat konstruktory !!! */
for ( int i = 0; i < _n; i++)
{ /* zavolat konstruktory !!! */
/* _p[ i].T( x._p[ i]); */
}
}
STL – vector – naivní implementace – push/pop
template< class T> void vector< T>::push_back(const T& x)
{
T * p = _p;
_n = _n + 1;
_p = new T[ _n + 1]; /* nevolat konstruktory !!! */
for ( int i = 0; i < _n - 1; i++)
{ /* zavolat konstruktory !!! */
/* _p[ i].T( p[ i]); */
}
}
template< class T> void vector< T>::pop_back()
{
/* ... */
}
STL – vector – vylepšená implementace - konstrukce
void * operator new( size_t s, void * p)
{ return p; }
template< class T> vector< T>::vector( size_type n, const T & v)
{
_n = n;
_p = reinterpret_cast< T *>( new char[ _n * sizeof( T)]);
for ( int i = 0; i < _n; i++)
{
new( _p[ i]) T( v);
}
}
template< class T> vector< T>::~vector()
{
for ( int i = 0; i < _n; i++)
{
_p[ i]->T::~T();
}
delete[] reinterpret_cast< char *>( _p);
}
STL – vector – naivní implementace – iterátory
template< class T, int delta> class oriented_iterator {
public:
T & operator *() const
{ return _i < _v->_n ? *( T *)0 : _v->_p[ _i];
}
const oriented_iterator< T, delta> & operator ++() /* prefix */
{ if ( _i < _v->_n ) _i++;
return * this;
}
oriented_iterator< T, delta> operator ++( int) /* postfix */
{ oriented_iterator< T, delta> old = * this;
operator ++(); return old;
}
bool operator ==( const oriented_iterator< T, delta> & b) const
{ return _i == _b->_i; }
private:
friend class vector< T>;
oriented_iterator( vector< T> * v, int i) { _v = v; _i = i; }
vector< T> * _v;
int _i;
};
STL – vector – naivní implementace – iterátory
template< class T> iterator vector< T>::begin()
{ return iterator( this, 0); }
template< class T> const_iterator vector< T>::begin() const
{ return const_iterator( this, 0); }
template< class T> iterator vector< T>::end()
{ return iterator( this, _n); }
template< class T> iterator vector< T>::end() const
{ return const_iterator( this, _n); }
Operátory new a delete
Operátory new a deletemohou 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);
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 konstruktoruX * p = new X(a,b,c);
(nekorektní) ekvivalentX * p = (X*)X::operator new(sizeof(X));
if (p) p->X::X(a,b,c); // zakázáno
Vztah operátoru delete a destruktorudelete p;
ekvivalentif (p) p->X::~X(); // povoleno
X::operator delete((void*)p);
Š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řídytemplate< class T> class X
{ /* ... */
void f(/*...*/);
};
Nepublikovaný hlavičkový soubor XBody.h Generická těla metod
#include "X.h"
template< class T> void X< T>::f(/*...*/)
{ /*...*/ }
Knihovní modul XBodyInt.cpp Instanciace pro typ int
#include "XBody.h"
template X< int>;