98 lines
5.2 KiB
Markdown
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... |