Kurz C++ (17.)

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++.

17.1. èablony - dokonΦenφ

17.1.1. StatickΘ atributy Üablony

    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;
}

17.1.2. Specializace Üablon

    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 navrat_typ JmenoTridy<T*>::JmenoMetody(parametry) { }    // u metod

    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*> { }

17.1.3. Instance a jejich vytvß°enφ

    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).

17.2. V²jimky

    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.

17.2.1. Detekce chyb

    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φ.

17.2.2. Vznik a zachycenφ v²jimky

    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:

    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.

17.2.3. Zotavenφ se z v²jimky

    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.

17.3. Co bude p°φÜt∞?

    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.

Ond°ej BuriÜin