V minulΘm dφlu jsme naΦali Üablony, dnes budeme pokraΦovat a probereme si tzv. specializaci Üablon. Potom bude nßsledovat odstaveΦek v∞novan² chybßm, seznßmφme se s mechanizmem v²jimek jazyka C++.
Pokud chceme uvnit° Üablony t°φdy mφt statick² atribut, nap°. pro poΦφtßnφ vytvo°en²ch instancφ, musφme - podobn∞ jako p°i definici metody - pou₧φt plnΘho zßpisu:
template<class T> class Test {
private:
static int pocet;
public:
// Metody
Test() { pocet++; }
~Test() { pocet--; }
static VypisPocet() { cout << pocet << endl; }
};
template<class T> int Test<T>::pocet = 0;
Lze takΘ p°i°adit Üablon∞ zalo₧enΘ na n∞jakΘm typu jinou implicitnφ hodnotu. Pokud chceme aby nap°. vÜechny Üablony, kterΘ budou mφt typov² parametr int, zaΦφnaly se statickou prom∞nnou pocet na hodnot∞ 5 m∙₧eme napsat:
int Test<int>::pocet = 5;
Ukß₧eme si p°φklad funkce main():
int main(int argc, char* argv[])
{
Test<int> mujtest;
Test<float> mujtest2;
Test<int>::VypisPocet();
// vypise 6 - pocet trid test s hodnotami
int
Test<float>::VypisPocet();
// vypise 1 - pocet trid test s hodnotami
float
char c;
cin >> c;
return 0;
}
Pokud pot°ebujeme Üablonu, kterß by m∞la pracovat s jednφm nebo vφce r∙zn²mi typy ÜablonovΘho parametru jinak, pom∙₧e nßm prßv∞ specializace. Lze tedy vytvo°it zßkladnφ Üablonu a pak nap°φklad pro typ char* upravit n∞kterΘ metody. To se m∙₧e hodit, jestli₧e chceme vytvo°it funkci pro porovnßvßnφ dvou typ∙. U Φφsel mßme jednoznaΦn∞ urΦenΘ uspo°ßdßnφ, ale pro °et∞zce bychom mohli specializovat tuto Üablonovou funkci tak, aby vracela slova v po°adφ, v jakΘm jsou uvedena ve slovnφku. Ukß₧eme si jin² p°φklad, budeme specializovat t°φdy, ale to samΘ lze provΘst i pro ÜablonovΘ funkce:
#include <iostream.h>
#include <string.h>
template<class T> class Container {
private:
T m_pole[20];
public:
Container();
VlozDoPole(int _index, T _prvek);
T VyberZPole(int _index);
};
template<class T> Container<T>::Container()
{
for(int i = 0; i < 20; i++)
{
m_pole[i] = 0;
// inicializace
}
};
template<class T> Container<T>::VlozDoPole(int _index, T _prvek)
{
if(_index < 20) // Jsme-li v poli
{
m_pole[_index] = _prvek;
}
};
template<class T> T Container<T>::VyberZPole(int _index)
{
if(_index < 20)
{
return m_pole[_index];
}
else
{
return 0;
}
};
Pokud budeme chtφt pou₧φt t°φdu Container k uchovßnφ prom∞nn²ch typu int bude vÜe v po°ßdku. Co se ale stane, pokud se rozhodneme uklßdat °et∞zce nßsledujφcφm zp∙sobem:
Container<char *> stringCont;
char* retezec = new char[5];
// jeden znak na '\0'
strcpy(retezec, "TEST");
stringCont.VlozDoPole(0, retezec);
Metoda stringCont::VlozDoPole() p°i°adφ do pole ukazatel na °et∞zec "TEST", problΘm je v tom, ₧e pokud nynφ zm∞nφme hodnotu prom∞nnΘ retezec, nebo ji dokonce pomocφ delete sma₧eme, tak se zm∞ny promφtnou i do naÜφ instance t°φdy Container. To samoz°ejm∞ nechceme, v prvnφm p°φpadu by doÜlo k p°epsßnφ ulo₧en²ch dat. Ve druhΘm by v nejhorÜφm p°φpadu mohlo dojφt k p°φstupu k pam∞ti, kterß nenφ naÜe. Pom∙₧eme si tedy nap°φklad specializacφ Üablony Container pro typ char*:
template<> class Container<char*> {
private:
char* m_pole[20];
public:
Container();
~Container();
VlozDoPole(int _index, char* _prvek);
char* VyberZPole(int _index);
};
template<typename> Container<char*>::Container()
{
for(int i = 0; i < 20; i++)
{
m_pole[i] = NULL;
}
}
template<> Container<char*>::~Container()
{
for(int i = 0; i < 20; i++)
{
delete m_pole[i];
// uvolnime pamet
}
}
template<> Container<char*>::VlozDoPole(int _index, char* _prvek)
{
if(_index < 20)
{
int ilen = strlen(_prvek) + 1;
// znak '\0'
m_pole[_index] = new char[ilen];
if(m_pole[_index])
{
strcpy(m_pole[_index], _prvek);
}
}
}
template<> char* Container<char*>::VyberZPole(int _index)
{
if(_index < 20)
{
return m_pole[_index];
}
else
{
return NULL;
}
}
Vidφme, ₧e specializace se provede pomocφ zßpisu:
template<> class Container<typ> {
... }
// v deklaraci tridy
template<> navrat_typ JmenoTridy<typ>::JmenoMetody(parametry)
{ }
// u metod
Pokud by nßs napadlo, ₧e bychom provedli specializaci pro ukazatele na T, museli bychom napsat nßsledujφcφ:
template<class T> class Container<T*> {
... }
// v deklaraci tridy
template
Bohu₧el takto definovanß Üablona se v p°ekladaΦi Microsoft Visual C++ 6.0 nep°elo₧φ, nep°elo₧φ se ani v nejnov∞jÜφm produktu firmy Microsoft - Visual Studiu .NET. Ale nap°φklad pod Borland C++ Builderem verze 4.0 to p°elo₧it jde, ukß₧eme si jen jak by t°φda byla definovßna a jednu metodu:
template<class T> class Container<T*> {
private:
T* m_pole[20];
public:
Container();
~Container();
VlozDoPole(int _index, T* _prvek);
T* VyberZPole(int _index);
};
template<class T> Container<T*>::Container()
{
for(int i = 0; i < 20; i++)
{
m_pole[i] = NULL;
}
}
V n∞kter²ch starÜφch p°ekladaΦφch se m∙₧eme setkat se zßpisem specializace nßsledujφcφm zp∙sobem:
class JmenoTridy<int*> { }
P°edpoklßdejme, ₧e Container je objektovß Üablona z p°edchozφho odstavce, doposud jsme instance Üablony vytvß°eli nßsledujφcφm zp∙sobem:
Container<int> intCont;
Jazyk C++ nßm ale nabφzφ jeÜt∞ jinou mo₧nost, explicitnφ vytvo°enφ instance Üablony. Provede se to takto:
template class Container<int>;
Kdy₧ pou₧ijeme v programu Üablonu
vytvo°enou prvnφm zp∙sobem, p°ekladaΦ pro ni nemusφ vytvo°it ·pln∞ vÜechny
metody. P°ekladaΦ jazyka C++ toti₧ p°elo₧φ jen ty funkce kterΘ opravdu v
programu zavolßme. Existuje pßr v²jimek, nap°. virtußlnφ metody se vytvß°ejφ
v₧dy.
Pokud vytvo°φme Üablonu druh²m zp∙sobem, tedy explicitn∞, dojde k
vytvo°enφ vÜech metod.
Instance Üablony jsou rovnocennΘ s klasicky vytvo°en²mi t°φdami, lze je tedy pou₧φt jako p°edky odvozenΘ t°φdy. Zdrojov² k≤d v²Üe uvedenΘho p°φkladu naleznete v sekci Downloads (projekt Specialiazace).
Chvilku se zamyslete, jak by byl sv∞t krßsn², kdyby chyby prost∞ neexistovaly. U₧ivatelΘ by v₧dy zadali na vstupu sprßvnΘ ·daje, spojenφ dvou poΦφtaΦ∙ p°es sφ¥ by se nikdy nep°eruÜilo a hlavn∞: programy by neobsahovaly ani jednu chybu. No a te∩ hurß zp∞t do reality, kde ka₧d², i sebelΘpe napsan², program obsahuje hromady chyb. Nenφ se Φemu divit, u₧iteΦnΘ programy jsou v∞tÜinou p∞kn∞ slo₧itΘ. Nejprve si povφme, jakΘ mßme mo₧nosti detekce chyb a pak se podφvßme na jazykem C++ nabφzenou obsluhu v²jimek.
Nejprve je nutnΘ rozliÜit na chyby, kterΘ m∙₧eme rovnou oÜet°it, proto₧e znßme dostatek informacφ o vzniklΘm problΘmu. Ale existujφ i chyby, u nich₧ nevφme, proΦ p°esn∞ vznikly a je t°eba poslat zprßvu o chyb∞ do ÜirÜφho kontextu (nap°. volajφcφ funkci), kde u₧ jsou pot°ebnΘ informace k dispozici. Prvnφm druhem chyb se nebudeme zab²vat, proto₧e jdou jednoduÜe oÜet°it. Vrhneme se proto na ten zajφmav∞jÜφ druh. V nßsledujφcφm odstaveΦku jsou uvedeny mo₧nΘ reakce na chybu.
Pokud se podφvßte do dokumentace p°ekladaΦe, urΦit∞ si vÜimnete, ₧e existuje mnoho funkcφ s nßvratovou hodnotu. Tato hodnota je v∞tÜinou pou₧ita k p°enosu p°φznaku chyby. Programßtor ale b²vß tvorem lφn²m a tak mßlokdy pφÜe "ty zbyteΦnΘ" °ßdky pro ov∞°enφ nßvratovΘho k≤du. Vzpome≥me si t°eba na operßtor new, kter² v p°φpad∞ chyby p°i alokaci pam∞ti vrßtφ hodnotu NULL. Je pravdou, ₧e p°i takto d∙le₧itΘ operaci v∞tÜinou nßvratovou hodnotu ov∞°φme. Ale t°eba u takovΘ funkce fprintf() neov∞°ujeme, kolik znak∙ bylo zapsßno do v²stupnφho souboru, co₧ je p°esn∞ Φφslo, kterΘ funkce vracφ. UrΦit∞ se ale najdou funkce, kterΘ si nem∙₧ou dovolit pl²tvat hodnotami vracen²mi z funkce. Potom n∞kterΘ funkce ze standardnφ knihovny jazyka C nastavujφ globßlnφ p°φznak chyby (prom∞nnß errno a funkce perror()). DalÜφ z mo₧nostφ je pak pou₧φt velice mßlo znßmΘho i pou₧φvanΘho mechanizmu obsluhy signßl∙. Poslednφ mo₧nostφ je pak pou₧φt funkcφ setjmp() a longjmp(). Prvnφ z t∞chto funkcφ ulo₧φ vÜechny d∙le₧itΘ informace procesoru do bufferu, druhß funkce tyto informace obnovφ a program tak m∙₧e pokraΦovat z mφsta, kde byl skok nastaven. Je jasnΘ, ₧e ·pln∞ nejhorÜφ reakcφ na chybu je, kdy₧ program vypφÜe chybovou hlßÜku a z niΦeho nic se ukonΦφ.
Nev²hodou v²Üe uveden²ch funkcφ je, ₧e si programßtor m∙₧e °φct: "Pot°ebuji rychle funkΦnφ program, kontrolu chyb dod∞lßm jindy". Jen₧e je jasnΘ, ₧e k tomu pak u₧ v∞tÜinou nedojde. DalÜφm problΘmem je kontrola chyb, k≤d programu je pak straÜlivou sm∞sicφ k≤du, kter² kontroluje, jestli funkce prob∞hla bez problΘm∙, a vlastnφho v²konnΘho k≤du. To pak znaΦn∞ sni₧uje Φitelnost k≤du.
Jak vφme, tak v jazyce C++ m∙₧eme vytvß°et objektovΘ typy. Ty p°i vzniku volajφ konstruktor pro svou inicializaci a p°i zßniku by m∞l b²t zavolßn destruktor, aby doÜlo k "·klidu". Pokud funkci opustφme pomocφ signßl∙ nebo funkce longjmp(), nedojde k zavolßnφ destruktor∙, co₧ Φinφ nßvrat z v²jimeΦnΘho stavu skoro nemo₧n²m. V p°φpad∞, ₧e dosßhneme v²jimeΦnΘ situace ve funkci, kterß vracφ chybu p°es nßvratovou hodnotu, nemusφ mφt ₧ßdn² smysl dßle ve funkci pokraΦovat.
Jinou mo₧nostφ zachycenφ chyby jsou prßv∞ v²jimky. Fungujφ zjednoduÜen∞ tak, ₧e v mφst∞ vzniku v²jimky vytvo°φme objekt, kter² ponese informace o vzniklΘm problΘmu a tento objekt "vyhodφme" z funkce. V²jimky majφ v²hodu, ₧e se k≤d d∞lφ na Φßst, kterß se mß provΘst a Φßst, kde se starßme o vzniklΘ chyby. Druhou v²hodou je, ₧e programßtor musφ chyby oÜet°it. Pokud funkce toti₧ vyhodφ v²jimku a volajφcφ funkce ji nezachytφ, tak se v²jimka Üφ°φ do nad°azen²ch blok∙, dokud ji n∞kdo nezachytφ.
V programu m∙₧eme vyvolat tzv. synchronnφ v²jimku pomocφ klφΦovΘho slova throw. Jak jsme si uvedli, m∙₧eme z funkce vyhodit libovoln² objekt. Tφmto objektem m∙₧e b²t jedna ze standardnφch v²jimek, °et∞zec (tedy ukazatel na char) nebo naÜe t°φda. V nßsledujφcφm k≤du funkce vyvolß v²jimku, pokud bude jako parametr zadßno zßpornΘ Φφslo:
int Test(int a)
{
int iResult = 0;
if(a < 0)
{
throw "Cislo je mensi nez 0";
}
// dalsi vypocet
return
iResult;
}
Pokud tuto funkci zavolßme se zßporn²m parametrem ve funkci main() dojde k vyvolßnφ v²jimky a program se ukonΦφ, proto₧e v²jimku nikdo nezachytil. NapiÜme nynφ obsluhu v²jimky. To provedeme tak, ₧e k≤d kter² chceme provΘst (tedy funkci Test()) umφstφme do pokusnΘho bloku try a za blok try umφstφme blok catch, kter² zachytφ v²jimku. K≤d bude vypadat nßsledovn∞:
int main() {
try {
int iResult = Test(-5);
// dalsi kod pracujici s vysledkem
}
catch(char* e)
{
cout << e << endl;
}
char c; //
cekame na stisk klavesy
cin >> c;
return 0;
}
To je nejjednoduÜÜφ reakce na chybu. KlφΦovΘ slovo throw zp∙sobφ vytvo°enφ objektu, p°iΦem₧ se pro n∞j zavolß konstruktor. Potom je tento objekt vrßcen z funkce, aΦkoliv funkce Test() vracφ hodnotu s typem int. K≤d popsan² komentß°em se neprovede, proto₧e by ani nem∞lo smysl ho provΘst se Üpatn²m v²sledkem funkce Test(). Navφc se zruÜφ vÜechny objekty, kterΘ byly v dob∞ vzniku v²jimky sprßvn∞ vytvo°eny. Pokud majφ instance objektov²ch typ∙ pam∞¥ovou t°φdu auto, jsou zavolßny jejich destruktory. Pokud bychom se z funkce vraceli normßlnφm zp∙sobem, byly by zruÜeny veÜkerΘ objekty.
Potom nßsleduje zachycenφ v²jimky v bloku catch - tzv. handler (obsluha v²jimky). V zßvorkßch za klφΦov²m slovem je uveden typ v²jimky, kter² se mß zachytit. Pokud je to stejn² typ v²jimky, kter² byl vyvolßn, vstoupφ program do tohoto bloku. V bloku je mo₧nΘ provΘst ·klidovΘ prßce - nap°φklad uzav°enφ soubor∙, pokud bychom Φφsla Φetli z pevnΘho disku. Pokud se nebude typ v²jimky shodovat dojde k Üφ°enφ v²jimky do nad°azenΘho bloku, tedy do funkce main(), v tomto p°φpad∞ by op∞t doÜlo k ukonΦenφ programu, proto₧e by vznikla nezachycenß/neobslou₧enß v²jimka (unhandled exception). V p°φpad∞ vzniku nezachycenΘ/neobslou₧enΘ v²jimky dojde k zavolßnφ funkce terminate(). Tato funkce nevolß destruktory objekt∙ a pokud dojde k zavolßnφ tΘto funkce, je to jen a jen chyba programßtora. V p°φpad∞ pot°eby lze tuto funkci p°edefinovat na jinou zavolßnφm funkce set_terminate(). Tato funkce vracφ ukazatel na p∙vodnφ funkci terminate(), jejφ₧ adresu je vhodnΘ uschovat a obnovit. Na nßmi definovanou funkci pro ukonΦenφ jsou kladeny nßsledujφcφ po₧adavky:
nesmφ vracet ₧ßdnou hodnotu (musφ tedy b²t void)
nemß ₧ßdnΘ parametry
nesmφ vyvolat v²jimku
musφ zavolat funkci, kterß ukonΦφ program (zavolßnφ terminate() znamenß, ₧e program se nem∙₧e z chyby vrßtit)
Nßsleduje p°φklad pro nastavenφ vlastnφ ukonΦovacφ funkce:
#include <iostream.h>
#include <stdlib.h>
#include <eh.h>
void konecna() {
cout << "Chyba z ktere se nelze vratit!" << endl;
exit(0);
}
void (*old_terminate)() = set_terminate(konecna);
// nastaveni vlastni funkce terminate()
int main()
{
try {
throw "Chyba";
// vyvolame vyjimku
}
catch(char*)
{
// zachyti retezce
terminate(); // dojde k okamzitemu ukonceni programu
}
return 0;
}
Jen poznamenejme, ₧e tohle je ukßzkov² p°φklad. Takhle by nem∞l program nikdy dopadnout.
Za jednφm blokem try m∙₧e nßsledovat vφce handler∙, tak₧e lze chytat vφce r∙zn²ch druh∙ v²jimek. M∙₧eme mφt nap°φklad:
try {
//blok try
}
catch(char* e)
{
// zachyti retezce
}
catch(int e)
{
// zachyti integer
}
catch(...)
{
// zachyti vse ostatni
}
Znak ... znamenß, ₧e tφmto handlerem jsou zachyceny vÜechny v²jimky. Je jasnΘ, ₧e m∙₧e b²t uveden pouze na konci seznamu handler∙, jinak by p°ebral prßci vÜem ostatnφm. Po vykonßnφ t∞la jednoho handleru program ostatnφ p°eskakuje.
Jazyk C++ p°φmo nepodporuje mo₧nost zotavenφ z v²jimky. JednoduÜe p°edpoklßdß, ₧e handler ukonΦφ korektn∞ program. To ovÜem nemusφ b²t vhodnΘ, p°edevÜφm u real-time aplikacφ. Ukß₧eme si upraven² program, kde Φφslo, kterΘ bude p°edßno jako parametr funkci Test() bude zadßvat u₧ivatel. V p°φpad∞ ÜpatnΘho Φφsla dojde k vyvolßnφ v²jimky:
#include <iostream.h>
int Test(int a)
{
int iResult = 0;
if(a < 0)
{
throw "Cislo je mensi nez 0";
}
// dalsi vypocet
return
iResult;
}
int main()
{
int input = 0;
int baddata;
while(baddata) {
try {
cout << "Zadejte
cislo: ";
cin >> input;
int iResult =
Test(input);
// Dalsi
prace s iResult
baddata = 0;
}
catch(char* e)
{
cout << e <<
endl;
baddata = 1;
}
}
char c;
cin >> c;
return 0;
}
V prom∞nnΘ baddata mßme ulo₧eno jestli u₧ivatel zadal chybnß data. Pokud vznikne v²jimka, byla zadßna Üpatnß data a smyΦka pokraΦuje. Jestli₧e prob∞hne cel² blok try, je smyΦka ukonΦena.
Kurz jazyka C++ se nßm pomalu blφ₧φ ke svΘmu konci. P°φÜt∞ dokonΦφme v²jimky, povφme si n∞co o prostorech jmen. Doposud jsme pou₧φvali objektovΘ proudy jen ke vstupu z klßvesnice nebo v²stupu na monitor, ukß₧eme si tedy, jak pomocφ nich Φφst a zapisovat z/do souboru.
PřφÜtě nashledanou.