Obsluha chyb pomocí výjimek
C++ 14

image_printTisk

Tento článek je článkem úvodním tematice obsluhy chyb v jazyce C++. V tomto článku si představíme motivaci zavedení výjimek jazyka C++ jako aparátu pro detekci a zpracování chyb. Dále představíme základní syntaxi a představíme nějaké zásady, kterých bychom se při návrhu obsluhování chybových stavů měli držet.

Motivace zavedení výjimek

Než si ukážeme konkrétní syntaxi zápisu výjimek, řekneme si něco o důvodech jejich zavedení do jazyka C++. První, co je dobré si uvědomit je, že místo v programu, kde proběhne detekce nějaké chyby a místo její opravy, není téměř nikdy totožné. Například, pokud knihovna pro práci se soubory zjistí, že daný soubor, který chceme otevřít, neexistuje, může existovat několik scénářů, jak na tuto situace bude reagovat aplikační kód, který danou knihovnu používá.

Proto výjimka je jakýmsi komunikačním prostředkem, kdy knihovna, která zjistí chybu, vyvolá výjimku nějakého typu a aplikační kód pak definuje speciální obsluhu chyb, což je blok kódu, kde je výjimka zachycena a zpracována.

Druhým důvodem zavedení výjimek je oddělení výkonného aplikačního kódu od zpracování chyb. Uvažujme následující příklad:

int main() {
    int errorcode = funkce1();
    if(errorcode > 0) {  
        // Obsluha chyby a uvolnění prostředků
        goto exit;
    }

    errorcode = funkce2();
    if(errorcode > 0) {  
        // Obsluha chyby a uvolnění prostředků
        goto exit;
    }

    errorcode = funkce3();
    if(errorcode > 0) {  
        // Obsluha chyby a uvolnění prostředků
        goto exit;
    }

exit:;
    return 0;
}

Na tomto příkladu jsou vidět tři funkce, kdy každá je kontrolovaná na korektnost jejího provedení a pro případ, kdy se něco nepovede, tak je zahájena obsluha chyby, uvolnění příslušných prostředků a skok na konec programu. Co na tomto kódu není hezké, že obsluha chyb každé funkce “nafukuje” funkci main() a celý kód není vůbec přehledný.

Další nevýhoda, kterou daný kód na výše uvedeném příkladě má, je, že kdyby některá z funkcí chtěla vrátit nějakou hodnotu, nelze to provést, protože se vrací právě chybový kód. Lze samozřejmě použít reference na proměnnou a vrátit tak vracenou hodnotu v argumentu, ale ani to se nepokládá za příliš čisté řešení. Navíc se stejně nezbavíme narůstání a míchání kódu obsluhami chyb každé funkce.

Na závěr této podkapitoly si zmíníme ještě dvě klasifikace výjimek. Zaprvé můžeme výjimky členit na systémové a aplikační. Systémovou výjimkou může být například chyba dělení nulou, aplikační výjimky většinou plynou z chyb problémové domény, generované většinou čistě z vrstev aplikačního kódu. Druhé členění je spíše z hlediska jejich obsluhy, a to na opravitelné a neopravitelné. Opravitelná výjimka umožňuje obsluze výjimky uvést program do stabilního stavu. Například, pokud není soubor nalezen, lze tuto zprávu zobrazit uživateli. Kdežto u výjimek, které nelze opravit, zbývá jediné možné řešení zapsat o ní informace do logovacího souboru a před ukončením programu vyrozumět uživatele, případně administrátory systému.

Syntaxe výjimek

V této podkapitole se již podíváme na konkrétní syntaktický zápis výjimek i s podrobným popisem, jak fungují. A nejlépe opět začneme příkladem, který si dále v textu vysvětlíme.

Chyba při načítání zdrojového kódu.

Nyní přistupme k popisu příkladu. Máme zde definovanou prázdnou třídu DivisionByZeroException. Tato třída reprezentuje typ našeho chybové stavu, tedy chybu při dělení nulou. Patří mezi zásady dobrého návrhu software navrhnout a definovat hierarchickou strukturu tříd reprezentující různé chybové stavy v aplikaci. Součástí tříd reprezentující výjimky, nebo jinak chybové stavy, mohou být i doplňující informace jako chybová zpráva pro uživatele, případně číselný kód chyby. Příklad definice takové třídy pro výjimku si ale ukážeme později.

