Files
PPC/Výjimky.md

98 lines
5.2 KiB
Markdown

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á:
```cpp
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
```cpp
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
```cpp
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...