Files
PPC/Výjimky.md

5.2 KiB

Výjimka je způsob jak aktivně kontrolovat správný chod programu a popasovat se s nečekanými chybami. V podstatě mě zajímají 3 hlavní operátory a to:

  • try -> blok, ve kterém se snažím zachytit chybu
  • catch -> blok, který řeší co se stane, když nastane chyba
  • throw -> operátor, který umí chybu vyhodit

Dále se u základní C++ setkáme s třemi knihovnami, které s chybami souvisí a to?

  • <exception> - základní funkcionalita chyb
  • <stdexcept> - seznam typů chyb, které můžeme využít na "házení" při problémech
  • <new> - umí vyhazovat chyby typické u práci s pamětí (třeba nedostatek místa)

Funkcionalitu musíme přidat pomocí #include <exceptions>

Hlavní smysl chyb je zachytit nečekané věci v programu. Neměli bychom řešit problémy s kterými počítáme. Zatímco můžeme řešit třeba dělení nulou, které nestane při očekáváném vstupu od uživale (a nějaký typ chyby na to existuje), tak protože to čekáme a připravujeme se na to, tak bychom to měli řešit spíše jinak. Chyba má smysl hlavně v případech, které nelze odtušit a to například:

  • otevření souboru, který je prázdný
  • nedostatek paměti
  • přetečení Chyby jsou praktické, ale zatěžují program. Proto je třeba s nimi být opatrný a nepoužívat je na všechno zbytečně (a také samozřejmě kvůli přehlednosti).

Pro embedded systémy existuje alternativní způsob jak řešit chyby, který není tak náročný a to std::optional a std::expected v C++23.

Základ

Strukturálně program s chytáním chyb vypadá:

try
{
# zde je blok kde hledám chybu
}
catch
{
# zde je blok kde řeším chybu
}

Try

try funguje jednoduše, všechno co je uvnitř je zpracovaný výjimka a můžu řešit, co s ní pak udělám pomocí catch. S tím pak můžu řešit důmyslnou logiku výjimek, kdy na různých vrstvách programu řeším různé problémy dle toho, kdy se mi to hodí. Například na nízké vrstvě můžu vyhodit nějakou chybu ze špatné operace. na střední vrstvě se rozhodnu, jestli je řešitelná (můžu ji třeba napravit) a nebo jestli ne. Pokud ne, tak ji pošlu do vysoké vrstvy, která ji chytí a vypíše uživateli a ukončí proces, kde vznikla.

Všechny chyby, které nejsou chyceny pomocí try spustí std::terminate, což způsobí pád programu.

Tohle je vhodné si spojit ještě s konceptem stack unwinding. To je jak chyba cestuje -> stále nahoru v cestě programu (stack -> cesta volání - spíš paměť, kde je cesta uložena). Všechno putuje nahoru a když to skončí úplně nahoře v main, tak je průser.

Throw

throw vyhazuje chybu. Může to být text, např: throw "Tady je chyba\n"; , ale obecně je spíš vhodné vyhodit nějaké oficiální objekt představující nějaký typ chyby z <stdexcept>.

např: throw std::invalid_argument( "received negative value" );

  • zde je možné poslat společně s typem chyby argument se zprávou...

Catch

V bloku catch, který následuje za try se řeší, jak se zachovat v případě nějaké chyby

try
{
	#něco
}

#catch("Text") {} #nedává smysl, nechytá se hodnota, ale typ
catch(const std::bad_alloc& e) {} #vyjímka špatné alokace
catch(int) {} #chytí všechny integery
catch(std::exception) {} #chytí všechny chyby
catch(...) {}#chytí úplně všechno

Catch funguje jako funkce. Má argumenty, které nejsou hodnoty, ale typy hodnot, které jdou "zavolat". Proto nemůžu chytat text, ale můžu chytat např. const char* e -> a e je to co se předá.

v případe catch(const std::bad_alloc& e) {} se používá Reference protože objekt může být teoreticky velký a my ho nechceme kopírovat.

What()

.what() je funkce chybových objektů. Ta vypisuje jejich textovou hodnotu. Například pro throw std::invalid_argument( "received negative value" ); vypíše "received negative value".

Volal bych třeba

catch(const std::bad_alloc& e) 
{
	std::cout << e.what()
}

set_terminate a terminate()

Když nastane situace, kdy se dojde k nějakému fatálnímu programu, na který program není připraven, zavolá se std::terminate(). Ten zavolá abort() a ukončí program. To může nastat, když výjimka není zachycena, ale případů je víc:

  • během stack unwindingu (tedy při opouštění funkcí po throw) dojde k další výjimce
  • vyhodí se výjimku z destruktoru během unwindingu
  • porušení pravidla noexcept (funkce označená jako noexcept vyhodí výjimku)

terminate je poslední funkce, která se zavolá před ukončením programu a nevrací dále dál tok programu. My pokud chceme můžeme nastavit vlastní funkci terminate, pro větší kontrolu nad programem pomocí std::set_terminate(my_func);

Pozn: destruktory nesmí házet výjimky proto, protože při výjimce nastane situace, kdy se ukončuje funkce a destruktory se volají. Pokud by destruktor vyhodil výjimku, mohla by nastat situace, kdy jsou 2 výjimky najednou, což je problém.

Typy chyb

  • runtime chyby -> range, overflow, etc.
  • logické chyby -> domain, out_of_range...
  • bad_cast
  • bad_function_call
  • bad_alloc -> většinou nedostatek paměti od new...