Nyní se přesuneme k popisu první funkce, která se nachází hned za třídou definující naši výjimku, a to funkci evaluateFunction(). Tato funkce bere na vstup hodnotu X a jejím úkolem je vypočítat hodnotu Y funkce dle zadaného vzorečku v příkaze return. Je důležité si povšimnout, že zde dochází k dělení hodnotou X. Jak je známo z matematiky, nelze dělit nulou, taková operace není definována. Proto před tím, než se přejde k vlastnímu výpočtu hodnoty Y, je zde umístěn podmínkový příkaz, který testuje hodnotu X na číslo nula. Pokud je podmínka splněna, dochází právě k vyvolání výjimky pomocí instance třídy DivisionByZeroException. K tomu nám slouží, jak ukazuje příklad, příkaz throw.

Další funkcí, kterou si zde popíšeme, je funkce s názvem evaluateInterval(). Tato funkce bere na svůj vstup dvě čísla A a B reprezentující uzavřený interval <a,b>, ve kterém jsou počítány zmíněné hodnoty funkce. Její tělo se rozděleno na dva bloky. První blok je uvozen klíčovým slovem try a druhému bloku předchází slovo catch. Blok try uvozuje kód, pro který se testuje výskyt výjimek, a to nejen v tomto bloku, ale v jakékoliv zavolané funkci. Na našem příkladu se v tomto bloku prochází ve for cyklu zadaný interval a vypočítané hodnoty jsou zobrazovány na standardní výstup. Blok catch má v kulatých závorkách uvedený datový typ zachytávané výjimky ve formě reference na konstantní objekt. V tomto bloku se právě musí nacházet obslužná rutina pro nápravu chyby, kterou zprostředkovává daná výjimka. Na našem příkladu je pouze na standardní výstup vypsána zpráva o chybě dělení nulou. Právě rozdělení kódu do bloků trycatch realizuje oddělení výkonného kódu od kódu obsluhující chybové stavy, jak bylo popsáno v předchozí podkapitole.

Na závěr funkce main vyvolává tři výpočty různých intervalů pro zadanou funkci. Stojí zde pouze za povšimnutí, že chybu dělení nulou vyvolává výpočet druhého intervalu, což díky zachycení dané výjimky nemá vůbec vliv na to, aby mohl být v pořádku vypočítán interval třetí.

Zásady pro obsluhu chybových stavů

 

image_printTisk
Obsluha chyb pomocí výjimek
C++ 14
Ohodnoťte tento článek

Související články

  • Technika RAII a blok finally Tento článek pojednává o technice či metodě RAII sloužící ke korektní správě prostředků i při vyhození výjimky a dále popisu bloku finally, který sice v syntaxi jazyka C++ není přímo […]
  • Překlad do strojového kódu V tomto článku se seznámíme s tím, jak překladač převádí zdrojový kód programu do binární podoby spustitelné operačním systémem. Článek se zmiňuje nejen o procesu překladu zdrojových kódů, […]
  • Komentáře Tento článek pojednává o způsobu zápisu komentářů ve zdrojových kódech jazyka C++. Budou popsány oba druhy komentářů, tedy jednořádkové i jejich víceřádková varianta. Použití […]
  • Metody předávání hodnot argumentů funkcím Tento článek pojednává o způsobech či metodách, jak lze předávat hodnoty argumentů volaným funkcím. V zásadě existují dva přístupy. Prvním je předání hodnotou, druhým je předání […]
  • Aritmetika ukazatelů a pole V tomto článku si vysvětlíme, co je aritmetika ukazatelů. Ukážeme si význam a aplikaci dalších operátorů vztahujících se k ukazatelům a popíšeme si jejich vzájemné vztahy k polím různých […]
  • Ukazatele V tomto článku si představíme nový pojem ukazatel jako proměnnou obsahující adresu na jinou oblast paměti. Seznámíme se dvěma operátory při související s adresací a na závěr si ukážeme, […]